XSS 和 CSRF
XSS
XSS (Cross-site scripting) 跨站脚本攻击,首字母缩写本应为 CSS,但因为 CSS 在网页设计领域已经被广泛指层叠样式表(Cascading Style Sheets),所以将意为“交叉”的 Cross 改以交叉形的 X 做为缩写。
XSS 是指攻击者利用网站没有对用户提交数据进行转义或过滤的缺点,在网站上注入恶意脚本,受害者在使用网站时恶意脚本被执行的攻击。注入脚本有 JavaScript、CSS、HTML,注入方法有很多,比如:表单提交、URL 参数、图片上传、外链等。XSS 的危害有盗取帐号、转账…。可搭建 pikachu 靶场实验。
从利用的角度上,XSS 可以分为 3 类:存储型、反射型、DOM 型,另外,如果自己注入的 XSS 脚本,仅能 XSS 到自己,则称为 Self XSS。不管哪种类型的 XSS,XSS 的本质就是让受害者浏览器执行攻击者插入的脚本,本质没有区别。
- 存储型
存储型 XSS 是指注入的脚本被存储在服务器中持久化的 XSS。在 IM、留言、文章、个人信息…这些场景中最常出现,是危害最大的 XSS。
以 IM 为例,如果服务端在往数据库存入用户聊天数据时没做 XSS 处理,而正好 CSR 或者 SSR 渲染时又直接输出,这时则会出现漏洞。攻击者在聊天框中输入以下 Payload,所有在聊天室的人都将被攻击。
1 | <img src=# onerror="alert('xss')"> |
- 反射型
反射型 XSS 是指将用户的输入反射给浏览器的一种 XSS。非持久化,与 DOM 型不同的是用户的输入在服务端渲染(字符串),常出现在搜索栏中,攻击者构造好含恶意代码参数的 URL后,欺骗受害者去访问。
以如下搜索栏 SSR 代码为例:
1 | <input name=keyword value='<%- $keyword %>'> |
攻击者构造 Payload 为 ' oninput=alert('xss')//
的 URL meiyike.cn?keyword=%27%20oninput=alert(%27xss%27)//
,在浏览器上反射为以下形式,然后诱导受害者点击触发。
1 | <input name=keyword value='' oninput=alert('xss')//'> |
- DOM 型
DOM 型是通过对 DOM 树的修改而实现的 XSS,其本质上也属于反射型,只不过用户的输入在前端渲染(innerHTML
、appendChild
、document.write
…),属于前端自身浏览器解析机制的漏洞,没有服务端的参与(存储型与反射型都需要服务器响应参与)。
注:HTML5 规范中指定不执行由 innerHTML
插入的 <script>
。
Payload 和绕过方式
- Payload
用以完成各种具体功能的 XSS 脚本,被称为 XSSPayload,常用 XSSPayload 有以下类型。
1 | // img |
可根据具体的输出点(value 属性中、html 标签中、script 标签中)来构造 Payload,比如在 value 属性中,可提前闭合属性和标签:
1 | <input value="[输出]" type=text> |
- 绕过方式
大多数 XSS 检查器或 WAF 都是利用黑名单或者白名单的形式对 XSS 攻击进行拦截,常见的绕过方式有以下几种。
1 | 编码绕过 |
防范
XSS 需要客户端和服务端来共同防范(DOM 型完全由客户端防范),常用的手段有,转义和过滤、CSP、HttpOnly。
- 转义和过滤
不要信任用户提交的数据,对用户的输入进行转义(escape)和过滤。各种类型的输出点转义和过滤规则不一样,输出在 HTML 标签之间和属性中(比如 value)时,要考虑 HTML 构造中的尖括号、双引号、”&” 等关键字符,对输出进行 HTML Entity 编码转义,过滤移除用户输入中的 style
、script
、iframe
节点等,移除 onerror
等 DOM 属性,输出在 script 标签之间,则要考虑分号、注释、引号等关键字符。
现在前端框架(React、Vue)都具有内置的 XSS 预防功能,标签会被转义输出。
1 | const xss = '<img src=# onerror="alert(\'xss\')">'; |
1 | <img src=# onerror="alert('xss')"> |
对一定会渲染 HTML 的位置(富文本)需要使用 XSS 检查器过滤,比如 sanitize-html、js-xss。
1 | import sanitize from 'sanitize-html'; |
在传统的服务端 SSR 中,原理一样。
1 | <!-- EJS 中使用 <%= 代替 <%- 实现 HTML 的转义 --> |
- CSP
内容安全策略 CSP (Content Security Policy) 可在服务端使用 HTTP 的 Content-Security-Policy 头部来指定策略,也可在前端通过 meta
标签设置。前端和服务端设置 CSP 的效果相同,但是 meta
无法使用 report。
1 | Content-Security-Policy: default-src 'self' |
1 | <meta http-equiv="Content-Security-Policy" content="form-action 'self';"> |
上面的配置只允许加载同域下的资源。
- HttpOnly
对于以盗取 Cookie 为目的的 XSS,设置 Cookie 的 HttpOnly 属性是一种有效的防范手段。浏览器会禁止页面中的 JavaScript 访问带有 HttpOnly 属性的 Cookie。在 Express 下设置 httpOnly:
1 | res.cookie('sessionId', 1, {maxAge: 60 * 1000, httpOnly: true}) |
对于存储型 XSS,服务端和客户端都需要正确进行过滤输出。
防御的方法,一般认为是正确escape(转义),就是替换尖括号、引号等特殊符号。但是这是不够的,因为这只解决了html的问题。考虑如下:
1 | <script>var name = '<?= $name ?>';</script> |
CSRF
CSRF (XSRF) 跨站请求伪造, 是一种冒充受信任用户,向服务器发送非预期请求的攻击方式。例如,用户登录网站 A,保留了会话 Cookie,然后用户被某些信息诱导访问危险网站 B,B 上提前构造好参数的 img
标签对 A 的服务端发起跨域 GET 请求,并且携带了 A 的 Cookie ,身份被冒用,请求被执行。
1 | <img src="https://www.example.com/index.php?action=delete&id=123"> |
造成 CSFR 的根本原因是跨域访问时的第三方 Cookie 携带。
XHR、font… 这些 HTTP 请求默认都是同源策略的,但是 img
、link
、script
、iframe
的 GET 请求允许 cross-origin,另外,CORS 也可用来打破同源策略,一旦设置不当,范围过宽,都会造成 CSRF 漏洞。
防范
- 验证码
添加验证码来识别是不是用户主动去发起请求,简单可靠,低成本,但对用户交互不友好。
- HTTP Referer
HTTP 请求头 Referer 字段,记录了请求的来源地址,服务器验证这个来源地址是否合法即可。
- Samesite Cookie
Cookie 的 Samesite 属性,用来声明 Cookie 是否仅限于第一方或者同一站点上下文。可用来防止 CSRF 攻击和用户追踪。Samesite 有三个属性值,分别是 strict
、lax
和 none
。
1 | res.cookie('sessionId', 1, {samesite: 'lax'}) |
strict
严格模式,表明 Cookie 在任何情况都不可能作为第三方的 Cookie。此时,在 B 站点下发起对 A 站点的任何请求,A 站点的 Cookie 都不会包含在 Cookie 请求头中。
lax
宽松模式,允许安全 HTTP 方法(Get
、OPTIONS
、HEAD
)携带 Cookie,但是不安全 HTTP 方法(POST
、PUT
、DELETE
)不能携带。Lax
是 Chrome 80 起的默认设置。
none
没有限制,必须同时设置 Secure 属性(Cookie 只能通过 HTTPS 协议发送)。
- Tooken + 自定义 Header
CSRF 依赖于 Cookie,如果不通过 Cookie 保持会话,则无法利用 CSRF 相关的攻击向量。可将会话保留在浏览器本地存储中,然后通过自定义 HTTP Header(比如 authorization)携带。