An Unsafe Deserialization Vulnerability and Types of Deserialization
Deserialization
Unsafe Deserialization (also referred to as Insecure Deserialization) is a vulnerability wherein malformed and untrusted data input is insecurely deserialized by an application. It is exploited to hijack the logic flow of the application end might result in the execution of arbitrary code. Although this isn’t exactly a simple attack to employ, it featured in OWASP’s Top 10 most recent iteration as part of the Software and Data Integrity Failures risk, due to the severity of impact upon compromise.
The process of converting an object state or data structure into a storable or transmissible format is called serialization. Deserialization is its opposite - the process of extracting the serialized data to reconstruct the original object version.
Unsafe Deserialization issues arise when an attacker is able to pass ad hoc malicious data into user-supplied data to be deserialized. This could result in arbitrary object injection into the application that might influence the correct target behavior.
Impact
A successful Unsafe Deserialization attack can result in the full compromise of the confidentiality, integrity, and availability of the target system, and the oft-cited Equifax breach is probably the best example of the worst outcome that can arise. In Equifax’s case, an unsafe Java deserialization attack leveraging the Struts 2 framework resulted in remote code execution, which, in turn, led to the largest data breach in history.
Prevention
It is important to consider any development project from an architectural standpoint to determine when and where serialization is necessary. If it is unnecessary, consider using a simpler format when passing data.
In cases where it is impossible to forego serialization without disrupting the application’s operational integrity, developers can implement a range of defence-in-depth measures to mitigate the chances of being exploited.
- Use serialization that only permits primitive data types.
- Use a serialization library that provides cryptographic signature and encryption features to ensure serialized data are obtained untainted.
- Authenticate before deserializing.
- Use low privilege environments to isolate and run code that deserializes.
Finally, if possible, replace object serialization with data-only serialization formats, such as JSON.
Testing
Verify that serialization is not used when communicating with untrusted clients. If this is not possible, ensure that adequate integrity controls (and possibly encryption if sensitive data is sent) are enforced to prevent deserialization attacks including object injection.
OWASP ASVS: 1.5.2, 5.5.1, 5.5.3
Types of Deserializations
Unsafe Deserialization in .NET
Vulnerable Example
[Serializable]
public class SomeClass
{
public string SomeProperty { get; set; }
public double SomeOtherProperty { get; set; }
}
class Program
{
static void Main(string[] args)
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream(File.ReadAllBytes("untrusted.file"));
SomeClass obj = (SomeClass)binaryFormatter.Deserialize(memoryStream);
Console.WriteLine(obj.SomeProperty);
Console.WriteLine(obj.SomeOtherProperty);
}
}
[Serializable]
public class DangerousClass
{
private string path;
public DangerousClass(String path) {
this.path = path;
}
public ~DangerousClass() {
File.Delete(path)
}
}
Prevention
var someObject = new SomeClass();
someObject.SomeProperty = "some value";
someObject.SomeOtherProperty = 3.14;
using (BinaryWriter writer = new BinaryWriter(File.Open("untrusted.file", FileMode.Create)))
{
writer.Write(someObject.SomeProperty);
writer.Write(someObject.SomeOtherProperty);
}
var someObject = new SomeClass();
using (BinaryReader reader = new BinaryReader(File.Open("untrusted.file", FileMode.Open)))
{
someObject.SomeProperty = reader.ReadString();
someObject.SomeOtherProperty = reader.ReadDouble();
}
References
Unsafe Deserialization in Java
Vulnerable Example
@Controller
public class MyController {
@RequestMapping(value = "/", method = GET)
public String index(@CookieValue(value = "myCookie") String myCookieString) {
// decode the Base64 cookie value
byte[] myCookieBytes = Base64.getDecoder().decode(myCookieString);
// use those bytes to deserialize an object
ByteArrayInputStream buffer = new ByteArrayInputStream(myCookieBytes);
try (ObjectInputStream stream = new ObjectInputStream(buffer)) {
MyObject myObject = stream.readObject();
// ...
}
}
}
Prevention
ObjectInputStream objectInputStream = new ObjectInputStream(buffer);
stream.setObjectInputFilter(MyFilter::myFilter);
public class MyFilter {
static ObjectInputFilter.Status myFilter(ObjectInputFilter.FilterInfo info) {
Class<?> serialClass = info.serialClass();
if (serialClass != null) {
return serialClass.getName().equals(MyClass.class.getName())
? ObjectInputFilter.Status.ALLOWED
: ObjectInputFilter.Status.REJECTED;
}
return ObjectInputFilter.Status.UNDECIDED;
}
}
public class MyFilteringInputStream extends ObjectInputStream {
public MyFilteringInputStream(InputStream inputStream) throws IOException {
super(inputStream);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
if (!objectStreamClass.getName().equals(MyClass.class.getName())) {
throw new InvalidClassException("Forbidden class", objectStreamClass.getName());
}
return super.resolveClass(objectStreamClass);
}
}
References
Unsafe Deserialization in NodeJS
Vulnerable example
function myJSONParse(data) {
return eval(`(${data})`);
}
myJSONParse("require('child_process').exec('id > /tmp/proof')"
Prevention
const object = {foo: 123};
JSON.stringify(object) // '{"foo":123}'
JSON.parse('{"foo":123}') // { foo: 123 }
References
Unsafe Deserialization in PHP
<?php
class FSResource {
function __construct($path) {
$this->path = $path;
if (file_exists($path)) {
$this->content = file_get_contents($path);
}
}
function __destruct() {
file_put_contents($this->path, $this->content);
}
}
$resource = new FSResource('/tmp/file');
print(serialize($resource));
# Prints the following string representation:
# O:10:"FSResource":2:{s:4:"path";s:9:"/tmp/file";s:7:"content";s:0:"";}
$instance = unserialize('O:10:"FSResource":2:{s:4:"path";s:9:"/tmp/file";s:7:"content";s:0:"";}');
print($instance->path);
# Prints the path attribute:
# /tmp/file
Vulnerable Example
<?php
$instance = unserialize($_GET["data"]);
http://localhost/script.php?data=O:10:%22FSResource%22:2:{s:4:%22path%22;s:9:%22shell.php%22;s:7:%22content%22;s:27:%22%3C?php%20system($_GET[%22cmd%22]);%22;}
Prevention
References
Unsafe Deserialization in Python
Vulnerable example
import pickle
@app.route("/import_object", methods=['POST'])
def import_object():
data = request.files.get('user_file').read()
user_object = pickle.loads(data)
store_in_database(user_object)
return 'OK'
import pickle
import os
class Pickle(object):
def __reduce__(self):
return os.system, ('id > /tmp/proof',)
o = Pickle()
p = pickle.dumps(o)
print(p)
sf@secureflag.com:~$ python3 generate.py
b'\x80\x03cposix\nsystem\nq\x00X\x0f\x00\x00\x00id > /tmp/proofq\x01\x85q\x02Rq\x03.'
sf@secureflag.com:~$ python3
>>> import pickle
>>> pickle.loads(b'\x80\x03cposix\nsystem\nq\x00X\x0f\x00\x00\x00id > /tmp/proofq\x01\x85q\x02Rq\x03.')
0
sf@secureflag.com:~$ cat /tmp/proof
uid=1000(sf) gid=1000(sf) groups=1000(sf)
Prevention
References
Unsafe Deserialization in Ruby
User = Struct.new(:name, :role)
user = User.new('Mike', :admin)
puts Marshal.dump(user).inspect
# Prints the following string representation:
# "\x04\bS:\tUser\a:\tnameI\"\tMike\x06:\x06ET:\trole:\nadmin"
user = Marshal.load("\x04\bS:\tUser\a:\tnameI\"\tMike\x06:\x06ET:\trole:\nadmin")
print(user.name);
# It prints the following string:
# Mike
Vulnerable Example
class FSResource
def initialize path
@path = path
end
def to_s
open(@path).read
end
end
# Craft the payload to execute `id` via the `open` function instead of opening a file
obj = FSResource.new('|id')
payload = Marshal.dump(obj)
# Unserializing the payload allows to execute arbitrary commands
serialized_obj = Marshal.load(payload)
puts serialized_obj
# It prints the output of id:
# uid=1002(admin) gid=1002(admin) groups=1002(admin)
References
Unsafe Deserialization in Scala
Vulnerable Example
def handler() =
AuthAction(parse.multipartFormData) { implicit request => {
request.body.file("file") match {
case Some(file) => {
// deserialize data from Base64 file upload
val base64Data = new String(Files.readAllBytes(Paths.get(file.ref.path.toString()))).trim()
val data = Base64.getDecoder().decode(base64Data)
val ois = new ObjectInputStream(new ByteArrayInputStream(data))
val object = ois.readObject().asInstanceOf[MyClass]
ois.close()
// ...
}
case None => InternalServerError("...")
}
}
}
Prevention
class SafeInputStream(inputStream: InputStream) extends ObjectInputStream(inputStream) {
override def resolveClass(objectStreamClass: java.io.ObjectStreamClass): Class[_] = {
objectStreamClass.getName match {
case "MyClass" | "scala.Some" | "scala.Option" => super.resolveClass(objectStreamClass)
case _ => throw new InvalidClassException("Forbidden class", objectStreamClass.getName)
}
}
}
Post a Comment