DNS Rebinding攻撃は、Same-Origin Policy(SOP)をバイパスしてブラウザを踏み台にし、被害者のlocalhost上で動くAPIサーバーへ外部から任意リクエストを送り込む攻撃手法である。ローカルで動く管理ダッシュボード、開発サーバー、IoT機器の設定UIなどが標的になりうる。本記事では仕組み、攻撃シナリオ、防御策をExpressの実装コードとともに解説する。
攻撃の仕組み: TTL 0とDNSレコード差し替え
SOPはオリジン(スキーム+ホスト名+ポート)が同一の場合のみクロスオリジンアクセスを許可する。DNSリバインディングはこの「ホスト名」の解釈を悪用する。
- 攻撃者が制御する
evil.attacker.comのAレコードのTTLを0秒にしておく - 被害者が
http://evil.attacker.com/payload.htmlにアクセスする - ブラウザがDNSキャッシュを破棄した直後、攻撃者のDNSサーバーは
evil.attacker.comのAレコードを127.0.0.1に差し替える - payload.html内のJavaScriptが
http://evil.attacker.com:8080/adminにfetch()する - ブラウザは
evil.attacker.comと同一オリジンと判断してSOP制限をかけず、実際のリクエストはlocalhost:8080に届く
[攻撃者DNS]
evil.attacker.com TTL=0 → (最初) 1.2.3.4 → (切替後) 127.0.0.1
↓ ↓
malicious page Admin API bypass
攻撃シナリオ
個人開発でよくある構成を例に取る。
-
localhost:8080でExpress製のAdmin APIが動いている - APIはAuthorizationヘッダを要求しない(ローカルからしか叩けないと思っている)
- 開発者がブラウザで
evil.attacker.comの罠ページを踏む
この状況でDNSリバインディングが成立すると、攻撃者はAdmin APIの全エンドポイントに自由にアクセスできる。機密データの取得、設定の書き換え、シェルコマンドの実行インターフェースがある場合はRCEまで至る。
防御策1: Host headerの検証(Express実装)
サーバー側でリクエストの Host ヘッダを検証することで、DNSリバインディングを無効化できる。ブラウザはリクエスト先のホスト名をHostヘッダにセットするため、差し替え後のDNSで 127.0.0.1 に到達しても evil.attacker.com というHostヘッダが残る。
const express = require('express');
const app = express();
const ALLOWED_HOSTS = new Set([
'localhost',
'127.0.0.1',
'[::1]',
]);
function validateHost(req, res, next) {
const host = (req.headers['host'] || '').split(':')[0];
if (!ALLOWED_HOSTS.has(host)) {
res.status(403).json({
error: `Forbidden host: ${host}`,
});
return;
}
next();
}
app.use(validateHost);
app.get('/admin', (req, res) => {
res.json({ status: 'ok', data: 'sensitive' });
});
app.listen(8080, '127.0.0.1', () => {
console.log('Listening on 127.0.0.1:8080');
});
ALLOWED_HOSTS に開発用の固定ホスト名だけを列挙する。動的なサブドメインは入れてはならない。
防御策2: バインドアドレスを127.0.0.1に限定
Node.jsのhttp.createServer()は、デフォルトで 0.0.0.0(全インターフェース)にバインドする場合がある。開発サーバーは明示的に 127.0.0.1 にバインドすることで、外部ネットワークからのパケットは届かなくなる。
// NG: 全インターフェースにバインド
app.listen(8080);
// OK: loopbackのみ
app.listen(8080, '127.0.0.1');
ただしDNSリバインディングはブラウザを経由するためloopback経由で届く。バインド制限だけでは防げない点に注意が必要である。
防御策3: DNSリバインディング対策ルーターの設定
多くのブロードバンドルーターには「DNSリバインディング保護」設定がある。ローカルサブネットのIPアドレスを返す外部DNSクエリをドロップする機能で、企業ネットワークや自宅環境では有効化を確認する。
Linuxのdnsmasqベースのルーターではデフォルトで有効になっていることが多い。
# dnsmasqのDNSリバインディング保護確認
grep -r "stop-dns-rebind" /etc/dnsmasq.conf /etc/dnsmasq.d/ 2>/dev/null
# なければ追加
echo "stop-dns-rebind" >> /etc/dnsmasq.conf
防御策4: プライベートネットワークアクセス制限(Chrome)
Chrome 98以降、Private Network Access(PNA)制限が段階的に強化されている。パブリックなWebページからプライベートIPへのfetchにはプリフライトが必要になり、サーバー側が Access-Control-Allow-Private-Network: true を返さない限りブロックされる。
開発サーバー側でこのヘッダを返さないようにすることが、追加の防御になる。
// プライベートネットワークアクセスを意図的に拒否するミドルウェア
app.use((req, res, next) => {
if (req.headers['access-control-request-private-network']) {
res.status(403).end();
return;
}
next();
});
チェックリスト
- サーバーの
Hostヘッダを許可リストで検証しているか - 開発サーバーのバインドアドレスが
127.0.0.1になっているか - Admin系エンドポイントに認証(Bearer token等)がかかっているか
- ルーターのDNSリバインディング保護が有効か
- ローカルAPIにCSRFトークンを要求しているか
まとめ
DNS Rebindingは「ローカルからしか見えないから安全」という思い込みを崩す攻撃である。Host headerの検証と認証の追加は数行で実装できる。開発段階からこの2つを入れておくだけで、ほとんどのDNSリバインディングシナリオは無効化できる。