# 壹.5.5 XSS与CSRF攻击

## 壹.5.5.1 XSS

Cross Site Script,跨站脚本攻击。是指攻击者在网站上注入恶意客户端代码，通过恶意脚本对客户端网页进行篡改，从而在用户浏览网页时，对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

### 1.容易发生的场景

(1)数据从一个不可靠的链接进入到一个web应用程序。

(2)没有过滤掉恶意代码的动态内容被发送给web用户。

### 2.XSS攻击的共同点

将一些隐私数据如cookie、session发送给攻击者，将受害者重定向到一个由攻击者控制的网站，在受害者机器上进行恶意操作。

### 3.XSS攻击的类型

分为存储性（持久型）、反射型（非持久型）、基于DOM

#### **1）反射型**

反射型XSS攻击把用户输入的数据"反射"给浏览器。该攻击方式通常诱使用户 **点击一个恶意链接**，或者 **提交一个表单**, 在用户点击链接或提交表单的同时向用户访问的网站注入脚本。

**实践：模拟反射型XSS攻击**

在正常页面上添加一个恶意链接。恶意链接的地址指向localhost:3000。

然后攻击者有一个node服务来处理对localhost:3000的请求:

```javascript
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();

router.get('/', async ctx => {
  ctx.body = '<script>alert("XSS攻击")</script>';
});

app.use(router.routes())
app.listen('3000', ()=> {
  console.log('Listening 3000');
});
```

当用户点击恶意链接时，页面跳转到攻击者预先准备的页面，会发现在攻击者的页面执行了 js 脚本。这样就产生了反射型 XSS 攻击。攻击者可以注入任意的恶意脚本进行攻击，可能注入恶作剧脚本或注入能获取用户隐私数据(如cookie)的脚本。

#### **2）存储型**

存储型XSS把用户输入的带有恶意脚本的数据存储在服务器端。当浏览器请求数据时，服务器返回脚本并执行。

常见的场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论，文章或评论发表后，所有访问该文章或评论的用户，都会在他们的浏览器中执行这段恶意的 JavaScript 代码。

**实践：模拟存储型XSS攻击**

例如在一个论坛评论输入框内写下:

```markup
<script>alert("XSS攻击")</script>
```

该内容提交后会保存至该论坛数据库。而该论坛显示评论的页面则会把各用户提交的评论内容输出。例如显示评论页面可能是这样的:

```markup
<div>
  <p>用户1</p>
  <div>哈哈哈说得真好</div>
</div>
<div>
  <p>用户2</p>
  <div>赞同</div>
</div>
<div>
  <p>XSS攻击者</p>
  <div><script>alert("XSS攻击")</script>
</div>
</div>
```

其他用户访问该评论显示页面时，恶意脚本就会在浏览器端执行。

**3） 基于DOM**

基于DOM的XSS是指通过恶意脚本修改页面的DOM结构。是纯粹发生在 **客户端**的攻击。

**实践：模拟基于DOM的XSS攻击**

某正常网站的内容会显示url地址的中的参数。例如url为：

```
http://xxx.com?name=abc
```

其页面smarty模板为:

```markup
<div><%$smarty.get.name%></div>
```

得到页面为:

```markup
<div>abc</div>
```

那么XSS攻击者可以制作出这样的链接；

```
http://xxx.com?name=<script>alert("XSS攻击")</script>
```

那么其页面最终得到的是:

```markup
<div>
  <script>alert("XSS攻击")</script>
</div>
```

如果其他用户点击了XSS攻击者构造的链接，那么页面中就多了一段可执行脚本。这种攻击也可以说是反射型的。

### 4.XSS攻击的防范

#### **1） 现代主流浏览器内置CSP**

内容安全策略（CSP）用于检测和减轻用于 Web 站点的特定类型的攻击，例如 XSS 和数据注入等。

CSP本质上是建立白名单，规定了浏览器只能执行特定来源的代码。

通过 **Content-Security-Policy**HTTP头来开启CSP:

* 只允许加载本站资源Content-Security-Policy: default-src 'self'
* 只允许加载HTTPS协议图片 Content-Security-Policy: img-src https\://\*
* 允许加载任何来源框架 Content-Security-Policy: child-src 'none'

#### **2） HttpOnly阻止Cookie劫持攻击**

