Skip to content

Commit

Permalink
Land #18899, update ysoserial viewstate tool
Browse files Browse the repository at this point in the history
  • Loading branch information
adfoster-r7 authored Mar 14, 2024
2 parents 44c5422 + bcb4e3a commit 55dd5aa
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 83 deletions.
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

0 comments on commit 55dd5aa

Please sign in to comment.