1. 项目概述:为什么选择DVGA来学习GraphQL安全?
如果你正在接触Web安全,或者对API安全测试感兴趣,那么GraphQL这个技术栈你一定绕不开。它不像传统的REST API那样有固定的端点,一个
/graphql
入口就能处理所有请求,这种灵活性给开发者带来了便利,但也给安全带来了全新的挑战。传统的扫描工具和测试思路,在GraphQL面前常常会“失灵”。我自己在实战和内部渗透测试中,就遇到过不少因为GraphQL接口防护不当导致的数据泄露甚至远程代码执行案例。
那么,怎么系统性地掌握这些漏洞呢?看书看文档太抽象,直接拿生产环境练手又风险太高。这时候,一个设计精良的靶场就成了最佳选择。Damn Vulnerable GraphQL Application,简称DVGA,就是这样一个为安全研究者和开发者量身打造的“训练场”。它不是一个简单的漏洞列表,而是一个完整的、故意设计得漏洞百出的GraphQL应用,涵盖了从信息泄露到命令执行的10种常见高危漏洞。通过亲手攻击它、理解漏洞成因、再尝试修复,你能获得最接近实战的体验。
这不仅仅是“复现”,而是真正理解GraphQL安全攻防的思维。接下来,我会带你深入DVGA,拆解这10种漏洞的原理、利用手法,并分享我在测试过程中总结的排查技巧和防御思路。
2. DVGA环境搭建与核心功能解析
2.1 本地部署与初始化配置
DVGA的部署非常友好,官方推荐使用Docker,这也是最接近生产环境的方式。首先,确保你的机器上已经安装了Docker和Docker Compose。
# 克隆DVGA的仓库
git clone https://github.com/dolevf/Damn-Vulnerable-GraphQL-Application.git
cd Damn-Vulnerable-GraphQL-Application
# 使用docker-compose一键启动
docker-compose up -d
启动成功后,在浏览器中访问
http://localhost:5013
,你就能看到DVGA的Web界面了。第一次访问时,应用会提示你进行初始化设置,主要是设置一个管理密码。这个密码在后续某些挑战中会用到,务必记好。
注意:DVGA默认配置下,GraphQL的Introspection(自省)功能是开启的。这是一个关键点,因为在实际安全测试中,我们首先就要确认目标GraphQL端点是否暴露了过多的元信息。DVGA故意保留了这个“弱点”,作为我们学习的起点。
应用主要包含几个部分:一个前端操作界面,用于提交GraphQL查询和查看结果;一个后台的GraphQL服务器,处理所有逻辑;以及一个内置的SQLite数据库,存储用户、帖子等数据。整个架构模拟了一个简单的博客社交平台,有用户、帖子、评论等实体,这为我们后续的漏洞利用提供了丰富的上下文。
2.2 GraphQL基础与攻击面认知
在开始“攻击”之前,我们需要统一一下语言。如果你对GraphQL还不熟,没关系,理解几个核心概念就够了:
- 查询(Query) :用于获取数据,类比REST的GET。
- 变更(Mutation) :用于修改数据(增删改),类比REST的POST/PUT/DELETE。
- 订阅(Subscription) :用于建立实时数据连接(在DVGA中涉及较少)。
- 模式(Schema) :定义了所有可用的查询、变更及其参数和返回类型。这是GraphQL的“合同”。
- 自省(Introspection) :GraphQL内置的功能,允许客户端查询其模式定义。这是攻击者的“地图”。
GraphQL的攻击面与传统API有很大不同:
-
单点入口
:所有操作都通过
/graphql端点,没有不同的URL路径可供模糊测试。 - 强类型系统 :模式是固定的,攻击需要在其定义的“框架”内进行。
- 客户端指定查询 :服务器执行客户端发来的任意复杂查询,这带来了深度递归查询(DoS)和非法字段访问的风险。
- 错误信息 :默认情况下,GraphQL可能会返回详细的错误信息,包括堆栈跟踪,这可能导致信息泄露。
理解了这些,你就会明白,对GraphQL的测试,核心在于如何构造合法的GraphQL查询语句来实现非法的目的。接下来,我们就进入正题。
3. 十大GraphQL漏洞原理与实战利用
3.1 信息泄露:自省(Introspection)与错误处理
这是GraphQL安全测试的“敲门砖”。自省功能允许我们获取完整的模式定义。在DVGA中,你可以直接使用以下查询来获取所有类型、查询和变更:
query {
__schema {
types {
name
fields {
name
type {
name
kind
}
}
}
}
}
利用这个查询,你可以像查看API文档一样,发现所有可用的操作,比如隐藏的
admin
查询、
deleteAllUsers
变更等。在实际渗透测试中,如果目标GraphQL端点未禁用自省,你就获得了一份完整的攻击面清单。
另一个常见的信息泄露源是错误处理。DVGA中有一处设计,当查询不存在的字段时,服务器可能会返回包含内部路径或代码片段的详细错误。防御的关键在于生产环境中配置GraphQL服务器只返回模糊的错误信息(如“Internal Server Error”),而不是开发环境的详细跟踪。
实操心得
:我习惯使用
GraphQL Voyager
或
InQL
(Burp Suite插件)来可视化自省结果。图形化的界面能让你更快地理解数据模型之间的关系,发现那些可能被遗忘的、权限控制不当的深层字段。
3.2 注入类漏洞:SQL注入与NoSQL注入
是的,GraphQL并不能免疫注入攻击。如果后端在解析GraphQL参数后,将其不安全地拼接成数据库查询语句,注入漏洞依然存在。
在DVGA中,存在一个用于搜索帖子的查询,例如:
query {
posts(search: \"用户输入\") {
title
content
}
}
如果后端这样处理(伪代码):
query = \"SELECT * FROM posts WHERE title LIKE '%\" + args[\"search\"] + \"%'\"
那么传入
search: \"' OR '1'='1\"
就可能造成SQL注入。
对于NoSQL数据库(如MongoDB),如果使用了类似
$where
或不安全的
find
参数,也可能发生注入。DVGA包含了这样的场景,例如通过注入JavaScript表达式来操纵查询逻辑。
利用步骤 :
- 探测 :通过自省找到接收字符串参数的查询或变更。
-
模糊测试
:尝试输入典型的注入载荷,如单引号
'、双引号\"、反斜杠\\、括号()等,观察响应是否有语法错误或行为异常。 - 确认与利用 :根据错误信息或不同的返回结果,判断注入类型(字符型、数字型、布尔盲注)。然后使用GraphQL查询的格式,逐步注入Union查询、条件语句等,来提取数据。
-
自动化工具
:虽然传统的sqlmap对GraphQL支持有限,但可以配合
--data参数将GraphQL查询作为POST数据发送。更好的选择是使用专门的工具如GraphQLmap或InQL的扫描功能。
注意:GraphQL的参数通常是JSON格式,并且有类型约束。你的注入载荷必须构造为合法的JSON字符串值。例如,你需要对双引号和反斜杠进行转义:
\"search\": \\\"' OR 1=1-- \\\"。
3.3 权限绕过与业务逻辑漏洞
GraphQL本身不处理授权,这完全交给业务逻辑。如果开发者在每个“解析器”(Resolver,即处理每个字段的函数)中没有正确实施权限检查,就会导致越权。
DVGA中典型的例子是“访问私有帖子”。假设有一个查询:
query {
post(id: 123) {
title
content
private # 这是一个布尔字段,标记帖子是否私有
}
}
一个设计不当的解析器可能只在返回
private
字段时为
true
时检查用户权限,但在返回帖子
content
时却没有检查。攻击者即使不是作者,只要不请求
private
字段,就能直接读到私有帖子的内容。这就是一种“权限依赖于请求字段”的错误逻辑。
另一种情况是ID枚举。通过自省发现
getUser(id: ID)
查询后,攻击者可以遍历ID值(如1,2,3...),尝试获取其他用户的信息。如果后端没有检查“当前用户只能查询自己的资料”,就会导致水平越权。
排查技巧 :测试时,使用两个不同的用户账户(如普通用户A和管理员B,或两个普通用户)。用A的身份获取一个资源的ID(如你的帖子ID是5),然后用B的身份尝试用同样的查询和ID(5)去访问。观察是否能成功。关键在于测试所有涉及对象ID输入的查询和变更。
3.4 跨站脚本(XSS)漏洞
XSS可能发生在两个地方:一是GraphQL API返回的数据在前端被不安全地渲染;二是GraphQL接口本身(如GraphiQL或GraphQL Playground这类内置IDE)存在缺陷。
DVGA模拟了第一种情况。例如,一个创建评论的变更:
mutation {
createComment(postId: 1, content: \"<script>alert('XSS')</script>\") {
id
}
}
如果前端在展示评论内容时没有进行正确的HTML编码,那么当其他用户浏览这条评论时,脚本就会被执行。
测试方法
:找到所有接收富文本或长文本输入的变更操作,尝试插入简单的XSS载荷,如
<img src=x onerror=alert(1)>
。然后,通过相应的查询去获取并查看这些数据是如何被渲染的。如果使用的是GraphiQL,可以直接在响应面板查看;如果是集成的Web前端,则需要到前端页面去触发渲染。
3.5 文件上传与路径遍历
GraphQL可以通过
multipart/form-data
格式支持文件上传。DVGA包含了一个头像上传的功能。漏洞可能出现在多个环节:
-
文件类型校验绕过
:仅检查客户端传来的
Content-Type(如image/png),而不是检查文件魔数(Magic Number)。 -
路径遍历
:保存文件时,使用了用户可控的文件名,且未进行规范化处理。例如,文件名设置为
../../../etc/passwd,可能导致任意文件写入。 - 服务端模板注入(SSTI) :如果上传的文件后续会被某些模板引擎解析(如上传头像后文件名被嵌入HTML),可能造成更严重的漏洞。
在DVGA中,你需要通过自省找到文件上传的变更(通常叫
uploadFile
或
updateAvatar
),然后尝试上传一个包含恶意代码的图片文件(在EXIF信息中插入Payload),或者尝试使用路径遍历的文件名。
3.6 源代码与敏感文件泄露(Source Map)
这是一个现代Web应用中越来越受关注的漏洞。为了便于调试,前端JavaScript在构建时通常会生成
sourcemap
文件(
.map
后缀),它包含了压缩代码到原始源代码的映射。如果这些
.map
文件被部署到了生产环境的公开目录下,攻击者就可以利用它们还原出几乎完整的、带有清晰变量名和逻辑的源代码。
DVGA的前端可能引用了这样的source map。通过使用浏览器开发者工具的“源代码”面板,或者使用像
sourcemap-extractor
这样的工具,扫描前端JS文件末尾的
//# sourceMappingURL=
注释,就能找到.map文件的地址。还原后的代码可能泄露API密钥、内部接口路径、隐藏功能甚至后端逻辑漏洞。
实操心得
:在测试任何Web应用时,养成检查前端JS资源的习惯。除了
sourcemap
,还要注意是否有
.git
目录泄露、
DS_Store
文件、配置文件(如
.env
、
config.json
)被错误地放置在Web根目录下。
3.7 拒绝服务(DoS)与深度递归查询
这是GraphQL特有的高风险漏洞。由于GraphQL允许客户端在单个请求中指定非常复杂、嵌套的查询,如果服务器没有设置查询深度和复杂度限制,攻击者可以构造一个深度递归的查询,让服务器陷入巨大的计算或数据库查询中,从而导致拒绝服务。
例如,DVGA中可能存在“用户-帖子-评论”的循环引用关系。攻击者可以构造如下查询:
query {
user(id: 1) {
posts {
comments {
author { # author 可能又关联回 User
posts {
comments {
# ... 不断嵌套下去
}
}
}
}
}
}
}
只需嵌套几十层,就足以拖垮一个没有防护的服务器。
防御与测试
:在生产环境中,必须使用像
graphql-depth-limit
这样的中间件来限制查询深度(通常限制在5-10层)。测试时,你可以编写脚本自动生成不同深度的嵌套查询,观察服务器的响应时间和资源消耗。如果服务器在深度增加时响应时间呈指数级增长或直接崩溃,就说明存在风险。
3.8 批量操作滥用(Batch Requests)
某些GraphQL客户端或中间件支持批量请求,即将多个查询或变更打包在一个HTTP请求中发送。这原本是为了提高效率,但可能被滥用进行撞库攻击或暴力破解。
例如,DVGA的登录变更可能是:
mutation {
login(username: \"admin\", password: \"guess1\") { token }
}
攻击者可以构造一个包含数百个不同密码的批量变更请求,在一个HTTP连接中完成大量猜测尝试,从而绕过基于IP或请求频率的简单防护。
测试方法
:检查GraphQL端点是否接受数组形式的请求体。尝试发送一个包含多个
login
变更的JSON数组。观察服务器是顺序执行(可能被滥用)还是拒绝批量请求。防御措施包括禁用批量处理、对敏感操作(登录)实施严格的速率限制和验证码。
3.9 指令滥用(Directives)与查询持久化(Persisted Queries)
GraphQL指令(如
@include
、
@skip
)可以动态控制字段是否包含在响应中。在某些实现中,如果指令的参数处理不当,可能引入逻辑漏洞。此外,一些系统使用“持久化查询”,即客户端只发送查询的ID,服务器根据ID执行预定义的查询。如果ID的映射关系可被预测或篡改,可能导致未授权的查询被执行。
DVGA可能包含了需要特定指令才能访问的字段。攻击者需要尝试在未授权的情况下,通过操作指令参数来“激活”这些隐藏字段。对于持久化查询,则需要尝试枚举或猜测查询ID。
3.10 服务器端请求伪造(SSRF)与命令执行(RCE)
这是最严重的漏洞类别。如果GraphQL接口的某个参数被不安全地用于发起内部网络请求(如从用户提供的URL获取数据),就可能造成SSRF。更进一步,如果参数被传递到系统命令执行函数(如
eval()
、
os.system()
),则可能导致RCE。
在DVGA中,可能会有一个“从URL导入头像”的功能:
mutation {
updateProfile(avatarUrl: \"http://attacker.com/malicious.jpg\") { ... }
}
如果后端服务器直接请求了这个URL,攻击者可以将其指向内部服务(如
http://169.254.169.254/latest/meta-data/
获取云服务器元数据),实现SSRF。
更危险的场景是,可能存在一个“系统工具”相关的变更,接收一个
command
参数:
mutation {
runTool(name: \"ping\", args: \"127.0.0.1; cat /etc/passwd\") { output }
}
如果后端直接拼接字符串并调用
system()
,就会导致命令注入。
利用与防御
:测试所有接收URL或文件路径的参数,尝试访问
file:///etc/passwd
或内部网络地址。对于命令执行,使用分号
;
、反引号
`
、
$()
、
|
、
&
等符号进行测试。防御的核心是永远不要信任用户输入,对URL进行严格的协议和白名单域名的校验,对于系统命令调用,使用参数化列表(如Python的
subprocess.run([\'ping\', \'-c\', \'1\', user_input])
)而非字符串拼接。
4. 漏洞挖掘与自动化测试实战流程
掌握了单个漏洞的原理后,我们需要一套系统的方法来对未知的GraphQL端点进行安全评估。
4.1 手动测试流程与思维导图
-
侦察与信息收集 :
-
使用浏览器开发者工具或代理工具(如Burp Suite)捕获流量,找到GraphQL端点(通常是
/graphql、/api、/v1/graphql等)。 - 发送一个简单的自省查询,探测自省功能是否开启。如果被禁用,尝试通过错误信息或暴力猜解常见类型/字段名来收集信息。
-
使用
GraphQL Voyager或InQL对自省结果进行可视化分析,绘制出完整的数据模型图。
-
使用浏览器开发者工具或代理工具(如Burp Suite)捕获流量,找到GraphQL端点(通常是
-
漏洞扫描与探测 :
- 注入 :对所有字符串、数字参数进行模糊测试。
- 越权 :使用两个不同权限的令牌(Token),测试所有涉及对象ID的查询和变更。
- DoS :发送深度嵌套(>10层)和字段数量巨大的查询,观察响应。
- 敏感数据泄露 :检查错误信息、检查前端源码中的sourcemap、尝试访问常见的备份文件路径。
-
深入利用与漏洞链组合 :
- 将发现的漏洞组合。例如,先通过信息泄露拿到一个隐藏的管理员API名称,再通过权限绕过调用它,最后利用该API的命令注入参数实现RCE。
- 尝试绕过可能的WAF或基础防护。例如,对于GraphQL,WAF可能只检查常见的SQL注入关键词,但你可以将Payload放在GraphQL变量(Variables)中,或者对查询语句进行轻微变形(如添加换行、注释)。
4.2 自动化工具链介绍与使用
纯手动测试效率低,合理利用工具能事半功倍。
-
侦察阶段 :
-
InQL(Burp Suite Extension):最佳图形化工具,可进行自省、生成查询模板、扫描漏洞。 -
GraphQLmap:命令行工具,用于信息收集、注入扫描和利用。 -
Clairvoyance:在自省被禁用时,通过暴力破解和错误分析来重建GraphQL模式。
-
-
漏洞利用与扫描 :
-
GraphQL Raider(Burp Suite Extension):专注于GraphQL的主动扫描器,能自动测试注入、DoS等。 -
Nuclei:社区有大量针对GraphQL漏洞的检测模板,可进行大规模自动化扫描。
-
-
自定义脚本 : 对于复杂的逻辑漏洞或特定的利用链,最终往往需要自己编写Python脚本。使用
requests库发送精心构造的GraphQL查询,并处理返回的JSON数据。自动化处理登录、令牌管理、多步骤攻击流程。
实操心得 :不要过度依赖自动化工具。工具能帮你发现“低垂的果实”,但真正高危的、逻辑复杂的漏洞往往需要人工审计代码(如果可能)和深入理解业务流。我的习惯是先用InQL快速摸清API全貌,然后针对高风险操作(如文件上传、用户管理、系统操作)进行重点手动测试。
5. 防御方案设计与安全开发实践
攻击是为了更好的防御。理解了这些漏洞的利用方式,我们就能在设计和使用GraphQL时更好地规避它们。
5.1 服务端安全配置清单
以下是一份在生产环境中部署GraphQL服务时必须检查的安全清单:
| 安全措施 | 具体实现与推荐工具 | 防护的漏洞类型 |
|---|---|---|
| 禁用生产环境自省 |
在GraphQL服务器配置中关闭Introspection。例如,Apollo Server使用
introspection: false
。
| 信息泄露 |
| 控制错误信息 |
使用自定义的
formatError
函数,只返回对用户友好的通用错误信息,不泄露堆栈跟踪。
| 信息泄露 |
| 实施查询成本分析 |
使用
graphql-cost-analysis
等中间件,为类型和字段设置复杂度权重,拒绝超成本查询。
| DoS(深度/复杂度) |
| 设置查询深度限制 |
使用
graphql-depth-limit
,限制查询嵌套深度(如最大6-10层)。
| DoS(深度递归) |
| 实施查询持久化 | 使用Persisted Queries,只允许执行预定义的白名单查询。 | 批量滥用、未授权查询 |
| 严格的输入验证 | 在GraphQL模式验证之外,在解析器(Resolver)层对输入进行严格的类型、范围、格式校验。 | 注入、路径遍历、SSRF |
| 逐字段授权检查 | 在每个解析器的开头,根据当前用户上下文和要访问的资源,进行权限判断。 不要 依赖前端查询的字段选择。 | 权限绕过 |
| 安全的文件处理 | 文件上传时,校验文件魔数、重命名文件、存储在非Web可访问目录、使用云存储服务。 | 文件上传、路径遍历 |
| 命令执行安全 | 绝对避免使用用户输入拼接系统命令。使用参数化调用或更安全的API。 | 命令注入(RCE) |
| 启用CORS限制 | 正确配置CORS策略,只允许可信的前端域名访问GraphQL端点。 | CSRF等 |
5.2 安全SDL(安全开发生命周期)集成
安全不应是事后补救,而应融入开发流程:
- 设计阶段 :在定义GraphQL模式时,安全团队和开发团队一起进行威胁建模。问自己:这个字段暴露给哪些角色?这个变更操作可能被如何滥用?
-
开发阶段
:
- 使用静态代码分析工具(SAST)扫描代码中的安全反模式(如拼接SQL、命令)。
- 在代码审查中,将解析器的权限检查作为必审项。
-
测试阶段
:
- 将DVGA这类靶场作为新人培训和安全意识教育的工具。
- 在CI/CD管道中集成针对GraphQL的自动化安全测试(如使用Nuclei进行扫描)。
- 定期进行渗透测试,重点关照GraphQL接口。
-
运维阶段
:
- 在API网关层对GraphQL端点实施严格的速率限制和请求大小限制。
- 监控异常的查询模式,如深度过大、频率过高、来自异常IP的请求。
GraphQL是一把双刃剑,它强大的灵活性要求开发者承担起更大的安全责任。通过像DVGA这样的靶场进行系统性学习,你不仅能掌握攻击技巧,更能深刻理解每一种漏洞背后的根本原因,从而在设计和开发时就能将其规避。安全是一个持续的过程,保持学习,保持警惕,才能构建出真正健壮的应用。
163

被折叠的 条评论
为什么被折叠?