为避免跨域脚本 (XSS) 攻击，通过JavaScript的 Document.cookie API无法访问带有 HttpOnly 标记的Cookie，它们只应该发送给服务端。如果 Cookie 不想被客户端 JavaScript 脚本调用，那么就应该为其设置 HttpOnly 标记。

如上所述，发起XSS的攻击者既然可以通过注入恶意脚本获取用户的 Cookie 信息。所以，严格来说，HttpOnly 并非阻止 XSS 攻击，而是能阻止 XSS 攻击后的 Cookie 劫持攻击。

含有HttpOnly标志的Cookie在HTTP响应头Set-Cookie：

```
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
```

**实践**

设置含有HttpOnly标志的Cookie:

```
document.cookie = encodeURIComponent('name')+ '=' + encodeURIComponent('BnnieWang') + ';secure;HttpOnly'
//"name=BnnieWang;secure;HttpOnly"
```

验证：执行上述代码后输入document.cookie，可以看到得到的cookie中没有刚刚设置的这个cookie。

> Tips: Koa的ctx.cookies.set()方法是默认httpOnly为true

**对比cookie的secure标记**

标记为 Secure 的Cookie只能通过被HTTPS协议加密过的请求发送给服务端。

但即便设置了 Secure 标记，敏感信息也不应该通过Cookie传输，因为Cookie有其固有的不安全性，Secure 标记也无法提供确实的安全保障。**也就是说，即使设置了Secure标志，还是可以从前端通过document.cookie获取该cookie**。从 Chrome 52 和 Firefox 52 开始，不安全的站点（http:）无法使用Cookie的 Secure 标记。

#### **3） 输入检查/XSS Filter**

对于用户的任何输入要进行检查、过滤和转义。一般是检查用户输入的数据中是否包含 <，>,script 等特殊字符，如果存在，则对特殊字符进行过滤或编码。

**前端框架中自带的decodingMap**

一些前端框架中，都有一份decodingMap,会对用户输入所包含的特殊字符或标签进行编码或过滤，以防止XSS攻击。例如vue中的decodingMap:

```
// 在 vuejs 中，如果输入带 script 标签的内容，会直接过滤掉

const decodingMap = {

  '&lt;': '<',

  '&gt;': '>',

  '&quot;': '"',

  '&amp;': '&',

  '

  ': '\n'
}

```

**手动对输入内容进行转义:**

```
function escape(str) {
  str = str.replace(/&/g, '&amp;');
  str = str.replace(/</g, '&lt;');
  str = str.replace(/>/g, '&gt;');
  str = str.replace(/"/g, '&quto;');
  str = str.replace(/'/g, '&##39;');
  str = str.replace(/\//g, '&##x2F;')
}
```

通过转义攻击脚本:

```
<script>alert("XSS攻击")</script>
```

可转变为字符串:

```
&lt;script&gt;alert(&quto;XSS攻击;&quto;)&lt;&##x2F;script&gt;
```

**注意：富文本显示的转义**

对于显示富文本来说，不能用上述方法转义所有字符，因为这样会把需要的格式也去掉。例如会把< h1>标题 < /h1>也转义掉。这种方法通常采用白名单过滤的办法，把需要的标签(如< h1>)保留。

例如使用'xss'库:

```
const xss = require('xss');
const html = xss('<h1>XSS Demo</h1><script>alert("xss");</script>');
console.log(html)// <h1>XSS Demo</h1>&lt;script&gt;alert("xss");&lt;/script&gt;
```

这样就在过滤了script标签的同时保留了h1标签。

**4）输出检查**

在变量输出到 HTML 页面时，也可以使用上述(3)类似的编码或转义的方式来防御 XSS 攻击。

## 壹.5.5.2 CSRF

Cross Site Request Forgery,跨站请求伪造。是劫持受信任用户向服务器发送非预期请求的攻击方式。例如，这些非预期请求可能在url后加入一些恶意的参数，从而达到攻击者的目的。

通常情况下，CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任，可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器，从而在并未授权的情况下执行在权限保护之下的操作。

简单来说，**CSRF就是利用用户的登录态发起恶意请求**。

### 1. 场景举例：通过 Cookie 进行 CSRF 攻击

假设有一个 bbs 站点：*<http://www.c.com>*，当登录后的用户发起如下 GET 请求时，会删除 ID 指定的帖子：

```
http://www.c.com:8002/content/delete/:id
```

