【51CTO.com快译】作为最为流行的查询语言之一,GraphQL虽然能够支持创建灵活的API,但是它也容易放过、甚至给应用程序服务器带来各种恶意的查询。一些常见的GraphQL漏洞往往会在一致性评估和缺陷缓解等方面埋下各种安全隐患。在本文中,我们将深入和您探讨GraphQL的各种常见漏洞,以及降低此类风险的优秀实践。

什么是GraphQL?

作为一种服务器端运行时(runtime)的API查询语言,GraphQL能够优先返回客户端请求的数据。该语言不但能够让API变得轻巧、灵活,而且对于开发人员十分友好且方便他们进行快速开发。而作为REST API框架的替代方案,GraphQL允许开发团队在单个接口的调用中,创建可访问来自多个数据源的请求。该语言可以被部署到集成开发环境(IDE)中,并提供描述用户该如何请求数据的语法。可以说,GraphQL既提供了一个可预测运行的框架,又允许开发人员自行选择构建API的方法。

GraphQL的常见安全问答

API攻击普遍吗?

随着API普遍被使用,针对它的攻击尝试在数量上也在持续增加。这些攻击通常依赖于通过应用程序的编程接口,去自动化执行各种恶意操作。根据Wallarm.com的一份统计报告,截至2019年,恶意自动化攻击主机、及其网络已占互联网的20%。这使得保护API成为应用程序安全措施的一个关键环节。

是否有简化GraphQL安全性的工具?

目前,业界有多种开源的项目,可以简化GraphQL API的创建和管理。其中包括:Apollo client、Offix、Graphback和 OpenAPI-to-GraphQL。

GraphQL如何处理身份验证和授权?

由于GraphQL是一种服务器端运行时查询语言,因此它并不处理授权的逻辑。不过,该平台允许开发人员,在向客户端公开API之前,在业务逻辑层中实施身份验证和授权的检查。

GraphQL的具体安全性问题

凭借着其丰富的平台功能,以及能够简化API查询的创建过程,GraphQL已被誉为现代应用开发技术栈的关键组件。而为了协助企业减少GraphQL API的攻击面,我们可以通过如下方面来管控GraphQL平台的固有安全问题:

自定义标量的验证不足

在使用GraphQL时,原始数据往往是由标量类型(scalar type)表示的。GraphQL API通常支持五种基本的标量数据类型,即:Int、Float、Boolean、ID和String。虽然这个基本集合对于简单的API来说已经足够了,但是GraphQL也允许开发人员针对特殊的原始数据API类型需求,自行创建标量类型。当然,开发人员需要针对此类配置,额外地增加用户输入的验证、以及清理的过程。相反,如果未能实现此类功能,则会危及到GraphQL标量类型的安全性。

REST代理充当API攻击媒介

在调整现有的API以供GraphQL客户端调用时,开发人员通常会将GraphQL实现为,在内部REST框架之上的一个瘦代理层。如果在没有充分考虑到安全因素的情况下实施此类转换,那么恶意用户就可以任意修改API请求中所指定的路径或参数。而修改后的请求在解析到后端API时,攻击者便可以实施跨站请求伪造( cross-site request forgery,CSRF)了。

授权缺陷

GraphQL将配置授权和身份验证的检查责任,留给了最终实现者。即,GraphQL API在查询级别的解析器、以及加载附加数据的解析器中,需要包含多项授权检查。而当授权由查询级解析器直接处理时,任何未经检查的API实例都会暴露受攻击面。而且,随着API模式复杂性的增加,此类被攻击者利用的漏洞风险也会随之增加。

自省查询(Introspection Queries)可能会暴露敏感数据

开发人员为了去实现那些无法公开访问的“隐藏”API端点,会启用GraphQL服务器之间的API端点通信,或通过隐藏的管理功能来实现。其中,GraphQL包含了一个自省功能,可以在没有适当授权的情况下,轻松地访问各个端点。由于自省功能允许客户端访问有关GraphQL架构的信息,因此一旦有攻击发生,自省查询就可以被用于访问API的相关配置、以及其他客户端的私有信息。

速率限制难以实施

从本质上说,GraphQL API是比较复杂的。它的每一个查询都会涉及到多项操作,并且会消耗大量的服务器资源。因此,光靠限制接收到的HTTP请求数量,并使用默认的速率限制策略是不够的。如果两种对象类型之间存在着某种循环关系,那么攻击者就可以通过创建各种滥用查询(abusive queries),从而让查询本身变得异常复杂。以此产生的编排,能够对GraphQL应用发起拒绝服务式(DoS)的攻击。

常见的GraphQL漏洞

下面,我们来进一步讨论GraphQL有哪些常见的漏洞,可被恶意攻击者在API层面利用。

GraphQL批处理攻击

GraphQL框架能够支持自省查询的批处理,即:能够在一次性调用中,向后端API发送多个请求。由于减少了请求与服务器之间的往返次数,因此这对于减少API请求的开销非常实用。不过,攻击者也可以使用查询的批处理功能,通过反复从API服务器、或数据库处加载数据,来编排各种快速且难以被检测到的暴力攻击。

