HttpOnly

浏览器禁止页面的JavaScript访问带有HttpOnly属性的Cookie。
严格地说,HttpOnly并非为了对抗XSS——HttpOnly解决的是XSS后的Cookie劫持攻击

输入检查

常见的Web漏洞如XSS、SQL Injection等,都要求攻击者构造一些特殊字符,这些特殊字符可能是正常用户不会用到的,所以输入检查就有存在的必要的。
这些格式检查,有点像一种“白名单”,也可以让一些基于特殊字符的攻击失败。
输入检查的逻辑,必须放在服务器端代码中实现。

输出检查

一般来说,除了富文本的输出外,在变量输出到HTML页面时,可以使用编码或转义的方式来防御XSS攻击。

  1. 安全的编码函数
    如:HtmlEncode的作用是将字符转换成HTMLEntities。为了对抗XSS,HtmlEncode要求至少转换一下字符:
    &-->&
    <-->&lt;
    >-->&gt;
    "-->&quot;
    '-->'
    /-->/
    除了HtmlEncode外,还有JavascriptEncode、JSONEncode
  2. 只需一种编码吗?
    XSS攻击主要发生在MVC架构的View层。大部分的XSS漏洞可以在模板系统中解决。一些框架默认所有的变量都会被escape,这个做法是值得称道的,它符合“Secure By Default”原则。
    但是XSS是很复杂的问题,需要“在正确的地方使用正确的编码方式”,有的时候由于没有分清输出变量的语境,会导致XSS攻击发生。因此并非在模板引擎中使用了auto-escape就万事大吉了,XSS的防御需要区分情况对待。

正确地防御XSS

XSS的本质还是一种“HTML注入”,用户的数据被当成了HTML代码一部分来执行,从而混淆了原本的语义,产生了新的语义。
如果网站使用了MVC架构,那么XSS就发生在View层——在应用拼接变量到HTML页面时产生。所以在用户提交数据处进行输入检查的方案,其实并不是在真正发生攻击的地方做防御。想要根治XSS问题,可以列出所有XSS可能发生的场景,再一一解决。
| 场景 | 防御方法 |
| :-----| ----: |
| 在HTML标签中输出 | HtmlEncode |
| 在HTML属性中输出 | HtmlEncode |
| 在<script>中输出 | JavascriptEncode |
| 在事件中输出 | JavascriptEncode |
| 在CSS中输出 | encodeForCSS()函数 |
| 在地址中输出 | encodeForCSS()函数 |

处理富文本

有些时候,网站需要允许用户提交一些自定义的HTML代码,称之为“富文本”。比如一个用户在论坛里发帖,帖子的内容里要有图片、视频,表格等,这些“富文本”的效果都需要通过HTML代码来实现。

  1. 在过滤富文本时,“事件”应该被严格禁止。
  2. 在标签的选择上,应该使用白名单,避免使用黑名单。
  3. 尽可能地禁止用户自定义CSS与style。

防御DOM Based XSS

会触发DOM Based XSS的地方有很多,以下几个地方是JavaScript输出到HTML页面的必经之路。

  • document.write()
  • document.writeln()
  • xxx.innerHTML=
  • xxx.outerHTML=
  • innerHTML.replace
  • document.attachEvent()
  • window.attachEvent()
  • document.location.replace()
  • document.location.assign()

从业务风险角度看XSS的风险

在修补XSS漏洞时遇到的最大挑战之一是漏洞数量太多,因此开发者可能来不及。也不愿意修补这些漏洞。从业务风险的角度来重新定位每个XSS漏洞,就具有了重要的意义。

一般来说,存储型XSS的风险会高于反射型XSS。因为存储型XSS会保存在服务器上,有可能会跨页面存在。它不改变页面URL的原有结构。因此有时候还能逃过一些IDS的检测。
从攻击过程来说,反射型XSS,一般要求攻击者诱使用户点击一个包含XSS代码的URL链接;而存储型XSS,则只需要让用户查看一个正常的URL链接。
从风险的角度看,用户之间有互动的页面,是可能发起XSS Worm攻击的地方。而根据不同页面的Page View高低,也可以分析出哪些页面受XSS攻击后的影响会更大。比如在网站首页发生的XSS攻击,肯定比网站合作伙伴页面的XSS攻击要严重得多。