详解 CSRF 与 XSS 攻击

之前的一篇文章中,提到了 WordPress 的插件审核机制,其中对于代码安全性的要求,主要就是对 CSRF 与 XSS 攻击的防范。官方邮件中这样写道:

Not using Nonces and/or checking permissions

Please add a nonce to your POST calls to prevent unauthorized access.
Keep in mind, check_admin_referer alone is NOT bulletproof security. Do not rely on nonces for authorization purposes. Use current_user_can() in order to prevent users without the right permissions from accessing things.

Please sanitize, escape, and validate your POST calls

When you include POST/GET/REQUEST/FILE calls in your plugin, it's important to sanitize, validate, and escape them. The goal here is to prevent a user from accidentally sending trash data through the system, as well as protecting them from potential security issues.
SANITIZE: Data that is input (either by a user or automatically) must be sanitized. This lessens the possibility of XSS vulnerabilities and MITM attacks where posted data is subverted.
VALIDATE: All data should be validated as much as possible. Even when you sanitize, remember that you don't want someone putting in 'dog' when the only valid values are numbers.
ESCAPE: Data that is output must be escaped properly, so it can't hijack admin screens. There are many esc_*() functions you can use to make sure you don't show people the wrong data.
To help you with this, WordPress comes with a number of sanitization and escaping functions. You can read about those here: https://developer.wordpress.org/plugins/security/securing-input/
Remember: You must use the MOST appropriate functions for the context. If you're sanitizing email, use sanitize_email(), if you're outputting HTML, use esc_html(), and so on.
Clean everything, check everything, escape everything, and never trust the users to always have input sane data.

下面通过维基百科的内容介绍 CSRF 与 XSS 攻击的具体形式。

CSRF

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF,是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

攻击的细节

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

例子

假如一家银行用以执行转账操作的 URL 地址为 http://www.examplebank.com/withdraw?account=AccoutName&amount=1000&for=PayeeName,那么,一个恶意攻击者可以在另一个网站上放置如下代码:

1
<img src="http://www.examplebank.com/withdraw?account=Alice&amount=1000&for=Badman">

如果有账户名为 Alice 的用户访问了恶意站点,而她之前刚访问过银行不久,登录信息尚未过期,那么她就会损失 1000 资金。
这种恶意的网址可以有很多种形式,藏身于网页中的许多地方。此外,攻击者也不需要控制放置恶意网址的网站。例如他可以将这种地址藏在论坛,博客等任何用户生成内容的网站中。这意味着如果服务器端没有合适的防御措施的话,用户即使访问熟悉的可信网站也有受攻击的危险
透过例子能够看出,攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是欺骗用户浏览器,让其以用户的名义执行操作

防御措施

检查 Referer 字段

HTTP 头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer 字段应和请求的地址位于同一域名下。以上文银行操作为例,Referer 字段地址通常应该是转账按钮所在的网页地址,应该也位于 www.examplebank.com 之下。而如果是 CSRF 攻击传来的请求,Referer 字段会是包含恶意网址的地址,不会位于 www.examplebank.com 之下,这时候服务器就能识别出恶意的访问。
这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 http 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。

添加校验 token

由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再执行 CSRF 攻击。这种数据通常是表单中的一个数据项。服务器将其生成并附加在表单中,其内容是一个伪乱数。当客户端通过表单提交请求时,这个伪乱数也一并提交上去以供校验。正常的访问时,客户端浏览器能够正确得到并传回这个伪乱数,而通过 CSRF 传来的欺骗性攻击中,攻击者无从事先得知这个伪乱数的值,服务器端就会因为校验 token 的值为空或者错误,拒绝这个可疑请求。

XSS

跨站脚本(英语:Cross-site scripting,通常简称为:XSS)是一种网站应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了 HTML 以及用户端脚本语言。
XSS 攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是 JavaScript,但实际上也可以包括 Java,VBScript,ActiveX,Flash 或者甚至是普通的 HTML。攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、私密网页内容、会话和 cookie 等各种内容。

