[技術解説] PostCSSのPath Traversal脆弱性 (GHSA-r28c-9q8g-f849) とその対策
はじめに
PostCSSは、モダンなフロントエンド開発において欠かせないツールの一つです。CSSの変換、最適化、PostCSSプラグインによる機能追加など、その用途は多岐にわたります。しかし、その強力な機能ゆえに、セキュリティ上の脆弱性が発見された際には、速やかな対応が求められます。
本記事では、PostCSSのバージョン8系に存在するPath Traversal脆弱性「GHSA-r28c-9q8g-f849」(CVEなし、深刻度: High)について、日本のフロントエンドエンジニアの皆様向けに技術的な観点から詳細に解説します。この脆弱性は、悪意のあるCSSが処理されることで、サーバー上の任意の`.map`ファイルの内容が漏洩する可能性があるものです。
脆弱性の概要 (GHSA-r28c-9q8g-f849)
この脆弱性は、PostCSSがCSS内の `/*# sourceMappingURL=... */` コメントを処理する際に発生します。具体的には、「Previous Source Map Auto-Loading」の機能においてPath Traversalの欠陥があり、これにより、攻撃者が作成したCSSを通じて、本来アクセスできないはずのサーバー上の`.map`ファイルの内容が読み取られてしまう可能性があります。
深刻度は「High」と評価されており、機密情報の漏洩につながる危険性があります。
詳細な技術解説
PostCSSは、デフォルトでCSSテキスト内の `/*# sourceMappingURL=... */` コメントを自動的に検出し、これを「以前のソースマップ」としてディスクからロードしようとします。この処理は、`postcss.parse()` や `postcss().process()` が呼び出されるたびに行われ、`map: false` オプションが明示的に渡されない限り実行されます。
脆弱性の核心は、ソースマップのパスを構築する `loadMap()` メソッドにあります。このメソッドは、`join(dirname(opts.from), annotation)` の形式で候補パスを作成します。ここで `annotation` は、CSSコメント内の攻撃者が制御可能な文字列です。Node.jsの `path.join()` は `..` セグメントを正規化しますが、指定されたディレクトリ(`dirname(opts.from)`)内にパスを制約するサンドボックス機能は持っていません。そのため、攻撃者が `../../../` のようなシーケンスを `annotation` に含めることで、意図されたディレクトリ外へのPath Traversalが可能になります。
さらに、もし `opts.from` が全く設定されていない場合、`annotation` は完全に未修正のまま使用されます。この場合、CSSコメント内に絶対パスが記述されていれば、それがそのまま読み込まれてしまいます。
過去にPostCSS 8.5.12では、`.map`以外の任意のファイルが読み取られる可能性があった、より深刻なPath Traversalが修正されました。しかし、その修正は「解決されたパスが`.map`で終わること」を強制するものであり、Path Traversal自体を防ぐものではありませんでした。そのため、現在の最新リリース(8.5.16)においても、ファイルシステム上でアクセス可能な任意の`.map`ファイルであれば、この脆弱性を突いて読み取ることが可能です。
読み込まれたソースマップに `sourcesContent` フィールド(バンドラーやトランスパイラーによって生成されたマップによく含まれる)が存在する場合、そのコンテンツは `result.map` にマージされ、PostCSSの呼び出し元に返されます。これにより、Path Traversalによって読み取られたファイルのコンテンツが、攻撃者によって提供されたCSSを処理した結果として漏洩することになります。
具体的な攻撃シナリオを考えてみましょう。
1. あるWebサービスが、ユーザーから送信されたCSSを受け取り、それをPostCSSで処理します。例えば、リンティング、フォーマット、または変換のために `postcss().process(userCss, { from: '/app/uploads/user123/input.css', to: '/app/uploads/user123/output.css' })` のようなコードが実行されます。この際、PostCSSの `map` オプションはデフォルトのまま(`false`ではない)です。
2. 攻撃者は、以下のような `sourceMappingURL` コメントを含むCSSをサービスに送信します。`/*# sourceMappingURL=../../../../some/other/app/dist/bundle.js.map */` (あるいは、`from` が設定されていない場合は `/etc/passwd.map` のような絶対パス)。
3. PostCSSは、この悪意のあるコメントを検出し、Path Traversalを実行して指定された`.map`ファイルを読み込もうとします。例えば、サーバー上の `bundle.js.map` ファイルが読み込まれます。
4. 読み込まれた `.map` ファイルに `sourcesContent` が含まれている場合、その内容が `result.map` にマージされます。ほとんどのビルドパイプラインでは、生成されたソースマップはCSS出力の隣に書き出されるか、API経由で返されます(ソースマップはブラウザの開発者ツールで利用されることを想定しているため、通常は公開されたり、容易に取得できる場所に配置されたりします)。
5. 攻撃者は、出力された `result.map` を取得し、その中に含まれる `sourcesContent` から、Path Traversalによって読み取られた `bundle.js.map` の内容を抽出します。これにより、機密性の高いファイル(例えば、アプリケーションのソースコードや設定情報など、`.map`ファイルに含まれる可能性のある情報)が漏洩することになります。
この攻撃は、ユーザーがCSSテキストを送信する以外の認証やユーザー操作を必要としません。
この脆弱性の影響は、PostCSSを介してユーザーが完全に信頼できないCSSを処理するすべてのアプリケーションに及びます。`map: false` オプションを明示的に指定していない場合、攻撃者はPath Traversal(または `from` が未設定の場合は絶対パス)を利用して、プロセスがアクセス可能なファイルシステム上の任意の`.map`ファイルの内容を開示できます。
特に、CI/CDパイプライン、オンラインCSSエディタ、テーマエディタなど、ユーザーからのCSS入力を直接PostCSSで処理するサービスは大きなリスクにさらされます。
あなたのプロジェクトへの影響と確認方法
あなたのプロジェクトがこの脆弱性の影響を受けるかどうかを確認するには、以下の点をチェックしてください。
1. **PostCSSの使用有無**: プロジェクトでPostCSS(`postcss`パッケージ)を使用していますか? `package.json`の依存関係を確認してください。
2. **PostCSSのバージョン**: PostCSSのバージョンが8.x系(特に8.0.0から8.5.16まで)の場合、この脆弱性の影響を受ける可能性があります。この脆弱性は、PostCSS `8.4.31` で修正されています。
3. **ユーザー入力CSSの処理**: ユーザーが直接CSSテキストを送信し、それをPostCSSで処理するような機能(例:カスタムCSSの適用、CSSのプレビューなど)がありますか?
4. **`map: false` オプションの明示的な設定**: PostCSSの `process()` メソッドや `parse()` メソッドを呼び出す際に、`{ map: false }` オプションを明示的に設定していますか?もし設定していない場合、デフォルトの自動ロード機能が有効になっています。
上記の1〜3に該当し、かつ4に該当しない場合、あなたのアプリケーションは脆弱である可能性があります。
対策と修正
この脆弱性に対する最も確実な対策は、修正済みのPostCSSバージョンへの更新です。
**推奨される修正**: PostCSSの最新バージョン(脆弱性が修正された**8.4.31以降**)にアップデートしてください。
yarnを使用している場合: `yarn upgrade postcss`
npmを使用している場合: `npm update postcss`
パッチ適用済みのバージョンでは、ソースマップのパス解決において、`cssFile` のディレクトリ外へのアクセスを厳密に制限するロジックが追加されています。具体的には、解決されたパスがCSSファイル自身のディレクトリ内にとどまるように `path.resolve()` と `String.prototype.startsWith()` を用いて検証されます。
**一時的な回避策**: もしすぐにPostCSSをアップデートできない場合、緊急の回避策として、PostCSSの処理で `map: false` オプションを明示的に設定してください。
```javascript postcss().process(userCss, { map: false }) ```
このオプションを設定することで、PostCSSが `sourceMappingURL` コメントを解析し、外部のソースマップを自動的にロードする動作を無効にできます。
**一般的なセキュリティプラクティス**: ユーザーからの入力を処理する際は、常にセキュリティ上の注意を払うことが重要です。Path Traversalのような脆弱性を防ぐため、パス操作を行う際には、意図しないディレクトリ外へのアクセスを防ぐための厳格な検証ロジックを実装することを検討してください。Node.jsの `path` モジュールだけでは不十分な場合があることを認識し、`path.resolve()` と `path.join()` の挙動を理解した上で、適切なバリデーションを組み合わせる必要があります。
まとめ
PostCSSのPath Traversal脆弱性 (GHSA-r28c-9q8g-f849) は、ユーザーが制御可能なCSSから任意の`.map`ファイルの内容が漏洩する可能性がある、深刻度の高い脆弱性です。特に、ユーザー入力のCSSを処理するアプリケーションは、速やかな対応が求められます。
影響を受けるPostCSSのバージョンを使用している場合は、直ちに最新バージョン(8.4.31以降)へのアップデートを実施するか、`{ map: false }` オプションを適用してください。常にソフトウェアを最新の状態に保ち、セキュリティ情報を定期的に確認することは、安全なアプリケーション開発の基本です。
この記事が、日本のフロントエンドエンジニアの皆様のセキュリティ対策の一助となれば幸いです。