乐趣区

详记CSRF攻击与XSS攻击

一、CSRF(跨站请求伪造)

是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。

1、攻击过程

跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并运行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。这个过程可以这样来看:

  • 用户登录了sueRimn.com,并保留了登录凭证(Cookie
  • 攻击者引诱用户点击访问了sb.com(用户自身不知道)
  • sb.comsue.com 发送请求:sue.com/act=xx,浏览器会默认携带 sue.comcookie
  • sue.com接收到请求后,对请求进行验证,发现是曾经用户保留的cookie,误以为是用户本人操作,然后允许了执行请求
  • 在用户不知情的情况下,攻击者暗地完成了自己的攻击操作

可见完成一次 CSRF 攻击,用户必须依次存在着两个操作:

  • 登录信任网站 A,并保存Cookie
  • 在不登出网站 A 的情况下,点击危险网站 B

由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去运行。这利用了 web 中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的

攻击者并不能通过 CSRF 攻击来直接获取用户的账户控制权,也不能直接窃取用户的任何信息。他们能做到的,是 欺骗用户浏览器,让其以用户的名义运行操作

2、CSRF特点

  • 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击发生
  • 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据
  • 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是“冒用”
  • 跨站请求可以用各种方式:图片 URL、超链接、CORSForm 提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中,难以进行追踪

CSRF通常是跨域的,因为外域通常更容易被攻击者掌控。但是如果本域下有容易被利用的功能,比如可以发图和链接的论坛和评论区,攻击可以直接在本域下进行,而且这种攻击更加危险。

3、常见 CSRF 攻击类型

(1)GET 类型

GET类型的 CSRF 利用非常简单,只需要一个 HTTP 请求,一般会这样利用:

<img src="http://bank.example/withdraw?amount=10000&for=hacker" > 

在受害者访问含有这个 img 的页面后,浏览器会自动向 http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker 发出一次 HTTP 请求。bank.example就会收到包含受害者登录信息的一次跨域请求。

(2)POST 类型

这种类型的 CSRF 利用起来通常使用的是一个自动提交的表单,如:

 <form action="http://bank.example/withdraw" method=POST>
    <input type="hidden" name="account" value="xiaoming" />
    <input type="hidden" name="amount" value="10000" />
    <input type="hidden" name="for" value="hacker" />
</form>
<script> document.forms[0].submit(); </script> 

访问该页面后,表单会自动提交,相当于模拟用户完成了一次 POST 操作。

POST类型的攻击通常比 GET 要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许 POST 上面

(3)链接类型

链接类型的 CSRF 并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:

 <a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
  重磅消息!!<a/>

由于之前用户登录了信任的网站 A,并且保存登录状态,只要用户主动访问上面的这个 PHP 页面,则表示攻击成功。

4、防御措施

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对 CSRF 的防护能力来提升安全性。

上文中讲了 CSRF 的两个特点:

  • CSRF(通常)发生在第三方域名
  • CSRF攻击者不能获取到 Cookie 等信息,只是使用

针对以上两个特点制定两个反向的防御措施,如下:

(1)阻止不明外域的访问

主要的方式有:

  • 同源检测
  • Samesite Cookie
1)同源检测

既然 CSRF 大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。

那么问题来了,我们如何判断请求是否来自外域呢?

HTTP 协议中,每一个异步请求都会携带两个Header,用于标记来源域名:

  • Origin Header
  • Referer Header

这两个 Header 在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。服务器可以通过解析这两个 Header 中的域名,确定请求的来源域。

使用 Origin Header 确定来源域名

在部分与 CSRF 有关的请求中,请求的 Header 中会携带 Origin 字段。字段内包含请求的域名(不包含 pathquery)。

如果 Origin 存在,那么直接使用 Origin 中的字段确认来源域名就可以。

但是 Origin 在以下两种情况下并不存在:

  • IE11 同源策略:
  • 302 重定向:

检查 Referer 字段

HTTP头中有一个 Referer 字段,这个字段用以标明请求来源于哪个地址。在处理敏感数据请求时,通常来说,Referer字段应和请求的地址位于同一域名下。

这种办法简单易行,工作量低,仅需要在关键访问处增加一步校验。但这种办法也有其局限性,因其完全依赖浏览器发送正确的 Referer 字段。虽然 http 协议对此字段的内容有明确的规定,但并无法保证来访的浏览器的具体实现,亦无法保证浏览器没有安全漏洞影响到此字段。并且也存在攻击者攻击某些浏览器,篡改其 Referer 字段的可能。

设置 Referrer Policy 的方法有三种:

  1. CSP 设置
  2. 页面头部增加 meta 标签
  3. a标签增加 referrerpolicy 属性

