在ApolloServer中实施漏洞修复
首先
在本篇中,我们将对GraphQL API的服务器端实现中最常用的Apollo Server进行两个脆弱性防范措施的实施。
查询的复杂度和深度设置(用GraphQL来防止漏洞)
在Apollo Server的初始状态下,没有对查询的复杂性和深度进行限制的配置。
-
- 攻撃者から複雑なクエリを実行される
- 攻撃者から深い階層のクエリを実行される
还存在风险。
在这种状态下,存在着可能会浪费服务器资源并给服务器施加负载的恶意攻击者发起查询的风险。
我将对这两种攻击和应对措施进行解释。
执行复杂查询的风险和对策
进攻形象
在GraphQL中,您可以通过以下查询轻松地获取大量数据。
其中的”first”参数类似于SQL中的”limit”,用于指定要获取的数据数量。
如果数据存在,则此查询就会导致计算量为100 × 1000 x 1000 = 100000000。
{
companies(first: 100) {
name
groups(first: 1000) {
name
users(first: 1000) {
name
}
}
}
}
根据复杂性设定进行对策
为了防止类似恶意查询的执行,可以使用名为graphql-validation-complexity的库来计算查询的复杂度并随之增加的数值(=复杂度),只有当该数值符合阈值时才执行。
根据构建的GraphQL用例,可以设定复杂度上限即可。
您可以参考GitHub GraphQL API的参考文档。
https://docs.github.com/zh/graphql/overview/resource-limitations
validationRules: [
createComplexityLimitRule(1000000)
],
执行复杂层次的查询所面临的风险和对策
攻击图像
GraphQL中存在循环引用的反模式。
当GraphQL互相绑定时会发生,导致多层数据关联,增加服务器负载。
如果具有以下类似的查询,Recipe和Ingredient之间存在循环关系,执行成本会非常高。
恶意用户可能会向API发送这种嵌套查询,从而可能导致服务器崩溃。
query {
getRecipes {
recipes {
ingredients {
recipes {
ingredients {
recipes {
ingredients {
# ... and so on
}
}
}
}
}
}
}
}
深度限制设定的措施
可以通过限制查询的深度来作为上述措施。引入名为 graphql-depth-limit 的库,并根据构建的GraphQL用例设置复杂性上限即可。
validationRules: [
depthLimit(10)
],
设置响应头(用于防御来自Web服务器的弱点)
在Apollo Server的初始状态下,存在来自Web服务器的响应头漏洞。
-
- 情報がキャッシュサーバやプロキシサーバのキャッシュに保存される
-
- コンテンツの内容からファイルの種類を推測できる
-
- iframeなどを呼び出せる
- httpでアクセスできてしまう
目前的状态是存在这样的情况,单独来看不具备脆弱性,但存在可能被攻击者利用的风险。
关于这个来自Web服务器的漏洞的内容和对策,我会在下面简洁总结一下。
缓存设置错误
用户的重要信息可能会保存在缓存服务器或代理服务器的缓存中,其他用户可能会调用这些信息。
由此,用户的个人信息等可能被其他用户浏览到。
只需将以下内容添加到GraphQLResponse响应头中,即可进行防护:
Cache-Control:私有,不存储,不缓存,必须重新验证 Pragma:不缓存 Expires:-1
X-Content-Type-Options存在问题。
在特定的浏览器中,有一种功能可以根据内容推测文件类型并执行。
例如,如果文件扩展名是jpg,但内容实际上是JavaScript,则会执行JavaScript等。
由此,可能存在误识别图像上传器中上传的文件,并执行JavaScript的漏洞。
将以下内容添加到GraphQLResponse响应头即可实施防范措施。
X-Content-Type-Options: nosniff
防范”点击劫持”的不足。
目前尚未針對点击劫持(使用iframe載入其他網站並引導用户点击特定位置,以此攻擊方式更改其他網站的設定信息等)進行對策。
只需将下列内容添加到GraphQLResponse响应头中即可采取对策。
X-Frame-Options DENY 或 X-Frame-Options SAMEORIGIN。
严格传输安全性存在问题。
Strict-Transport-Security头是一个功能,可以强制Web浏览器使用https而不是http。
通过设置这个头,可以强制在使用http时转为https。
只需将以下内容添加到GraphQLResponse响应头中,即可进行防护:
Strict-Transport-Security: max-age=15724800; includeSubdomains
实施脆弱性的措施
实装样本
我会新增两个包裹。
npm install graphql-depth-limit // 深い階層のクエリ対策
npm install graphql-validation-complexity // 複雑なクエリ対策
下面是实现示例。
分别使用validationRules和formatResponse。
validationRulesにてクエリの複雑さと深さの設定(GraphQL由来の脆弱性対策)
formatResponseにてGraphQLResponseレスポンスヘッダの設定(Webサーバー由来の脆弱性対策)
正在进行。
const server = new ApolloServer({
validationRules: [
depthLimit(10),
createComplexityLimitRule(10000)
],
formatResponse: (response: GraphQLResponse | null, requestContext: GraphQLRequestContext<any>) => {
if (requestContext.response && requestContext.response.http) {
requestContext.response.http.headers.set('Cache-Control', "private, no-store, no-cache, must-revalidate");
requestContext.response.http.headers.set('Pragma', "no-cache");
requestContext.response.http.headers.set('Expires', "-1");
requestContext.response.http.headers.set('X-Content-Type-Options', "nosniff");
requestContext.response.http.headers.set('X-Frame-Options', "DENY");
requestContext.response.http.headers.set('Strict-Transport-Security', "max-age=15724800; includeSubdomains");
}
return response as GraphQLResponse;
},
});
server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
console.log(`? Server ready at ${url}`);
});
响应头的对策结果
Response Headers
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
// ここから
cache-control: private, no-store, no-cache, must-revalidate
expires: -1
pragma: no-cache
strict-transport-security: max-age=15724800; includeSubdomains
x-content-type-options: nosniff
x-frame-options: DENY
// ここまで
Content-Length: 217
ETag: W/"d9-CbCcSw/ttAs+CI2lNN5md0H9inw"
Date: Thu, 02 Sep 2021 08:03:40 GMT
Connection: keep-alive
Keep-Alive: timeout=5
确认查询的复杂度和深度设置。
可以很容易地使用 Apollo Studio 进行确认。
https://www.apollographql.com/docs/studio/
最后
以上是为了保护Apollo Server安全而采取的两个关键观点和实施措施。
在我所属的Quand株式会社目前正在招聘工程师。
我们的使命是升级地方产业,开发应用程序来解决制造业和建筑业等“现场”问题!
我也在全弹性和全远程的工作方式下工作。
有兴趣的人请查看入职手册!