原文:https://portswigger.net/blog/exposing-intranets-with-reliable-browser-based-port-scanning

0x00 概述

在本文中我将介绍如何使用JavaScript构造端口扫描器,如果大家感兴趣,可以直接从此处下载这款工具。

0x01 基于Chrome浏览器的实现

关于从Internet(互联网)区域到Intranet(内部网)区域进行端口扫描方面的内容,网上已经有一些研究文章。Jeremiah Grossman在之前介绍过如何在不依赖JavaScript的前提下,使用link元素和时序方法进行端口扫描,而Berend Jan Wever(Skylined)写了一个lan扫描器,也使用了时序攻击(timing attacks)以及WebRTC和XHR原理。 这两种技术都用到了时序攻击,因此不是100%可靠。 这里我已经提出了一种更为可靠的技术,但主要面向的是网络服务器扫描场景。

在测试在受限环境中使用浏览器来呈现用户提供的内容的Web应用程序时,我一直在寻找能够提取特定IP上运行的服务信息的方法。之所以选择Chrome,是因为这个Web应用程序使用的正是这个浏览器。当某个端口没有被用户主机占用时,Chrome会拒绝连接,此时我注意到了一些有趣的行为。 Chrome会向用户显示一条消息,但这正是最为有趣的一点,Chrome会将实际的URL更改为chrome-error://chromewebdata/

当我们使用iframe向服务器上不存在的某个端口发送请求时,即使该端口没有监听任何内容,我们也会得到成功的onload事件。如果的确有服务器在该端口监听,浏览器也会有一个成功的onload事件,Chrome可能的确会这样做,以防止用户探测哪些端口处于打开状态。 我们可以利用这种行为,我想出了一种方法,可以使用iframe onload事件来确定端口是否打开。

如果我们首次加载url,捕获到onload事件,然后增加计数器值,再次发出相同的请求,但这次请求源中加了#(因为网址已更改为chrome-error:,而不是原始url),此时我们将获得第二次onload事件,因为网址已更改。 如果某个Web服务器正在目标地址上监听,我们只会得到一个onload事件,这是因为第二个url中包含一个哈希值,而当一个哈希发送到已加载的某个页面时,浏览器不会重新加载页面。

为了构造端口扫描程序,我首先创建了一个iframe元素和anchor元素。anchor元素用来执行对#url的点击行为。然后,我们需要将iframe的名称及anchor目标设置为相同的值,以便在点击操作会在iframe上而非顶层文档上执行:

iframe.name = a.target = 'probe'+Date.now();

然后我们需要设置iframe的url值以及anchorhref属性值设置为同一个目标:

iframe.src = url + ":" + pos;
a.href = iframe.src + '#';

iframe需要关联onload事件,有效端口只会触发一次onload事件,因此我们需要使用计时器,碰到无效端口时继续下次测试:

iframe.onload = function(){        
 calls++;        
 if(calls > 1) {
   clearTimeout(timer);
   next();
   return;
 }
 a.click();
};
timer = setTimeout(function(){
   validPorts.push(pos);
   next();
}, 5000);

以上就是主要思路,我们可以使用这种方法来扫描任意主机(包括本地IP)上的Web服务器。

请注意 :在最新版的Chrome上使用X-Frame-Options: DENY选项时会修改url,所以该工具会将这种情况判断为端口关闭。

0x02 基于Firefox浏览器的实现

我研究了如何在Firefox上使用这种技术,事实证明此时这种技术运用起来更加容易。如果是有效的Web服务器,那么Firefox会触发onload事件,并且不会因为拒绝连接而触发该事件,因此我们无需自动点击链接就可以轻松找到目标。我们只需查看onload事件是否被触发,或者检测是否超时即可(此时没有触发该事件)。Firefox还可以让我们创建大量iframe,并且不会造成性能上的损失。

这次我并没有使用单个iframe(如Chrome浏览器的应用场景),而是使用了iframe池。Firefox允许我们使用大量的iframe,这里我选择的数量为1000。

var id = 'iframe'+(pos%1000), 
iframe = document.getElementById(id) ? document.getElementById(id) : document.createElement('iframe'), timer;

然后只需要使用onload事件就可以简单判断目标是否为有效的Web服务器:

iframe.onload = function(){
   validPorts.push(pos);
   clearTimeout(timer);
   next();
};

Firefox场景比其他场景要更加快速,也更为强大,因为此时我们甚至可以扫描无效的响应场景。这样我们就能检测其他服务,比如Redis服务器等。

0x03 基于Edge浏览器的实现

Edge浏览器版的扫描器与Chrome版本相反,如果目标端口有效,那么url就会跳转到错误页面,触发onload事件;但如果目标端口无效,那么只有哈希值会发生改变,不会触发onload事件。

iframe.onload = function(){
  calls++;
  if(calls > 1) {
     validPorts.push(currentPos);
     return;
  }
  var a = document.createElement('a');
  a.href = 'ms-appx-web://microsoft.microsoftedge/assets/errorpages/dnserror.html#123';
a.target = iframe.name;
        a.click();
        a = null;
        if(calls === 1) {
          next();
        }
    };

我已经将以上技术整合到一个工具中,这款工具使用我最喜欢的异步JavaScript语言进行开发,大家可以访问此处下载该工具。

源链接

Hacking more

...