以下典型示例展示了,在搜索数字记录对象标识(Digital Record Object Identification,DROID)对象的不同实例时,进行GraphQL批处理查询的代码:

query {   droid(id: "2000"){     name   }   second:droid(id: "2001"){     name   }   third:droid(id: "2002"){     name   } }

而攻击者可以通过制造一些网络请求,来枚举API服务器中的每一个droid对象。这就可能会导致API级别的DoS攻击、暴力破解秘密数据、绕过请求的速率限制、以及对象枚举等安全问题。

GraphQL注入攻击

GraphQL API通常与作为数据源的数据库管理系统相连接。API后端的Resolver在收到请求后,会根据操作集来区分查询。在Resolver查询数据库时,如果其操作涉及到数据的提取,那么就会直接执行相应的获取操作。可见,如果来自API客户端的数据,在未被适当清理的情况下,执行任何受信的操作,那么黑客就可以通过编排SQL/NoSQL,来实施注入攻击。同样,如果对输入的清理不够充分,攻击者还会执行诸如LDAP注入、以及命令注入其他形式的注入攻击

GraphQL CSRF攻击

跨站请求伪造(CSRF)攻击是指,在合法用户不知情时,强迫Web服务器运行那些不必要的操作。当存在CSRF漏洞时,攻击者会在当前登录用户的上下文,发送经过身份验证的请求。而GraphQL类型的应用极易受到CSRF攻击,毕竟API在接收浏览器的请求时,会自动接受所有的cookie(其中就包括了会话cookie)。

目前,主要有两种类型的GraphQL CSRF攻击:基于Post和基于Get的CSRF。由于GraphQL使用多个API层来转换传入的多格式请求,而且能够影响到GraphQL应用的状态,因此大多数CSRF攻击通常以POST请求为目标。通常,许多开发人员会只接受设置为application/json的Content-Type标头。例如,以下POST请求可用于发出有效的GraphQL查询:

POST /GraphQLHTTP/1.1 Host: redacted Connection: close Content-Length: 100 accept: */* User-Agent: ... content-type: application/json Referer: https://redacted/ Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Cookie: ... {"operationName":null,"variables":{},"query":"{\n  user {\n    firstName\n    __typename\n  }\n}\n"}

服务器可以将此请求作为form-urlencoded POST请求予以接受:

POST /GraphQLHTTP/1.1 Host: redacted Connection: close Content-Length: 72 accept: */* User-Agent: Mozilla/5.0(Macintosh; Intel Mac OS X 11_2_2)AppleWebKit/537.36(KHTML, like Gecko)Chrome/89.0.4389.82 Safari/537.36 Content-Type: application/x-www-form-urlencoded Referer: https://redacted Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Cookie: ... query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A

而有经验的攻击者则可以使用自动化扫描工具,将其转换为CSRF的攻击接口:

<html>   <!-- CSRF PoC - generated by Burp Suite Professional -->   <body>   <script>history.pushState('', '', '/')</script>     <form action="https://redacted/graphql" method="POST">       <input type="hidden" name="query" value="{   user {     firstName     __typename   } } " />       <input type="submit" value="Submit request" />     </form>   </body> </html>

弥补GraphQL漏洞的清单

下面,我们将以清单的形式,给出一些与GraphQL安全有关的优秀实践。

防止GraphQL注入攻击

对于那些由LDAP、ORMs/SQL/NoSQL或XML等辅助解析器,来处理输入信息的应用而言,我们建议开发人员做到如下方面:

  • 选择诸如参数化语句等能够提供安全API的库。

  • 根据所选用的解析器的最佳实践,对输入进行转义或编码。

  • 遵循所选模块的文档,以正确的方式使用该工具。毕竟,大多数语言和框架都内置了编码/转义功能,因此了解它们的核心功能,并选择适合的用例是非常重要的。

预防DoS攻击

DoS攻击旨在使得GraphQL API变慢、甚至无法响应正常的请求。为了防御此类攻击,我们应做到:

  • 为传入的GraphQL查询实施深度的规则限制(depth limiting)。

  • 为基础设施和API层添加超时设定。

  • 执行查询的成本分析,以限制代价昂贵的查询。

  • 对每个API客户端的传入请求,实施速率限制。

GraphQL API的访问控制

为了保护对GraphQL API的合理访问,开发人员应该做到:

  • 验证当前用户是否有权根据他们的请求,查看、改变、以及修改数据。

  • 对端点和边缘实施授权控制。

  • 利用基于角色的访问控制(RBAC)中间件,通过查询和变异解析器(mutation solver),来启用访问控制。

  • 在公共的API中禁用自省查询。

  • 禁用GraphiQL之类针对GraphQL模式的探查工具。

通用GraphQL API安全实践

开发人员还可以用来保护GraphQL层的其他方法包括:

  • 对允许的字符使用白名单。

  • 为突变的输入预先定义好对应的GraphQL模式。

  • 使用单一的内部字符编码格式,来正确地处理Unicode输入。

  • 添加分页(pagination),以限制单个请求能够一次性访问到的信息量。

原文标题:Best Practices For GraphQL Security,作者:Sudip Sengupta

【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】