上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的请求中隐藏 Referer。如果攻击者将自己的请求这样填写,这个请求发起的攻击将不携带Referer

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/ff0cdbee.example/withdraw?amount=10000&for=hacker)

另外在以下情况下 Referer 没有或者不可信:

  1. IE6、7下使用 window.location.href=url 进行界面的跳转,会丢失Referer
  2. IE6、7下使用window.open,也会缺失Referer
  3. HTTPS页面跳转到 HTTP 页面,所有浏览器 Referer 都丢失
  4. 点击 Flash 上到达另外一个网站的时候,Referer的情况就比较杂乱,不太可信

综上所述:同源验证是一个相对简单的防范方法,能够防范绝大多数的 CSRF 攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。

2)Samesite Cookie属性

为了从源头上解决这个问题,Google起草了一份草案来改进 HTTP 协议,那就是为 Set-Cookie 响应头新增 Samesite 属性,它用来标明这个 Cookie是个“同站 Cookie“,同站 Cookie 只能作为第一方 Cookie,不能作为第三方 Cookie,Samesite 有两个属性值,分别是 Strict Lax

(2)提交时要求附加本域才能获取的信息
1)添加校验token

由于 CSRF 的本质在于攻击者欺骗用户去访问自己设置的地址,所以如果要求在访问敏感数据请求时,要求用户浏览器提供不保存在 cookie 中,并且攻击者无法伪造的数据作为校验,那么攻击者就无法再运行 CSRF 攻击。

CSRF Token的防护策略分为三个步骤:

  • CSRF Token 输出到页面中
  • 页面提交的请求携带这个Token
  • 服务器验证 Token 是否正确
2)双重 Cookie 验证

在会话中存储 CSRF Token 比较繁琐,而且不能在通用的拦截上统一处理所有的接口。

那么另一种防御措施是使用双重提交 Cookie。利用CSRF 攻击不能获取到用户 Cookie 的特点,我们可以要求 Ajax 和表单请求携带一个 Cookie 中的值。

双重 Cookie 采用以下流程:

  • 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw)。
  • 在前端向后端发起请求时,取出 Cookie,并添加到URL 的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。
  • 后端接口验证 Cookie 中的字段与 URL 参数中的字段是否一致,不一致则拒绝。

双重 Cookie 防御 CSRF 的优缺点:

  • 优点:

    • 无需使用Session,适用面更广,易于实施
    • Token储存于客户端中,不会给服务器带来压力
    • 相对于Token,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加
  • 缺点:

    • Cookie中增加了额外的字段
    • 如果有其他漏洞(例如XSS),攻击者可以注入Cookie,那么该防御方式失效
    • 难以做到子域名的隔离
    • 为了确保 Cookie 传输安全,采用这种防御方式的最好确保用整站 HTTPS 的方式,如果还没切 HTTPS 的使用这种方式也会有风险
(3)其他防御措施
1)CSRF测试

步骤:

  • 1:设置浏览器代理
  • 2:使用合法账户访问网站开始测试
  • 3:通过 CSRF 修改并伪造请求
  • 4:拿到结果如有漏洞进行修复
2)CSRF监控

CSRF 攻击有着比较明显的特征:

  • 跨域请求。
  • GET 类型请求 Header 的 MIME 类型大概率为图片,而实际返回 Header 的 MIME 类型为 Text、JSON、HTML。

在网站的代理层监控所有的接口请求,如果请求符合上面的特征,就可以认为请求有 CSRF 攻击嫌疑。我们可以提醒对应的页面和项目负责人,检查或者 ReviewCSRF 防护策略。

5、CSRF攻击简单总结

  • CSRF自动防御策略:同源检测(Origin Referer 验证)
  • CSRF主动防御措施:Token验证 或者 双重 Cookie 验证 以及配合Samesite Cookie
  • 保证页面的幂等性,后端接口不要在 GET 页面中做用户操作

二、XSS(跨站脚本攻击)

XSS 攻击是指在用户在访问信任的网站时注入恶意攻击脚本(主要是JavaScript),对客户端网页进行篡改,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:获取用户隐私数据像 cookiesession ,进行一些恶意操作。

用户是通过哪种方法“注入”恶意脚本的呢?

不仅仅是业务上的“用户的 UGC 内容”可以进行注入,包括URL 上的参数等都可以是攻击的来源。在处理输入时,以下内容都不可信:

  • 来自用户的 UGC 信息
  • 来自第三方的链接
  • URL 参数
  • POST 参数
  • Referer (可能来自不可信的来源)
  • Cookie (可能来自其他子域注入)

