Skip to content

Insecure Deserialization

What does this mean ?

When an application deserializes user-controllable data, this is known as insecure deserialization. This might allow an attacker to modify serialized objects in order to inject malicious data into the application code. It is even feasible to replace a serialized object with one of a completely different class. Surprisingly, regardless of whatever class was expected, objects of any class that is exposed to the application will be deserialized and created. As a result, unsafe deserialization is frequently referred to as an "object injection" vulnerability.

What can happen ?

An exception may be thrown if an object of an unexpected class is used. However, the harm may have already been done by this point. Many deserialization-based attacks are accomplished before the deserialization process is complete. This implies that even if the application's own functionality does not directly interact with the malicious item, the deserialization process might start an attack. As a result, applications whose logic is based on highly typed languages may be vulnerable to these tactics as well. Because it gives an access point to a vastly enlarged attack surface, unsecured deserialization can have a devastating impact. It enables an attacker to repurpose existing application code in malicious ways, resulting in a slew of additional flaws, including remote code execution. Even in the absence of remote code execution, unsafe deserialization can result in privilege escalation, unauthorized file access, and denial-of-service attacks.

Recommendation

The only safe architectural approach is to avoid accepting serialized objects from unknown sources or using serialization classes that only support basic data types. If it isn't an option, consider one or more of the following:

  • Integrity checks, such as digital signatures, should be used on all serialized objects to avoid hostile object creation or data manipulation.
  • As the code normally expects a specified set of classes, strong type restrictions are enforced during deserialization before object creation. Because bypasses to this strategy have been established, relying completely on it is not recommended.
  • When feasible, isolate and execute code that deserializes in low privilege settings.
  • Deserialization exceptions and failures, such as when the incoming type is not the intended type or the deserialization produces exceptions, should be logged.
  • Restricting or monitoring incoming and outgoing network connectivity from deserializing containers or servers.
  • Deserialization is being monitored, and an alert is being issued if a user deserializes on a regular basis.

Sample Code

Vulnerable :

using System.Web.UI.WebControls;
using System.Web.Script.Serialization;

class Bad
{
    public static object Deserialize(TextBox textBox)
    {
        JavaScriptSerializer sr = new JavaScriptSerializer(new SimpleTypeResolver());
        // BAD
        return sr.DeserializeObject(textBox.Text);
    }
}

Non Vulnerable :

using System.Web.UI.WebControls;
using System.Web.Script.Serialization;

class Good
{
    public static object Deserialize(TextBox textBox)
    {
        JavaScriptSerializer sr = new JavaScriptSerializer();
        // GOOD
        return sr.DeserializeObject(textBox.Text);
    }
}

Vulnerable :

public class RequestProcessor {
  protected void processRequest(HttpServletRequest request) {
    ServletInputStream sis = request.getInputStream();
    ObjectInputStream ois = new ObjectInputStream(sis);
    Object obj = ois.readObject(); // Noncompliant
  }
}

Non Vulnerable :

public class SecureObjectInputStream extends ObjectInputStream {
  // Constructor here

  @Override
  protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
    // Only deserialize instances of AllowedClass
    if (!osc.getName().equals(AllowedClass.class.getName())) {
      throw new InvalidClassException("Unauthorized deserialization", osc.getName());
    }
    return super.resolveClass(osc);
  }
}

public class RequestProcessor {
  protected void processRequest(HttpServletRequest request) {
    ServletInputStream sis = request.getInputStream();
    SecureObjectInputStream sois = new SecureObjectInputStream(sis);
    Object obj = sois.readObject();
  }
}

Vulnerable :

$data = $_GET["data"];
$object = unserialize($data);
// ...

Non Vulnerable :

$data = $_GET["data"];

list($hash, $data) = explode('|', $data, 2);
$hash_confirm = hash_hmac("sha256", $data, "secret-key");

// Confirm that the data integrity is not compromised
if ($hash === $hash_confirm) {
  $object = unserialize($data);
  // ...
}

Vulnerable :

var escape = require('escape-html');
var serialize = require('node-serialize');

app.get('/', function(req, res) {
  if (req.cookies.profile) {
      var str = new Buffer(req.cookies.profile, 'base64').toString();
      var obj = serialize.unserialize(str);

      if (obj.username) {
        res.send( "Hello " + escape(obj.username));
      }
  } else {
      res.cookie('profile', token , {
        maxAge: 500000,
        httpOnly: true
      });
      res.send("Hello world");
  }
});

Non Vulnerable :

var escape = require('escape-html');
var serialize = require('node-serialize');
const { check } = require('express-validator');

app.get('/', function(req, res) {
  if (req.cookies.profile) {
      var str = new Buffer(req.cookies.profile, 'base64').toString();
      var patched = check(str).isString().escape().trim();
      var obj = serialize.unserialize(patched);

      if (obj) {
        res.send( "Hello " + escape(obj));
      }
  } else {
      res.cookie('profile', token , {
        maxAge: 500000,
        httpOnly: true
      });
      res.send("Hello world");
  }
});

Vulnerable :

require 'yaml'
require 'set'

file = File.read('set.yml')
s = YAML.load(file)
p s

Non Vulnerable :

require 'yaml'
require 'set'

file = File.read('set.yml')
s = YAML.safe_load(file)
p s

References