使用瀏覽器對WebRTC的特性可以在滲透過程中掃描局域網,目前已經有許多利用XHR請求、websockets或存粹的HTML代碼來發現和識別局域網設備的例子。WebRTC名稱源自網頁即時通信(英語:Web Real-Time Communication)的縮寫,是一個支持網頁瀏覽器進行實時語音對話或視頻對話的API。
使用WebRTC ICE服務進行掃描的技術可以繞過[blocked ports list](https://chromium.googlesource.com/chromium/src.git/+/refs/heads/master/net/ba se/port_util.cc)且掃描速度較快。不過只在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上的活動主機的控制台日志