使用Semgrep加固Apollo GraphQL服务器的安全配置

本文介绍了如何利用Semgrep静态分析工具检测Apollo GraphQL服务器v3和v4中的常见安全配置问题,包括CSRF防护缺失和CORS策略错误。通过实际规则示例展示Semgrep的污点模式如何提升检测准确性。

使用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/plainapplication/x-www-form-urlencodedmultipart/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
2
3
4
patterns:
  - pattern: new ApolloServer({...})
  - pattern-not: |
      new ApolloServer({..., csrfPrevention: true, ...})

图1.1:检测Apollo服务器v3中csrfProtection选项配置错误的Semgrep规则

对于v4,我们查找所有将csrfPrevention选项设置为false的服务器初始化:

1
2
3
patterns:
  - pattern: |
      new ApolloServer({..., csrfPrevention: false, ...})

图1.2:检测Apollo服务器v4中csrfProtection选项配置错误的Semgrep规则

CORS

CORS允许服务器放宽浏览器的同源策略(SOP)。如预期那样,开发者有时过度放宽SOP,可能导致攻击者获取本不应访问的敏感信息。若需了解细节,请阅读PortSwigger的Web安全学院中关于CORS的说明。

在Apollo服务器中设置CORS策略

在Apollo服务器v3中,开发者可通过两种方式设置CORS策略。其一,将cors参数传递给ApolloServer类实例:

1
2
3
4
5
import { ApolloServer } from 'apollo-server';

const apolloServerInstance = new ApolloServer({
    cors: CORS_ORIGIN
});

图1.3:在Apollo GraphQL服务器v3中配置CORS

其二,在其使用的后端框架上设置CORS策略。例如,对于Express.js后端服务器,CORS属性作为参数传递给applyMiddleware函数:

1
2
3
4
5
6
7
8
import { ApolloServer } from 'apollo-server-express';

const apolloServerInstance = new ApolloServer({});

apolloServerInstance.applyMiddleware({
    app,
    cors: CORS_ORIGIN,
});

图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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
patterns:
  - pattern-either:
      - pattern-inside: |
          $X = require('apollo-server');
          ...
      - pattern-inside: |
          import 'apollo-server';
          ...
  - pattern: |
      new ApolloServer({...})
  - pattern-not: |
      new ApolloServer({..., cors: ..., ...})

图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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pattern-either:
  # 'true'表示反射所有源
  - pattern: |
      true
  # 点字符未转义
  - pattern-regex: ^/.*[^\\]\..*/$
  # 正则表达式未以'$'结尾
  - pattern-regex: ^/.*[^$]/$
  # 攻击者可从'null'源发起请求
  - pattern: |
      'null'

图1.6:检测不良CORS源的Semgrep模式

这些不良源可单独使用或包含在数组中。为测试这两种情况,我们首先检查独立或数组中的$CORS_SINGLE_ORIGIN元变量,然后使用元变量模式通过图1.6创建的模式定义何为不良源:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
pattern-either:
  - patterns:
      # 独立模式或在数组中
      - pattern-either:
          - pattern: |
              $CORS_SINGLE_ORIGIN
          - pattern: |
              [..., $CORS_SINGLE_ORIGIN, ...]
      - metavariable-pattern:
          metavariable: $CORS_SINGLE_ORIGIN
          pattern-either:
             # <前述不良源检查>

图1.7:检测单条目或数组中不良CORS源的Semgrep模式

最后,我们需要在ApolloServer初始化中找到这些源的用法。通过以下模式实现:

1
new ApolloServer({..., cors: $CORS_ORIGIN, ...})

$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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 案例1:具有非常宽松的'cors'(true)
const apollo_server_bad_1 = new ApolloServer({
    //ruleid: apollo-graphql-v3-bad-cors
    cors: { origin: true }
});

// 案例2:从变量获取非常宽松的'cors'
const bad_CORS_policy = { origin: true }
const apollo_server_bad_2 = new ApolloServer({
    //ruleid: apollo-graphql-v3-bad-cors
    cors: bad_CORS_policy
});

// 案例3:从变量获取非常宽松的'cors'(仅源)
const bad_origin = true;
const apollo_server_bad_3 = new ApolloServer({
    //ruleid: apollo-graphql-v3-bad-cors
    cors: { origin: bad_origin }
});

图1.8:Semgrep污点模式免费帮助捕获的多个测试案例

完整注释规则如图1.9所示:

 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
mode: taint
pattern-sources:
  - patterns:
      - pattern-inside: |
          { origin: $BAD_CORS_ORIGIN }
      - metavariable-pattern:
          metavariable: $BAD_CORS_ORIGIN
          pattern-either:
            # 'true'表示反射所有源
            - pattern: |
                true
            - patterns:
                # 独立模式或在数组中
                - pattern-either:
                    - pattern: |
                        $CORS_SINGLE_ORIGIN
                    - pattern: |
                        [..., $CORS_SINGLE_ORIGIN, ...]
                - metavariable-pattern:
                    metavariable: $CORS_SINGLE_ORIGIN
                    pattern-either:
                      # 点字符未转义
                      - pattern-regex: ^/.*[^\\]\..*/$
                      # 正则表达式未以'$'结尾
                      - pattern-regex: ^/.*[^$]/$
                      # 攻击者可从'null'源发起请求
                      - pattern: |
                          'null'
pattern-sinks:
  - patterns:
      # ApolloServer来自'apollo-server'包
      - pattern-either:
          - pattern-inside: |
              $X = require('apollo-server');
              ...
          - pattern-inside: |
              import 'apollo-server';
              ...
      # 接收器是ApolloServer的cors参数
      - pattern: |
          new ApolloServer({..., cors: $CORS_ORIGIN, ...})
      # 告知Semgrep接收器仅为$CORS_ORIGIN变量
      - focus-metavariable: $CORS_ORIGIN

图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规则,并尝试编写你自己的自定义规则吧!

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计