1、XSS注入方式

  • HTML 中内嵌的文本中,恶意内容以 script 标签形成注入。
  • 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)。
  • 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。
  • 在标签的 hrefsrc 等属性中,包含 javascript: 等可执行代码。
  • onloadonerroronclick 等事件中,注入不受控制代码。
  • style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。
  • 在 style 属性和标签中,包含类似 expression(...) 的 CSS 表达式代码(新版本浏览器已经可以防范)。

总之,如果开发者没有将用户输入的文本进行合适的过滤,就贸然插入到 HTML 中,这很容易造成注入漏洞。攻击者可以利用漏洞,构造出恶意的代码指令,进而利用恶意代码危害数据安全。

2、XSS攻击的分类

根据攻击的来源,XSS 攻击可分为存储型、反射型和 DOM 型三种。

(1)存储型XSS

存储型 XSS 的攻击步骤:

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

(2)反射型XSS

反射型 XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

反射型 XSS 跟存储型 XSS 的区别是 :存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。

(3)DOMXSS

DOM XSS 的攻击步骤:

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

DOM XSS 跟前两种 XSS 的区别:DOMXSS 攻击中,取出和执行恶意代码由浏览器端完成,不需要服务器解析响应的直接参与,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

对比表格

XSS攻击类型 存储位置 插入点
存储型XSS 后端数据库 HTML
反射型XSS URL HTML
DOMXSS 后端数据库 / 前端存储 /URL 前端JavaScript

3、XSS攻击的防御措施

(1)输入过滤

只要有输入数据的地方,就存在 XSS 攻击,XSS攻击就有两大因素:

  • 攻击者提交恶意代码
  • 浏览器执行恶意代码

所以,防御措施主要从这两个方面下手:

针对用户输入过程

在用户输入过程中,前端过滤输入的恶意代码,然后再提交给后端,这是不可行的,攻击者完全可以绕过前端过滤,直接构造请求提交恶意代码。

对于明确的输入类型,例如数字、URL、电话号码、邮件地址等等内容,进行输入过滤是必要的。

所以不仅需要前端对输入的格式进行格式检查,然后后端做相关的过滤检查。

针对浏览器执行恶意代码

输入过滤不是万全之策,那就要防止浏览器执行恶意代码,这个分为两步进行:

  • 防止 HTML 中出现注入
  • 防止 JavaScript 执行时,执行恶意代码
(2)预防存储型和反射型 XSS 攻击

存储型和反射型 XSS 都是在服务端取出恶意代码后,插入到响应 HTML 里的,攻击者刻意编写的“数据”被内嵌到“代码”中,被浏览器所执行。

预防这两种漏洞,有两种常见做法:

  • 纯前端渲染,代码和数据分离
  • 转义 HTML
1)纯前端渲染

纯前端渲染的过程:

  1. 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据
  2. 然后浏览器执行 HTML 中的 JavaScript
  3. JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上

在纯前端渲染中,会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。

但纯前端渲染还需注意避免 DOM XSS 漏洞(例如 onload 事件和 href 中的 javascript:xxx 等,请参考下文”预防 DOMXSS 攻击“部分)。

在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。

2)转义HTML

如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。

常用的模板引擎,如 doT.jsejsFreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善:

|XSS 安全漏洞 | 简单转义是否有防护作用 | |-|-| |HTML 标签文字内容 | 有 | |HTML 属性值 | 有 | |CSS 内联样式 | 无 | | 内联 JavaScript| 无 | | 内联 JSON| 无 | | 跳转链接 | 无 |

所以要完善 XSS 防护措施,我们要使用更完善更细致的转义策略。

(3)预防 DOMXSS攻击

DOM XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。

在使用 .innerHTML.outerHTMLdocument.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent.setAttribute() 等。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTMLouterHTMLXSS 隐患。

DOM 中的内联事件监听器,如 locationonclickonerroronloadonmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()setTimeout()setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

<!-- 内联事件监听器中包含恶意代码 -->
![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/3e724ce0.data:image/png,)

<!-- 链接内包含恶意代码 -->
<a href="UNTRUSTED">1</a>
<script>
// setTimeout()/setInterval() 中调用恶意代码
setTimeout("UNTRUSTED")
setInterval("UNTRUSTED")

// location 调用恶意代码
location.href = 'UNTRUSTED'

// eval() 中调用恶意代码
eval("UNTRUSTED")
</script>
(4)其他防御措施

虽然在渲染页面和执行 JavaScript 时,通过谨慎的转义可以防止 XSS 的发生,但完全依靠开发的谨慎仍然是不够的。以下介绍一些通用的方案,可以降低 XSS 带来的风险和后果。

