Node.jsとSSRF

2.6K Views

February 28, 23

スライド概要

Node学園 41時限目 LT
Node.js上でSSRF対策を実装する例

シェア

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Node.jsとSSRF はせがわようすけ @hasegawayosuke

2.

t #tng41

3.

https://amazon.jp/dp/B0BQM1KMBG/ t #tng41

4.

SSRF - Server Side Request Forgery  サーバーから他のサーバーへリクエストを発行するときに、リクエ スト先を攻撃者が指定することができる脆弱性  内部ネットワーク上のサーバーへ間接的にアクセス可能になる 正 規 の 流 れ 攻 撃 の 流 れ http://example.jp/post? text = おもしろいニュース見つけたよ! & url=http://example.com/news 利用者 おもしろいニュース見つけたよ! http://example.com/news GET http://example.com/news example.comニュース 今日のニュースは○△□です。 example.jp example.com example.comニュース 今日のニュースは○△□です。 https://example.jp/post?url= https://intra.example.jp/news 攻撃者 [社外秘] 組織変更のお知らせ GET https://intra.example.jp/news example.jp [社外秘] 組織変更のお知らせ 内部ネットワーク上のホスト intra.example.jp t #tng41

5.

SSRF - Server Side Request Forgery  SSRF概要  サーバーから攻撃者が指定した他のサーバー/サービスへリクエストが飛ぶ攻撃  SSRFの脅威  機密情報の漏えい、他のプロトコルへの攻撃、任意 コード実行など  近隣でどんなサービスが動いているかに依る 詳細は「SSRF基礎」参照 https://www.docswell.com/s/hasegawa/VZGWQK-SSRF  対策  接続可能なURLをアプリケーション内で事前に定義 しておく  iptables等でOS/コンテナレベルで接続先を制限 する  URLの検証は保険的な対策にしかならない(やらない よりはマシ) 特にサーバー上の通信クライアントがcurl等のときはgopherのよ うな柔軟性の高いプロトコルが利用できてしまい危険性が上がる t #tng41

6.

SSRFの対策は難しい  ロジック自体の抜け漏れ:例  IPアドレス表記の多様性 → http://169.254.169.254/とかhttp://2852039166/とか http://0xa9fea9fe/とか  DNSを使っての回避 169.254.169.254に名前を振る、短いTTLを充てる等  接続禁止先の抜け漏れ  内部ネットワークの構成変更等 今日は こっちだけ考える t #tng41

7.

JSによる接続先の制限  接続先のIPアドレスやポートをNode上のコードで制限すること はできるか  こういう感じのコードを実装したい if (接続先IPが内部アドレス or 接続先ポート番号が禁止ポート) { 接続をブロック } else { fetch(...) }  fetchは細かな制御ができないので使えない → 標準のhttp.request で頑張る t #tng41

8.

使えそうなもの: net.BlockList  接続禁止の仕組みになんかよさげなのが! https://nodejs.org/api/net.html#class-netblocklist  リストを管理、照合するだけ。各APIに適用する方法はない! const list = new net.BlockList() list.addAddress('169.254.169.254') list.check(ipAddress) // true or false  照合結果を用いて、接続可否を自分で制御する必要がある t #tng41

9.

使えそうなもの: http.requestのlookupオプション  http.requestはDNSの名前解決をカスタマイズ可能 … snip … … snip … https://nodejs.org/api/http.html#httprequestoptions-callback t #tng41

10.

実際に実装してみる bit.ly/tng41-ssrf t #tng41

11.
[beta]
const badAddresses = ['169.254.169.254']
const blockList = new net.BlockList()
badAddresses.forEach(address => {
blockList.addAddress(address)
})
Object.values(os.networkInterfaces()).forEach(nic => {
nic.forEach(o => {
const [network, prefix] = o.cidr.split(/¥//)
blockList.addSubnet(network, prefix | 0, o.family)
})
})

接続を禁止するIPアドレスの
リストを事前定義し
BlockListに登録する
ネットワークインタフェースを列
挙し、そのネットワークアドレス
を全て登録BlockListに登録
する

const _lookup = (hostname, options, callback) => {
const _callback = (err, address, family) => {
if (!err && blockList.check(address, `ipv${family}`)) {
throw new Error(`Invalid address: ${address}`)
}
callback.apply(this, [err, address, family])
}
dns.lookup.apply(this, [hostname, options, _callback])
}

DNSの名前解決でデフォ
ルトのlookupの前に
BlockListと照合し、合
致すれば例外

bit.ly/tng41-ssrf

12.
[beta]
const
const
const
const

objUrl = new URL(url)
isHttps = objUrl.protocol === 'https:'
port = objUrl.port ? objUrl.port | 0 : isHttps ? 443 : 80
badPorts = [0, 1, 7, 9, 11, ...]

接続禁止ポートはfetch standard参照

if (badPorts.includes(port)) {
接続先のポートが禁止リストに
throw new Error(`Invalid port: ${port}`)
合致すれば例外
}
if (net.isIP(objUrl.hostname)) {
ホスト名がIPアドレスのときに
if (blockList.check(objUrl.hostname)) {
禁止リストと合致するか検証
throw new Error(`Invalid address: ${objUrl.hostname}`)
(名前解決が走らないため)
}
}
const options = Object.create(null)
options.method = 'GET'
options.lookup = _lookup
lookupをカスタマイズしてrequest発行
const req = (isHttps ? https : http).request(url, options, (res) => {
...
})

bit.ly/tng41-ssrf

13.

まとめ  アプリケーションレイヤーで接続先を制限するのはめちゃくちゃめ んどくさい  先の実装でも何か抜け道ありそう  fetch使えない、サードパーティのHTTPクライアントもたぶん使えない ➡ アプリケーションレイヤーだけでSSRF対策するのはかなり厳し い  ネットワークレイヤーで制限するほうがたぶん簡単  多層防御という点で自分で実装してもいいけど、複雑化して別の問題が 発生しそう  いい対策方法あれば教えて欲しい t #tng41

14.

質問? ✉ t yosuke.hasegawa@gmail.com @hasegawayosuke t #tng41