Get Your Kicks on Route Sixty-Sink: Identifying Vulnerabilities Using Automated Static Analysis
Mandiant
Written by: Dillon Franke, Michael Maturi


Introduction
Today, we are releasing Route Sixty-Sink, an open-source tool that enables defenders and security researchers alike to quickly identify vulnerabilities in any .NET assembly using automated source-to-sink analysis. Route Sixty-Sink has already been used to find and exploit dozens of critical security issues, an example of which will be discussed in this blog post.
Background: Source-to-Sink Analysis
Identifying vulnerabilities within application binaries or source code is often a long and tedious process. To help with this, source-to-sink analysis is used—a form of data flow analysis that attempts to identify user input (a source) that is passed as the argument of a function call of interest (a sink).
A source is an entry point where user input enters an application and a sink is an action performed by the application, using user input from the source. When performing vulnerability research, sinks can be thought of as dangerous function calls that are executed using input from a source. Tables 1 and 2 provide several examples of sources and sinks.
Sources
Sinks
By enumerating a list of sinks, identifying them within an application, and backtracking them to user-controlled input, source-to-sink analysis can effectively identify vulnerabilities. As a straightforward example, imagine a .NET web application with an API route handler that looks like this:
using System.Diagnostics;
using System.Web;
public class HomeController : Controller
{
public IActionResult Index()
{
string arg = HttpUtility.ParseQueryString(myUri.Query).Get("q");
Process.Start("getHealth.exe", arg);
return View();
}
}
Assuming the System.Diagnostics.Process.Start()
API call was a sink we were interested in using, we would:
- Search the code base for the
System.Diagnostics.Process.Start()
function - Identify the
q
query parameter - Identify the API controller and route (
/home?q=
) - Analyze the user-supplied argument (
q
) passed to the sink and determine whether it introduces a vulnerability - Craft an exploit to obtain command injection (
GET /home?q=
)
What Does Route Sixty-Sink Solve?
Although it is effective, proper source-to-sink analysis is a time-consuming and manual process that is often infeasible. The following table identifies the main three pain points with manual static analysis and explains how Route Sixty-Sink helps resolve them.
How Does it Work?
Overview
Route Sixty-Sink traces the flow of user input through any .NET assembly and determines whether it is passed as an argument to a dangerous function call (a “sink”).
Route Sixty-Sink does this using two main modules:
- RouteFinder, which enumerates API routes in ASP.NET Core MVC and classic ASP page web applications.
- SinkFinder, which takes an entry point and creates a call graph of all classes and method calls. Then, it queries strings, method calls, and class names for sinks.


RouteFinder
RouteFinder quickly parses all identified route entry points exposed by a web application.
Input
As input, RouteFinder takes a compiled .NET web application assembly or a directory containing assemblies that make up the web application. RouteFinder supports the following routing:
- ASPX, ASMX, ASHX, and ASCX Pages
- ASP.NET Core MVC Routing
- Attribute Routing
- Conventional Routing (Basic default convention)
Output
RouteFinder outputs each assembly, controller, prefix, route, action, and authorization policy for all identified API endpoints within the application. An example is shown in Figure 1:


Figure 1: RouteFinder route parsing
SinkFinder
Given an entry point, SinkFinder recursively follows method calls within an application to find a user-defined list of “sinks.” It uses the following algorithm to do so:
- Enumerate classes and methods from input and dependency C# binaries and store them in a call graph
- Parse Common Intermediary Language (”CIL”) instructions starting with the entry point and identify all method calls
- If a sink is found, inform the user
- For each method call:
- Look up the method within the call graph and access it
- Repeat steps 2-3
Input
As input, SinkFinder takes a compiled .NET assembly or directory of assemblies. It also optionally accepts an additional .NET assembly or directory of assemblies to use as dependencies. The dependencies will be loaded into the call graph and their functions will be traversed to search for sinks.
Output
If any sinks are found, SinkFinder outputs a complete call graph of all method calls that lead to the sink. This allows the researcher to quickly identify the data flows resulting in a sink call.
Figure 2 shows an example of a SinkFinder call graph ending at a sink. Notice how SinkFinder can identify a sink through multiple nested function calls and third party dependencies. This would be very difficult to do using traditional static analysis approaches.


Figure 2: SinkFinder identifying a nested sink
Using Standalone SinkFinder to Query an Assembly
Let’s say you have a C# executable, and you’d like to determine whether it calls dangerous functions that could allow for local privilege escalation. This process becomes very simple using SinkFinder.
First, we would specify a list of sinks to search for in JSON format. A list of common sinks that will get most security researchers off to a great start can be found within the sinks.json
file of the Route Sixty-Sink GitHub repository. We run SinkFinder, and bam! We’ve already got two great leads for a local privilege escalation.


Figure 3: Finding sinks with standalone SinkFinder
Route Sixty-Sink: Combining RouteFinder and SinkFinder
The real magic happens when the RouteFinder and SinkFinder modules are combined to create Route Sixty-Sink—a tool that performs comprehensive source-to-sink analysis from API routes to sinks within seconds, allowing security researchers to spend less time reviewing source code and more time discovering vulnerabilities.