1)Content Security Policy

严格的 CSP XSS 的防范中可以起到以下的作用:

  • 禁止加载外域代码,防止复杂的攻击逻辑。
  • 禁止外域提交,网站被攻击后,用户的数据不会泄露到外域。
  • 禁止内联脚本执行(规则较严格,目前发现 GitHub 使用)。
  • 禁止未授权的脚本执行(新特性,Google Map 移动版在使用)。
  • 合理使用上报可以及时发现 XSS,利于尽快修复问题。
2)输入内容长度控制

对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。

3)其他
  • HTTP-only Cookie: 禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie
  • 验证码:防止脚本冒充用户提交危险操作
  • 白名单: 对于显示富文本来说,不能通过上面的办法来转义所有字符,因为这样会把需要的格式也过滤掉。这种情况通常采用白名单过滤的办法,当然也可以通过黑名单过滤,但是考虑到需要过滤的标签和标签属性实在太多,更加推荐使用白名单的方式

4、XSS攻击检测

两种方法:

(1)使用通用 XSS 攻击字符串手动检测 XSS 漏洞
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert()//>\x3e

这个字符串能够检测到存在于 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等多种上下文中的 XSS 漏洞,也能检测 eval()setTimeout()setInterval()Function()innerHTMLdocument.write()DOMXSS 漏洞,并且能绕过一些 XSS 过滤器。

只要在网站的各输入框中提交这个字符串,或者把它拼接到 URL 参数上,就可以进行检测了。

http://xxx/search?keyword=jaVasCript%3A%2F*-%2F*%60%2F*%60%2F*%27%2F*%22%2F**%2F(%2F*%20*%2FoNcliCk%3Dalert()%20)%2F%2F%250D%250A%250d%250a%2F%2F%3C%2FstYle%2F%3C%2FtitLe%2F%3C%2FteXtarEa%2F%3C%2FscRipt%2F--!%3E%3CsVg%2F%3CsVg%2FoNloAd%3Dalert()%2F%2F%3E%3E
(2)使用扫描工具自动检测 XSS 漏洞

常见自动扫描工具寻找 XSS 漏洞,例如 Arachni、Mozilla HTTP Observatory、w3af 等

5、XSS攻击简单总结

  • 防御存储型和反射型 XSS 是后端的责任。而 DOM XSS 是前端 的责任。防御 XSS 是需要后端和前端共同参与的系统工程
  • 转义应该在输出 HTML 时进行,而不是在提交用户输入时
  • 不同的上下文,如 HTML 属性、HTML 文字内容、HTML 注释、跳转链接、内联 JavaScript 字符串、内联 CSS 样式表等,所需要的转义规则不一致。业务 需要选取合适的转义库,并针对不同的上下文调用不同的转义规则
  • 不仅需要在全部需要转义的位置,对数据进行对应的转义。而且要防止多余和错误的转义,避免正常的用户输入出现乱码
  • 总结以下原则减少漏洞的产生:

    • 利用模板引擎 :开启模板引擎自带的 HTML 转义功能。例如:

      • ejs 中,尽量使用 <%= data %> 而不是 <%- data %>
      • doT.js 中,尽量使用 {{! data} 而不是 {{= data}
      • FreeMarker 中,确保引擎版本高于 2.3.24,并且选择正确的 freemarker.core.OutputFormat
    • 避免内联事件 :尽量不要使用 onLoad="onload('{{data}}')"onClick="go('{{action}}')" 这种拼接内联事件的写法。在 JavaScript 中通过 .addEventlistener() 事件绑定会更安全
    • 避免拼接 HTML:前端采用拼接 HTML 的方法比较危险,如果框架允许,使用 createElementsetAttribute 之类的方法实现。或者采用比较成熟的渲染框架,如 Vue/React
    • 时刻保持警惕 :在插入位置为 DOM 属性、链接等位置时,要打起精神,严加防范
    • 增加攻击难度,降低攻击后果:通过 CSP、输入长度配置、接口安全措施等方法,增加攻击的难度,降低攻击的后果
    • 主动检测和发现 :可使用 XSS 攻击字符串和自动扫描工具寻找潜在的 XSS 漏洞

三、区别

XSS利用的是用户对网站的信任,CSRF利用的是网站对用户网页浏览器的信任。

参考文章:

前端安全系列(一):如何防止 XSS 攻击

前端安全系列(二):如何防止 CSRF 攻击

前端面试查漏补缺 –(七)XSS 攻击鱼 CSRF 攻击

退出移动版