如发起 *<http://www.c.com:8002/content/delete/87343>* 请求时，会删除 id 为 87343 的帖子。当用户登录之后，服务器会设置如下 cookie并发送到浏览器:

```
  res.setHeader('Set-Cookie', ['user=22333; expires=Sat, 21 Jul 2018 00:00:00 GMT;']);
```

然后CSRF 攻击者准备了一个页面 \*\*\*\**<http://www.a.com>*：

```
<p>CSRF 攻击者准备的网站：</p>

<img src="http://www.c.com:8002/content/delete/87343">
```

该页面使用了一个 img 标签，其地址指向了删除用户帖子的链接：*<http://www.c.com:8002/content/delete/87343>*

可以看到，当登录用户访问攻击者的网站时，会向 [www.c.com](http://www.c.com) 发起一个删除用户帖子的请求。\*\*由于用户已经登录，则www\.c.com下已经存在了该user=22333的cookie，那么删除请求就会顺利进行。\*\*此时若用户在切换到 [www.c.com](http://www.c.com) 的帖子页面刷新，会发现ID 为 87343 的帖子已经被删除。

这个攻击过程中，攻击者借助受害者的 Cookie 骗取服务器的信任，但并不能拿到 Cookie，也看不到 Cookie 的内容。而对于服务器返回的结果，由于浏览器同源策略的限制，攻击者也无法进行解析。因此，攻击者无法从返回的结果中得到任何东西，他所能做的就是给服务器发送请求，以执行请求中所描述的命令，在服务器端直接改变数据的值，而非窃取服务器中的数据。

若 CSRF 攻击的目标并不需要使用 Cookie，则也不必顾虑浏览器的 Cookie 策略了。

### 2. CSRF的防范

防范CSRF可遵循以下几种规则:

* Get 请求不对数据进行修改
* 不让第三方网站访问到用户Cookie
* 阻止第三方网站请求接口
* 请求时附带验证信息，如验证码或Token

**1）验证码**

CSRF攻击往往是在用户不知情的情况下发起了网络请求。而验证码会保证用户必须与应用进行交互，才能完成请求。

**2）Referer Check**

在HTTP头中有一个字段叫做Referer,它记录了该HTTP请求的来源地址。通过Referer Check,可以检查是否来自合法的"源".

**实践**

以上例来说，如果是从www\.c.com发起的删帖请求，那么Referer值是<http://www.c.com>, 删帖请求应该被允许；而如果是从CSRF攻击者构造的页面www\.a.com发起删帖请求， 那么Referer值是<http://www.a.com>, 删帖请求应该被阻止。故只需要对每一个删帖请求验证其Referer即可防止CSRF攻击。

服务端验证Referer的代码：

```
const app = new Koa();
const router = new Router();

router.get('/', async ctx => {
  if (ctx.headers.referer !== 'http://www.c.com:8002/') {
    ctx.body = 'csrf攻击'
  }
});
```

**3）请求地址添加token验证**

可以在 HTTP 请求中以参数的形式加入一个随机产生的 token，并在服务器端建立一个拦截器来验证这个 token，如果请求中没有 token 或者 token 内容不正确，则认为可能是 CSRF 攻击而拒绝该请求。

CSRF 攻击之所以能够成功，是因为攻击者可以完全伪造用户的请求，该请求中所有的用户验证信息都是存在于 Cookie 中，因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的 Cookie 来通过安全验证。要抵御 CSRF，关键在于在请求中放入攻击者所不能伪造的信息，并且该信息不存在于 Cookie 之中。为请求添加token验证可以很好地做到这一点。

**4) SameSite Cookie**

给Cookie设置SameSite属性。这样服务器可以要求某个cookie在跨站请求时不会被发送，从而可以阻止跨站请求伪造攻击（CSRF）。但目前SameSite Cookie还处于实验阶段，并不是所有浏览器都支持。

## 壹.5.5.3 XSS与CSRF的对比总结

* XSS是利用用户对指定网站的信任；
* CSRF是利用网站对用户的信任。

## 参考文献

{% hint style="info" %}
<https://developer.mozilla.org/zh-CN/docs/Glossary/CSP>\
<https://developer.mozilla.org/zh-CN/docs/Glossary/Cross-site_scripting>\
<https://developer.mozilla.org/zh-CN/docs/Glossary/CSRF>\
<https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Cookies>
{% endhint %}