Figure 4: Route Sixty-Sink harnesses the combined power of RouteFinder and SinkFinder
Figure 4: Route Sixty-Sink harnesses the combined power of RouteFinder and SinkFinder ">
Case Study: Using Route Sixty-Sink to Find a Real World Vulnerability
About six months ago, we tested a large web application using the .NET MVC architecture for a client who had provided us with the application’s compiled assemblies. Before we dive into the vulnerability we found and how Route Sixty-Sink helped identify it, we need a small amount of background information.
Insecure Deserialization in Newtonsoft.Json
The Newtonsoft.Json.JsonConvert::DeserializeObject()
function is a sink we commonly hunt for (and is included within our starter sinks.json
list). This function instantiates an object using properties specified in a passed JSON string. This process is known as deserialization, and if it is not performed safely, critical vulnerabilities may be introduced that can lead to arbitrary object instantiation resulting in remote code execution. Muñoz and Mirosh provide an excellent overview to unsafe deserialization vulnerabilities.
Default deserialization with Newtonsoft.Json.JsonConvert::DeserializeObject()
is normally safe, as it will not instantiate any object other than a specified expected type. However, the Newtonsoft library allows developers to pass in a special Newtonsoft.Json.JsonSerializerSettings
object as a parameter which can allow for arbitrary objects to be instantiated. Table 4 shows an unsafe Newtonsoft.Json.JsonSerializerSettings
object being passed to the deserialization call which introduces a vulnerability if user input ends up within the JSON string.
var deser = JsonConvert.DeserializeObject(json, new
JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
Finding Unsafe Deserialization with Route Sixty-Sink
With this in mind, we crafted a regex query shown in the figure below in order to identify calls to Newtonsoft.Json.JsonConvert::DeserializeObject()
that accepted a Newtonsoft.Json.JsonSerializerSettings
object as a parameter.
Newtonsoft.Json.JsonConvert::DeserializeObject.*JsonSerializerSettings
Using the custom regex, we then ran Route Sixty-Sink on the compiled MVC application. As shown in Figure 5, Route Sixty-Sink quickly identified an API endpoint which called a function that matched the regex we built.


Figure 5: RouteFinder parsing MVC routes
Notice that Route Sixty-Sink conveniently identified both the controller (Discussions
) and the assembly (BaseCommon.dll
) and composed a call graph of all methods in the controller, one of which eventually led to the execution of our sink.
Next, we popped BaseCommon.dll
into DnSpy, a useful .NET decompiler, and located the specific class and method that executed our sink. The decompiler output is shown in Figure 6.


Figure 6: Unsafe deserialization of user input
As shown, the application took the DiscussionContext
parameter from the POST
request and deserialized it unsafely! We were then able to use ysoserial.net to generate a JSON payload that, when deserialized, would issue a DNS request to a server controlled by us. Our final exploit is shown in Figure 7.


Figure 7: Deserialization RCE Payload
A few moments later, we received a callback to our DNS server, proving that we had executed code on the application server.


Figure 8: DNS callback proving code execution
Contributing
The decision to release Route Sixty-Sink was made to allow the security community to both reap its benefits and continue its growth. The following sections explain the highest priority next steps for Route Sixty-Sink.
Use Route Sixty-Sink!
Our biggest hope for Route Sixty-Sink is that researchers and developers to use the tool to allow us to identify its strengths, shortcomings, and to make improvements.
Adding to the sinks.json
List
Provided with Route Sixty-Sink is a sinks.json
file containing a list of common .NET sinks that have been commonly used to discover vulnerabilities. We hope the security research community will continue to add to this file in order to improve Route Sixty-Sink’s sink detection capabilities.
Advanced Source-to-Sink Analysis
We hope to add additional features to Route Sixty-Sink in future releases, including:
- Parameter parsing for use as sources
- Advanced taint analysis
- Symbolic execution
- Low/High vulnerability fidelity assessment based on source-to-sink depth, intermediary validation, and/or sanitization functions
- Identification of non-web-based user-controlled inputs, including data from IPC mechanisms, data on disk, sockets, etc.
Additional Development
We designed Route Sixty-Sink with a modular design in order to encourage community collaboration and continued development. A researcher who wishes to define a new type of source can easily add it to Route Sixty-Sink and benefit from automated source-to-sink analysis.
For example, one might implement a module that identifies all .NET deserialization sources. Then, it would be possible to connect the module to SinkFinder to quickly identify interesting gadget chains.
If you have an idea for a new module, please reach out to us or submit a pull request!
Conclusion
We have introduced Route Sixty-Sink, an open source utility to perform automated source-to-sink analysis within .NET binaries. We have already used Route Sixty-Sink to identify dozens of issues in open-source and proprietary web applications, and we hope it will continue to identify more issues in order to improve the state of application security worldwide. We urge the reader to use and contribute to this project!
GitHub Repository
Our GitHub repository can be found at https://github.com/mandiant/route-sixty-sink