iT邦幫忙

2023 iThome 鐵人賽

DAY 27
0

這篇主要介紹一些 Strawberry 內建提供的安全性相關的擴充功能,下面會示範幾個跟查詢有關的安全性擴充功能設定。

在 OWASP Cheat Sheet [1] 上有介紹很多 GraphQL 安全性建議,這邊就介紹一些阻斷服務(DoS)攻擊有關的事項,在 GraphQL 基本上是利用客戶端可以定義查詢的特性,以製造複雜的查詢或產生大量查詢請求的方式消耗服務的資源,來達到阻斷服務攻擊的目的。

深度巢狀查詢攻擊:

透過像是下面這樣型態間互相關聯,並且 GraphQL 並沒有使用快取機制,不斷地循環向下深度巢狀格式的查詢操作,來製造大量的查詢消耗資源。

https://ithelp.ithome.com.tw/upload/images/20231012/20161957BBPwA0K2r2.png

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 層,下面看看剛剛那段查詢操作被阻止的訊息:

https://ithelp.ithome.com.tw/upload/images/20231012/20161957hU6mHN8udE.png

再看看正常的查詢深度是怎麼計算的:

https://ithelp.ithome.com.tw/upload/images/20231012/20161957njpyq4Jgju.png

複雜查詢攻擊

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),
    ],
)

https://ithelp.ithome.com.tw/upload/images/20231012/201619570agDFkdeRz.png

posts, {, edges, {, node, authorand }等等這些都算字詞數量。

最大字詞數量通常設在 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),
    ],
)

https://ithelp.ithome.com.tw/upload/images/20231012/20161957h2nkbWDlI4.png

其他預防阻斷攻擊的方法

  • 限制查詢回傳資料數量,像是使用基於位移的分頁,可以設定回傳的筆數,如果沒有在伺服器端限制最大可設定回傳筆數,可能導致查詢超大量資料。
  • 設定合理的逾時時間(Timeout),限制任何單一請求可以消耗的資源數量的簡單方法。
  • 查詢成本分析(Query Cost Analysis),可以參考 Github GraphQL API 的 Node limit [3]。
  • 速率限制(Rate Limit),限制存取服務或是查詢操作的頻率。
  • 快取(Cache),查詢操作解析的快取或是使用 DataLoader 快取資料。

Strawberry 還有內建其他跟安全性防護的擴充功能,可以參考官方文件中的擴充功能列表 [4],上面標示security的標籤。

這次修改內容可以參考 Git commit:

參考資料


上一篇
Day 26:Strawberry Django 查詢優化
下一篇
Day 28:整合追蹤服務
系列文
Django 與 Strawberry GraphQL:探索現代 API 開發之路30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言