使用浏览器对WebRTC的特性可以在渗透过程中扫描局域网,目前已经有许多利用XHR请求、websockets或存粹的HTML代码来发现和识别局域网设备的例子。WebRTC名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。
使用WebRTC ICE服务进行扫描的技术可以绕过blocked ports list且扫描速度较快。不过只在chrome上才能生效。
代码:https://github.com/jacob-baines/turnscan.js/blob/master/docs/turnscan.js
演示:https://jacob-baines.github.io/turnscan.js/index.html
图1
扫描原理-什么是ICE Server?
扫描技术使用WebRTC ICE服务。ICE服务是WebRTC RTCPeerConnection用于自我发现、NAT遍历和中继的STUN或TURN服务,通过将服务器列表传递到RTCPeerConnection的构造器来实现。下面是一个和谷歌公共STUN服务器有关的构造器之一:
var rtc = new RTCPeerConnection({
iceServers:[{“urls”:”stun:stun.l.google.com:19302”}]
});
当上述RTCPeerConnection进入ICE收集状态时,将尝试连接到所提供的服务器。
流量中的协议显示
ICE服务可以绑定到UDP或TCP端口,
基于UDP通信。
图2
基于TCP通信:
传递给RTCPeerConnection构造器的URL必须符合RFC 7064(STUN)或RFC 7065(TURN)。TURN URI的方案如下:
图3
可以通过“?transport=TCP”强制ICE使用TCP。
判断存活目标主机
以下JSFiddle生成256 TURN URI,以查找192.168.[0–255] .1范围内的活动主机。
JSFiddle运行:https://jsfiddle.net/49n5oLj7/?utm_source=website&utm_medium=embed&utm_campaign=49n5oLj7
源码如下:
var brute_array = [];
for (i = 0; i < 256; i++) {
brute_address = "turn:192.168." + i + ".1:445?transport=tcp";
brute_array.push({
urls: brute_address,
credential: "lobster",
username: "albino"
});
}
var rtc_brute = new RTCPeerConnection({
iceServers: brute_array,
iceCandidatePoolSize: 0
});
rtc_brute.createDataChannel('', {
reliable: false
});
rtc_brute.onicecandidateerror = function(e) {
if (e.url == null) {
return;
}
url_split = e.url.split(":");
host_div = document.createElement('div');
host_div.id = url_split[1];
host_div.innerHTML = url_split[1];
document.getElementById('hosts').appendChild(host_div);
}
// trigger the gathering of ICE candidates
rtc_brute.createOffer(function(offerDesc) {
rtc_brute.setLocalDescription(offerDesc);
}, function(e) {
console.log("Create offer failed callback.");
});
当icecandidateerror事件生成时,这个地址就被确定为“活动的”。如果主机以某种形式拒绝连接,Chrome就会将生成错误事件。理想情况下,在Chrome发送初始信息后,会立刻有RST回复或一个快速拒绝。虽然服务可能只是保持连接打开,但错误事件将需要大约30秒来生成。
指定IP端口扫描
扫描192.168.88.1上的21、22、23、25、53、80、443、445、5900和8080端口。
var ports = [21, 22, 23, 25, 53, 80, 443, 445, 5900, 8080];
var target = "192.168.88.1";
address_div = document.createElement('div');
address_div.id = target;
address_div.innerHTML = target;
document.getElementById("hosts").appendChild(address_div);
var scan_array = [];
for (i = 0; i < ports.length; i++) {
probe_address = "turn:" + target + ":" + ports[i] + "?transport=tcp";
scan_array.push({
urls: probe_address,
credential: "lobster",
username: "albino"
});
port_div = document.createElement('div');
port_div.id = ports[i]
port_div.innerHTML = " -> Port " + ports[i] + " - ?"
document.getElementById(target).appendChild(port_div);
}
var port_scan = new RTCPeerConnection({
iceServers: scan_array,
iceCandidatePoolSize: 0
});
port_scan.createDataChannel('', {
reliable: false
});
port_scan.onicecandidateerror = function(e) {
if (e.url == null) {
return;
}
url_split = e.url.split(":");
port_split = url_split[2].split("?");
if (e.hostCandidate != "0.0.0.x:0") {
document.getElementById(port_split[0]).innerHTML = " -> Port " + port_split[0] + " - <b><i>Open</i><b>"
} else {
document.getElementById(port_split[0]).innerHTML = " -> Port " + port_split[0] + " - Closed"
}
}
setTimeout(function() {
if (port_scan.iceGatheringState === "gathering") {
port_scan.close();
}
}, 60000);
port_scan.onicegatheringstatechange = function(e) {
if (port_scan.iceGatheringState == "complete") {
port_scan.close();
}
}
port_scan.createOffer(function(offerDesc) {
port_scan.setLocalDesc ription(offerDesc);
},
function(e) {
console.log("Create offer failed callback.");
});
扫描结果:
图4 端口扫描结果
基于Chrome生成的icecandidateerror
事件,脚本能够将端口分类为“打开”或“关闭”。每个icecandidateerror
都有一个hostCandidate变量。任何完成TCP三次握手的ICE服务器都会在hostCandidate
中列出本地IP和端口(例如192.168.88.x:51688)。无法访问的ICE服务器以“0.0.0.x:0”的形式生成hostCandidates
。所以判断一个端口是否打开很简单。
图5 扫描192.168.88.0/24上的活动主机的控制台日志