C#
C Sharp and the .NET Framework
Hello World
The first step is creating a new project. With the console
template for a simple CLI app, you can easily fill an empty directory with the necessary files:
You can find external packages in the NuGet Gallery, and then add them to your project:
Finally, run the main Program.cs
file:
Deserialization
There are different ways to serialize objects in C#, which is the process of turning it into a string. Then, this string can be passed around through other channels and eventually be deserialized to receive an identical copy of the original object.
Creating arbitrary objects with fields is dangerous when this deserialized string is in the attacker's control. By abusing lax configuration, you can instantiate objects with special behaviour to read/write files, or even achieve Remote Code Execution if the right gadgets are accessible.
Newtonsoft Json.NET
The most common form on deserialization in the web is JSON. The Json.NET library is the most widely-used for turning some string from the user into an instance of a class. The fields on this class define the structure of the JSON, for example (source):
The above example is secure, because it only allows deserializing basic data types. It can be wrongly configured, however, to allow all classes instead, which may include dangerous ones we call "gadgets". This is possible if a JsonSerializerSettings
is given as the 2nd argument with a .TypeNameHandling
value other than None
.
This enables a special $type
key for each JSON object (also in nested properties) that can reference any loaded class, and set its fields. This is only possible for properties with the Object
type because all gadgets will inherit from it:
You can easily generate a payload by serializing it first with the same library and classes, then send it to the target. Make sure to include TypeNameHandling.All
to ensure any types are included and the target can resolve them. You should structure your classes exactly the same as the target because the $type
key includes this information:
When ran with dotnet run
, this will generate the object with payload first, and then serialize it into JSON ready to send to the target. The 2nd part will similar the target receiving the string, and deserializing it into a vulnerable type. You will see that the set {}
method is called twice:
The syntax is pretty simple, so if you want to, you can even handcraft these payloads. The syntax for the $type
key is Path.To.Class, AssemblyName
, where the path to the class is follows the nested structure of namespaces and classes to your gadget.
For another example, see the writeup below:
Json.NET is far from the only library allowing arbitrary objects to be deserialized. To get an overflow, see the table below to understand which library supports what features:
Gadget Chains
You'll be very lucky if you have the source code of your target application, and find a single setter in there that allows RCE. Instead, you should rely on chains of gadgets, often in widely-used libraries.
One small gadget can maybe call a function on another gadget, which grabs a property from a third gadget to ultimately use it in an unsafe way. It's an art to combine these in creative ways, and requires a good understanding of what's available and possible in the codebase. The ysoserial.net
tool collects such gadgets and can generate them with payloads at will:
To use it, select a gadget chain with -g
, select the Formatter with -f
(eg. Json.Net
). Most gadgets will achieve RCE, and with the -c
argument you can customize the final shell command it executes.
If the target loads the PresentationFramework
assembly and you cause it to insecurely deserialize the above payload, the calc.exe
command will be executed. If the conditions on the target are unknown, you should try many different known chains until one works.
Finding Gadgets
To find your own gadgets, you should look for code that you are able to trigger during deserialization. These are get {}
and set {}
methods as mentioned above, but the constructor will also be called. You can pass named arguments to the constructor by your key names, for example:
Some gadgets will call methods on your arguments, such as the HashMap
calling .hashCode()
to turn it into a unique integer. This means any vulnerable logic inside an object's hashCode
implementation will also be callable if we just wrap in in a hashmap! Combing gadgets in chains like this is the standard way to find exploits.
LINQ Injection
Language Integrated Query (LINQ) is a Microsoft library for C# used to query objects similar to SQL syntax. It does, however, support C# syntax with function calls embedded inside the syntax, such as:
The above inserts user input from showProducts.name
into the Where()
call, which without sanitization allows an attacker to escape the "
(double quote) and rewrite the query. For example:
X") || 1==1 || "" == ("X
: Shows all productsX") || 1==2 || "" == ("X
: Empty array
Version < 1.3.0 RCE
The following Github Repository and accompanying article explain how to exploit such an injection for consistent Remote Code Execution.
Latest version property access
The patch only restricts method calling to predefined types. This means that methods on Strings, Arrays, etc. will work, but methods on custom types will not. It is still possible to run methods on custom types that are inherited from allowed classes, and it is still possible to access any properties.
"".GetType().Module.Assembly
still works to get the Standard Module.
GetType().Module.Assembly
gets the module of the object passed into the Where()
function, often custom code.
By chaining more properties and using ToArray()
on enumerables, it is possible to enumerate all classes, attributes, properties and methods in a module. The following script implements this using binary search and requires a test()
function that injects in such a way that you can evaluate a condition.
Example output looks like this (note that some magic members are also added, these can be ignored):
Filter Bypasses
Any method call like
.GetType()
can be obfuscated as.@GetType()
Whitespace also works, eg.
. GetType()
Last updated