原文:https://portswigger.net/blog/exposing-intranets-with-reliable-browser-based-port-scanning
在本文中我将介绍如何使用JavaScript构造端口扫描器,如果大家感兴趣,可以直接从此处下载这款工具。
关于从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值以及anchor
的href
属性值设置为同一个目标:
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,所以该工具会将这种情况判断为端口关闭。
我研究了如何在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服务器等。
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语言进行开发,大家可以访问此处下载该工具。