這篇主要介紹一些 Strawberry 內建提供的安全性相關的擴充功能,下面會示範幾個跟查詢有關的安全性擴充功能設定。
在 OWASP Cheat Sheet [1] 上有介紹很多 GraphQL 安全性建議,這邊就介紹一些阻斷服務(DoS)攻擊有關的事項,在 GraphQL 基本上是利用客戶端可以定義查詢的特性,以製造複雜的查詢或產生大量查詢請求的方式消耗服務的資源,來達到阻斷服務攻擊的目的。
透過像是下面這樣型態間互相關聯,並且 GraphQL 並沒有使用快取機制,不斷地循環向下深度巢狀格式的查詢操作,來製造大量的查詢消耗資源。
query MyQuery {
posts {
edges {
node {
author {
id
username
posts {
title
author {
posts {
author {
posts {
author {
posts {
author {
username
}
}
}
}
}
}
}
}
}
}
}
}
}
上面的查詢產生了 48 次 SQL 查詢,是將DjangoOptimizerExtension
移除後的結果,如果有其他自定義查詢的欄位將會產生更多 SQL 查詢。
這個情況需要使用限制查詢深度來阻止這樣的操作,Strawberry 提供QueryDepthLimiter
讓我們使用:
# server/schema.py
# ... 省略
from graphql_sync_dataloaders import DeferredExecutionContext
+from strawberry.extensions import QueryDepthLimiter
from strawberry_django.optimizer import DjangoOptimizerExtension
# ... 省略
schema = strawberry.Schema(
query=query,
mutation=mutation,
execution_context_class=DeferredExecutionContext,
extensions=[
DjangoOptimizerExtension(),
+ QueryDepthLimiter(max_depth=5),
],
)
# ... 省略
這邊假設我們設定查詢深度只能 5 層,下面看看剛剛那段查詢操作被阻止的訊息:
再看看正常的查詢深度是怎麼計算的:
GraphQL 客戶端可以定義查詢操作與資料格式,所以伺服器端每次都需要去解析查詢操作後,去執行查詢,可想而知,如果查詢很複雜,不只要花費更多計算資源去解析查詢,也需要更多資源去執行查詢。
這時候我們需要使用一些快取機制,像是快取查詢操作解析的結果,或是像前一篇查詢優化所做的那些方法,來減少計算與資料查詢。
假設有個型態有許多欄位跟關聯欄位,攻擊者透過不斷變換定義查詢操作的格式,而且格式都是相當複雜的,透過一次又一次的密集發送請求,讓我們的快取機制失效,達到阻斷服務攻擊。
避免這樣的攻擊,就是限制客戶端定義複雜的查詢格式,其最簡單的作法是限制字詞(Token)的數量:
# server/schema.py
# ... 省略
from graphql_sync_dataloaders import DeferredExecutionContext
-from strawberry.extensions import QueryDepthLimiter
+from strawberry.extensions import MaxTokensLimiter, QueryDepthLimiter
from strawberry_django.optimizer import DjangoOptimizerExtension
# ... 省略
schema = strawberry.Schema(
query=query,
mutation=mutation,
execution_context_class=DeferredExecutionContext,
extensions=[
DjangoOptimizerExtension(),
QueryDepthLimiter(max_depth=10), # 從 5 改成 10
+ MaxTokensLimiter(max_token_count=50),
],
)
posts
, {
, edges
, {
, node
, author
and }
等等這些都算字詞數量。
最大字詞數量通常設在 800 ~ 2000 字詞 [2]。
GraphQL 可以在同一個查詢操作內同時呼叫許多的查詢,攻擊者可能使用建立許多別名的查詢,製造批次攻擊。
下面使用限制別名數量的擴充功能:
# server/schema.py
# ... 省略
from graphql_sync_dataloaders import DeferredExecutionContext
-from strawberry.extensions import MaxTokensLimiter, QueryDepthLimiter
+from strawberry.extensions import MaxAliasesLimiter, MaxTokensLimiter, QueryDepthLimiter
from strawberry_django.optimizer import DjangoOptimizerExtension
# ... 省略
schema = strawberry.Schema(
query=query,
mutation=mutation,
execution_context_class=DeferredExecutionContext,
extensions=[
DjangoOptimizerExtension(),
QueryDepthLimiter(max_depth=10),
MaxTokensLimiter(max_token_count=1000), # 從 50 改成 1000
+ MaxAliasesLimiter(max_alias_count=5),
],
)
Strawberry 還有內建其他跟安全性防護的擴充功能,可以參考官方文件中的擴充功能列表 [4],上面標示security
的標籤。
這次修改內容可以參考 Git commit: