使用Semgrep保护Apollo GraphQL服务器
tl;dr:我们公开的Semgrep规则集新增了9条规则,用于检测Apollo GraphQL服务器v3和v4的配置错误。使用semgrep --config p/trailofbits即可尝试。
在审计多个客户的Apollo GraphQL服务器时,我反复发现相同的问题:跨站请求伪造(CSRF)允许攻击者以用户身份执行操作、缺乏速率限制导致密码或MFA令牌可被暴力破解,以及跨源资源共享(CORS)配置错误使得攻击者能够获取本不应访问的敏感信息。开发者忽视这些问题有多种原因:Apollo GraphQL v3的默认配置不佳(例如csrfProtection选项默认未开启)、对GraphQL特定功能(如批量查询)理解不足,以及对Web概念(如同源策略和CORS工作原理)认识不清。
重复发现相同问题促使我利用内部研发时间,在未来审计中持续检测这些问题,从而腾出时间寻找更深层、更复杂的漏洞。Semgrep——一种用于检测单文件中简单模式的静态分析工具——是理想选择,因为这些问题可通过类grep结构轻松检测,且无需跨过程或其他复杂分析。
我们开源了用于发现Apollo GraphQL服务器v3和v4配置错误的Semgrep规则。这些规则利用Semgrep的污点模式,简化编写过程并提高准确性。快去测试你的GraphQL服务器吧!
我们此前已公开发布用于检测Go并发错误和机器学习库误用的Semgrep规则。
常见GraphQL问题
GraphQL的某些设计选择使得一些漏洞(如CSRF)比传统REST服务器更普遍。当然,GraphQL服务器也面临所有常见问题:访问控制缺陷(如GitLab中泄露私有用户信息的漏洞,或HackerOne中允许攻击者披露用户机密数据的漏洞)、SQL注入(如HackerOne GraphQL服务器中的SQL注入)、服务器端请求伪造(SSRF)、命令注入等。
本文将重点介绍我们创建的用于检测CSRF和CORS配置错误的规则,并展示如何使用Semgrep的污点模式减少定义值流入接收器所需模式的数量,从而节省时间并提高规则准确性。
CSRF
CSRF是一种攻击手段,允许恶意行为者诱骗已认证用户在其访问的网站上执行非预期操作(如修改用户资料)。若需了解细节,请阅读PortSwigger的Web安全学院中关于CSRF的说明。
Apollo服务器中的CSRF攻击
在引入csrfPrevention选项前,CSRF问题一直困扰着Apollo GraphQL服务器。CSRF漏洞在Apollo服务器中普遍存在的原因有二:开发者将变更(mutation)误标为查询(query),以及Apollo服务器允许用户通过GET请求执行查询操作(但不允许变更操作)。查询不应改变状态(类似RESTful API中的GET请求),而变更预期会改变状态(类似POST、PATCH、PUT或DELETE)。若开发者遵循此约定,一切正常。然而,我尚未发现哪个代码库未将变更误标为查询,这使得这些误标操作立即面临CSRF攻击风险。
幸运的是,Apollo团队深知此问题,并在v3版本中添加了csrfPrevention选项以彻底解决。该选项通过确保任何请求必须包含非text/plain、application/x-www-form-urlencoded或multipart/form-data的Content-Type头、非空的X-Apollo-Operation-Name头或非空的Apollo-Require-Preflight头来防止CSRF攻击。这确保请求始终会预检,从而阻止CSRF攻击。
csrfPrevention选项在v3中默认为false,在v4中默认为true,因此仍使用v3的用户需在服务器初始化时主动添加此选项——根据我们的经验,这几乎从未发生。
使用Semgrep检测CSRF配置错误
我们创建了两条Semgrep规则来检测v3和v4中的配置错误。对于v3,我们查找所有未将csrfPrevention选项设置为true的ApolloServer初始化:
|
|
图1.1:检测Apollo服务器v3中csrfProtection选项配置错误的Semgrep规则
对于v4,我们查找所有将csrfPrevention选项设置为false的服务器初始化:
|
|
图1.2:检测Apollo服务器v4中csrfProtection选项配置错误的Semgrep规则
CORS
CORS允许服务器放宽浏览器的同源策略(SOP)。如预期那样,开发者有时过度放宽SOP,可能导致攻击者获取本不应访问的敏感信息。若需了解细节,请阅读PortSwigger的Web安全学院中关于CORS的说明。
在Apollo服务器中设置CORS策略
在Apollo服务器v3中,开发者可通过两种方式设置CORS策略。其一,将cors参数传递给ApolloServer类实例:
|
|
图1.3:在Apollo GraphQL服务器v3中配置CORS
其二,在其使用的后端框架上设置CORS策略。例如,对于Express.js后端服务器,CORS属性作为参数传递给applyMiddleware函数:
|
|
图1.4:在使用Express后端的Apollo GraphQL服务器v3中配置CORS
在Apollo服务器v4中,开发者必须直接在后端设置CORS。因此,为v4编写规则超出我们Apollo特定Semgrep查询的范围——其他Semgrep规则已覆盖大多数此类情况。
我们的v3规则覆盖了Express.js和内置Apollo服务器后端的用法,因为这些是我们最常见的使用场景。若你使用其他后端框架,我们的规则可能不适用,但我们欢迎向trailofbits/semgrep-rules提交PR!基于现有查询适配应非常轻松。;)
检测缺失的CORS策略
各后端的规则非常相似,因此我们以检测内置Apollo服务器中CORS配置错误的规则为例。我们在同一文件中有两条规则:一条检测未定义CORS策略的情况,另一条检测配置不当的CORS策略。
为检测缺失的CORS策略,我们查找cors参数未定义的ApolloServer实例化,并确保ApolloServer来自apollo-server包(ApolloServer类也可能来自apollo-server-express包,但我们不希望捕获这些情况)。查询如图1.5所示:
|
|
图1.5:检测Apollo GraphQL服务器v3中缺失CORS策略的Semgrep规则
检测不良CORS策略
检测不良CORS策略则不那么简单。我们需要检测以下几种情况:
- 源(origin)设置为
true——true表示服务器接受所有源。 - 源设置为
null——攻击者可诱骗用户从沙盒iframe等环境发起来自null源的请求。 - 源是包含未转义点字符的正则表达式——正则表达式中点匹配任意字符,因此若使用
/api.example.com$/正则表达式,它将匹配apiXexample.com域(可能被攻击者控制)。 - 源未以
$字符结尾——正则表达式中$匹配字符串结尾,因此若使用/api.example.com/正则表达式,它也会匹配api.example.com.attacker.com域(攻击者控制的域)。
这些并未覆盖所有不良CORS策略(例如,不良策略可能直接包含攻击者域或允许攻击者上传HTML代码的域)。我们通过以下规则测试上述所有情况:
|
|
图1.6:检测不良CORS源的Semgrep模式
这些不良源可单独使用或包含在数组中。为测试这两种情况,我们首先检查独立或数组中的$CORS_SINGLE_ORIGIN元变量,然后使用元变量模式通过图1.6创建的模式定义何为不良源:
|
|
图1.7:检测单条目或数组中不良CORS源的Semgrep模式
最后,我们需要在ApolloServer初始化中找到这些源的用法。通过以下模式实现:
|
|
此$CORS_ORIGIN可直接内联使用(如cors: true),或来自变量(如cors: corsOriginVariableDefineElsewhere)。手动定义源的所有可能来源非常繁琐。幸运的是,借助Semgrep的污点模式,我们无需如此!
我们只需定义以下内容:
- pattern-sources:不良CORS策略——定义为
{origin: $BAD_CORS_ORIGIN},其中$BAD_CORS_ORIGIN是前述定义的不良源模式。 - pattern-sinks:不良CORS策略不应流入的位置——定义为模式
new ApolloServer({..., cors: $CORS_ORIGIN, ...})中的$CORS_ORIGIN元变量。
通过污点模式,我们可以捕获设置CORS策略的多种方式:直接设置(图1.8案例1)、通过配置整个CORS策略的变量(案例2)、通过仅设置源的变量(案例3),以及许多其他我们不想手动定义的配置。
|
|
图1.8:Semgrep污点模式免费帮助捕获的多个测试案例
完整注释规则如图1.9所示:
|
|
图1.9:检测Apollo GraphQL服务器v3中不良CORS策略的Semgrep规则
我们还为希望详细审查Apollo服务器CORS策略的审计者和安全工程师创建了一条Semgrep规则,即使策略可能安全。该规则报告任何非false或空数组的CORS策略——这些显然是良好的CORS策略。当你想手动检查所有硬编码源时非常有用,但不建议集成到CI管道中,因为它会产生误报(审计规则)。你可以在trailofbits.javascript.apollo-graphql.v3-cors-audit.v3-potentially-bad-cors找到此规则。
总结
Semgrep擅长发现单文件中的简单模式,如本文所述。对于更复杂的分析,你可能需要使用如CodeQL之类的工具,但它也有缺点:学习曲线更陡峭、对不同语言使用不同API、需要编译代码,且不支持Semgrep支持的某些语言(如Rust)。
Semgrep的最大限制之一是缺乏文件间和过程间分析。例如,上述规则无法捕获CORS策略在一个文件中设置而Apollo Server初始化在另一个文件中的情况。这现在可能通过Semgrep Pro Engine(原DeepSemgrep)实现,它增强了Semgrep引擎的文件间分析能力。但该功能目前仅限付费客户且支持语言有限。
在Trail of Bits,我们广泛使用静态分析工具,并常编写针对客户代码库的自定义规则和查询。这些规则能提供巨大价值,因为它们可发现代码库特定模式,甚至强制执行组织的最佳工程实践。当我们编写的规则对社区有用时,我们乐于开源它们。请访问https://github.com/trailofbits/semgrep-rules 查看。
使用semgrep --config p/trailofbits尝试我们的新Apollo GraphQL规则,并尝试编写你自己的自定义规则吧!