背景和现状

当网景(Netscape)最初推出 JavaScript 语言时,他们也察觉到准许网页服务器发送可执行的代码给一个浏览器的安全风险(即使仅是在一个浏览器的沙盒里)。它所造成的一个关键的问题在于用户同时打开多个浏览器视窗时,在某些例子里,网页里的片断代码被允许从另一个网页或对象取出数据,而因为恶意的网站可以用这个方法来尝试窃取机密信息,所以在某些情形,这应是完全被禁止的。为了解决这个问题,浏览器采用了同源决策 —— 仅允许来自相同域名系统和使用相同协议的对象与网页之间的任何交互。这样一来,恶意的网站便无法借由 JavaScript 在另一个浏览器窃取机密数据。此后,为了保护用户免受恶意的危害,其他的浏览器与服务端指令语言采用了类似的访问控制决策。
XSS 漏洞可以追溯到 1990 年代。大量的网站曾遭受 XSS 漏洞攻击或被发现此类漏洞,如 Twitter,Facebook,MySpace,Orkut,新浪微博和百度贴吧。研究表明,最近几年 XSS 已经超过缓冲区溢出成为最流行的攻击方式,有 68% 的网站可能遭受此类攻击。根据开放网页应用安全计划(Open Web Application Security Project)公布的 2010 年统计数据,在 Web 安全威胁前 10 位中,XSS 排名第 2,仅次于代码注入(Injection)。

攻击手段和目的

攻击者使被攻击者在浏览器中执行脚本后,如果需要收集来自被攻击者的数据(如 cookie 或其他敏感信息),可以自行架设一个网站,让被攻击者通过 JavaScript 等方式把收集好的数据作为参数提交,随后以数据库等形式记录在攻击者自己的服务器上。
常用的 XSS 攻击手段和目的有:

  • 盗用 cookie,获取敏感信息。
  • 利用植入 Flash,通过 crossdomain 权限设置进一步获取更高权限;或者利用 Java 等得到类似的操作。
  • 利用 iframe、frame、XMLHttpRequest 或上述 Flash 等方式,以(被攻击)用户的身份执行一些管理动作,或执行一些一般的如发微博、加好友、发私信等操作。
  • 利用可被攻击的域受到其他域信任的特点,以受信任来源的身份请求一些平时不允许的操作,如进行不当的投票活动。
  • 在访问量极大的一些页面上的 XSS 可以攻击一些小型网站,实现 DDoS 攻击的效果。

漏洞的防御和利用

过滤特殊字符

避免 XSS 的方法之一主要是将用户所提供的内容进行过滤,许多语言都有提供对 HTML 的过滤:

  • PHP 的 htmlentities() 或是 htmlspecialchars()
  • Python 的 cgi.escape()
  • ASP 的 Server.HTMLEncode()
  • ASP.NET 的 Server.HtmlEncode() 或功能更强的 Microsoft Anti-Cross Site Scripting Library。
  • Java 的 xssprotect (Open Source Library)。
  • Node.js 的 node-validator。

使用 HTTP 头指定类型

很多时候可以使用 HTTP 头指定内容的类型,使得输出的内容避免被作为 HTML 解析。如在 PHP 语言中使用以下代码:

1
2
3
<?php
header('Content-Type: text/javascript; charset=utf-8');
?>

即可强行指定输出内容为文本 / JavaScript 脚本(顺便指定了内容编码),而非可以引发攻击的 HTML。

用户方面

包括 Internet Explorer、Mozilla Firefox 在内的大多数浏览器皆有关闭 JavaScript 的选项,但关闭功能并非是最好的方法,因为许多网站都需要使用 JavaScript 语言才能正常运作。通常来说,一个经常有安全更新推出的浏览器,在使用上会比很久都没有更新的浏览器更为安全。


参考文章:
跨站请求伪造 - 维基百科
跨站脚本 - 维基百科
WordPress Nonces
Securing Input