---
title: "Web 安全概览"
description: "介绍 Web 端常见的攻击手法及原理 (如: XSS、CSRF、SSRF、JSON Hijacking)。并且提供 Docker 靶场，方便根据介绍的攻击手法去实战"
date: 2019-10-17
tags: ["web","xss","csrf","ssrf","json hijacking"]
lang: zh-Hans
url: https://bugs.cc/zh/posts/web-security-overview/
translation: https://bugs.cc/posts/web-security-overview/
---

# Web 安全概览

## 前言

介绍一下 `Web` 端相关的攻击手法及原理，包括但不限于 `前端` `后端` `运维`

本文后端语言将使用 `PHP`，因为经过对比，`PHP` 作为后端代码演示，是最显而易见的。

以及本文介绍的所有攻击手法都将提供 `Docker` 靶场，方便后续大家自行研究、测试。

此篇文章是由我之前写的安全文章进行了部分汇总、优化及添加。如果大家比较感兴趣，可以去 [Freebuf Black-Hole](https://www.freebuf.com/author/Black-Hole) 看我之前写的文章。

## 前端

### XSS

`XSS` 本质就是在别人的浏览器里执行你的 `JavaScript` 代码，其他都是一种辅助手段。

#### DOM XSS

利用 JavaScript 操作 DOM 的特性，来做到攻击

经常出现的情况: 前后端分离架构

很多开发者，都知道不到万不得已不要使用 `eval` 函数，不使用的原因其中一点就是不安全，但是难道不使用 `eval` 就不会有安全问题么？答案是否定的。

见下面的代码:

```html
<script>
  window.open(new URL(location.hash.slice(1)).href)
</script>
```

为了更好的说明，我这里简单的把作用说明一下:

> 假设当前 url 为 `http://baidu.com/#http://360.cn`，那么 `location.hash.slice(1)` 的结果就是 `http://360.cn`
>
> `new URL(url).href` 则是会对 url 进行解析，并取出解析后的 url。如果你的 url 不满足它内部的判断，则会报错。

是不是觉得这段代码没什么问题。毕竟有 `new URL()` 这种浏览器内置函数帮我们做了过滤。

OK，那我们就要去攻破这个函数的判断。下图为 `URL` 接口规范中的[列表](https://url.spec.whatwg.org/#example-url-parsing):

![](https://bugs.cc/images/web-security-overview/url-parsing-spec-table.png)

我们可以看到 `hello:world` 是符合规范的。而且 `hello:` 正好又是 `JavaScript` 里的`标记`，类似于 C 语言中的 `goto`。那我们更改 url 为: `http://baidu.com/#javascript:alert(1)`。则会成功触发漏洞。

[Docker 靶场](https://github.com/alo7/web-security-docker/tree/master/front-end/XSS/DOM-XSS)

## 后端

### 反射 XSS

因为前/后端没有做好相对应的过滤，导致的问题[^reflected-dom]

经常出现的情况: MVC 架构

因为像 `MVC` 这种架构，其实是在后端语言中编写前端代码，在访问 url 的时候，会先由后端根据请求去构造前端页面，再返回前端源码到浏览器端，而这个过程中，如果出现了 XSS 漏洞，又因为后端参与其中，所以我们一般认为这是 `反射型XSS`。

见下面的 PHP 代码:

```php
<?php
  // 关闭浏览器的 XSS 检测机制
  header("X-XSS-Protection: 0;");

  $bg = $_GET['bg'];
  if (empty($bg)) {
    $bg = '999';
  }
  echo "<div style='width: 120px; height: 120px; background-color:#$bg'>我是一只小方块</div>";
?>
```

可以看到，这段代码将会获取 url 参数中的 `bg` 来获取背景颜色。乍一看，好像没什么问题。但是要知道其中 `$bg` 是可控的。那我们只需要把里面的标签闭合掉就行。见下面:

`http://127.0.0.1:8082/?bg=123' onclick='alert(1)`

上面的代码放在 `bg` 参数里，那代码就变成了

\<div style='width: 120px; height: 120px; background-color:#**`123' onclick='alert(1)`**'\>我是一只小方块\<\/div\>

[Docker 靶场](https://github.com/alo7/web-security-docker/tree/master/back-end/XSS/non-persistent)

### 存储型 XSS

在反射 XSS 的基础上，做了一步 入库的操作

见下面的代码:

```php
// 获取客户端的 IP 地址
// 此段代码来之: https://stackoverflow.com/questions/3003145/how-to-get-the-client-ip-address-in-php
$ipaddress = 'UNKNOWN';
$keys = array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR');
foreach($keys as $k) {
  if (isset($_SERVER[$k]) && !empty($_SERVER[$k])) {
    $ipaddress = $_SERVER[$k];
    break;
  }
}

// 获取内容，并对内容做编码过滤
$content = htmlspecialchars($_POST['content'], ENT_QUOTES);

$sql = "INSERT INTO xss.message (content, ip) VALUES ('$content', '$ipaddress')";
$conn->query($sql);
```

这段代码看起来是没有任何问题的，因为我们已经对 `content` 参数做了进一步的编码过滤。[^htmlspecialchars]

那即使我们写入了 HTML 标签等代码，也会被转码，假如我们提交的内容为 `<script>alert(1)</script>` 那在数据库的里表现就会变成: `&lt;script&gt;alert(1)&lt;/script&gt;`

那难道没有办法绕过去么？答案当然是可以。

可以看到，这段代码不止把内容入库了，还把用户的 IP 也入库了。那问题就出现了，`CLIENT-IP`、`X_FORWARDED_FOR` 是用户可控的。只需要安装 [ModHeader](https://bewisse.com/modheader/) 这个浏览器插件即可如图:

![ModHeader 插件的请求头设置: CLIENT-IP 的值填成 XSS 代码 script alert(1)](https://bugs.cc/images/web-security-overview/modheader-clientip-xss.png)

修改后，再去重新提交一次，就会发现已经成功入库了，如图:

![xss.message 表的查询结果: 伪造 IP 后 XSS 代码未经编码直接入库](https://bugs.cc/images/web-security-overview/mysql-stored-xss.png)

[Docker 靶场](https://github.com/alo7/web-security-docker/tree/master/back-end/XSS/persistent)

### CSRF

CSRF 可以理解为是一种借刀杀人的手法，一般出现在表单提交里。最常见的是出现的开源项目里，比如各类 CMS。

```html
<form method="post" action="addAdminUser.php">
  <div class="input-group">
    <label for="username">要添加的管理员账号: </label>
    <input type="input" name="username">
  </div>
  <div class="input-group">
    <label for="password">要添加的管理员密码: </label>
    <input type="password" name="password">
  </div>
  <div class="input-group">
    <button type="submit">添加</button>
  </div>
</form>
```

见上面代码，此代码存在于管理员后台里。用于添加管理员账号。并且 `addAdminUser.php` 里是有对当前用户的 `Cookies` 做了校验，非管理员账户不能添加。

但是这个表单里没有 `验证码` 、`Token`，以及 `addAdminUser.php` 里没有做 `referer` 来源校验。

就会出现 `CSRF` 漏洞。

因为浏览器发现你请求了某个资源后，会自动把你未过期的 `Cookies` 加入到请求头里。也就是说，即使是在其他网站里，发送了添加管理员的请求，会自动把你登陆后的 `Cookies` 加入到请求里，服务端会认为是你本人触发的（因为 `Cookies` 校验成功）

至于为什么这里 `Cors` 没有起到作用，是因为这种方式，请求后是拿不到任何返回信息的，所以 `Cors` 并不会拦截。如果你使用 `Ajax` 来请求，是肯定会被拦截掉，因为 `Ajax` 请求是可以拿到返回信息，所有会被 `Cors` 拦截掉。

那除了 `form` 标签，还有什么方法可以进行攻击呢？在 `w3c cors` 规范中，有这么一句话:

> A [simple cross-origin request](https://www.w3.org/TR/cors/#simple-cross-origin-request) has been defined as congruent with those which may be generated by currently deployed user agents that do not conform to this specification. Simple cross-origin requests generated outside this specification (such as cross-origin form submissions using `GET` or `POST` or cross-origin `GET` requests resulting from `script` elements) typically include [user credentials](https://www.w3.org/TR/cors/#user-credentials), so resources conforming to this specification must always be prepared to expect simple cross-origin requests with credentials.
>
> ----详情可见 [w3c cors](https://www.w3.org/TR/cors/#security)

这里没有说的特别细，我补充了一下，大致意思是说，使用 `GET` 或 `POST` 进行表单提交时，亦或者使用一些 `HTML` 标签引起的 `GET` 请求，如: `a`、`img` 等，基本上都会携带用户凭据 (Cookies)

[Docker 靶场](https://github.com/alo7/web-security-docker/tree/master/back-end/CSRF)

### SSRF

`SSRF` 本质其实和 `CSRF` 相差不大。只是 `CSRF` 面向的是客户端的用户，而 `SSRF` 面向的是服务端本身。

这种漏洞一般出现于: 会访问并返回用户可控的资源的功能上。

例如: `在线查看网站源码`、`获取用户发链接的title`、`在线翻译网页` 等

```php
<script>
  // 根据 url 动态拼接 url
  function seeCode() {
    const url = document.getElementsByTagName('input')[0].value;
    location.href = location.origin + location.pathname + '?url=' + url
  }
</script>

<input type="text" placeholder="请输入要查看源代码的网站 URL"/>
<button onclick="seeCode()">查看</button>
<?php
  $url = $_GET['url'];
  if ("" == trim($url)) {
    return;
  }

  // 获取内容
  $websiteCode = file_get_contents($url);

  // 转码后存入 textarea 标签里
  echo "<textarea>".htmlspecialchars($websiteCode, ENT_QUOTES)."</textarea>";
?>
```

可以看到，这里没有对用户的输入做任何的过滤，导致用户可以直接输入 `http://192.168.1.2` 等 url，来访问内网资源，因为访问资源的是服务端本身，而服务端本身也是可以访问同局域网的资源的。

[Docker 靶场](https://github.com/alo7/web-security-docker/tree/master/back-end/SSRF/)

### JSON Hijacking

`JSON Hijacking` 其实本质和 `CSRF` 原理一样[^json-hijacking-principle]

这个攻击手法和 `CSRF` 最大的不同，就是 `JSON Hijacking` 在 `CSRF` 的基础上，又利用了 `JSONP` 的特性

```php
<?php
  require 'utils.php';

  // 用户认证、并获取用户信息
  $result = getUserInfo();
  if (count($result) != 3) {
    echo "";
    exit();
  }

  $fnName = $_GET['callback'];
  if ("" == trim($fnName)) {
    echo "";
    exit();
  }

	// 输出为: getInfo({"name": name，"balance": balance });
  echo "$fnName({name: '$result[0]', balance: '$result[2]'})";
?>
```

```html
<script>
  function getInfo(data) {
    console.log(data); // {"name": name, "balance": balance}
  }
</script>
<script src="./json.php?callback=getInfo"></script>
```

如果有恶意攻击者，写了一个页面，并且也在网页里加入了 `<script src="http://xxx/json.php?callback=getInfo"></script>`

根据上文提到的  `w3c cors` 规范中，如果用户事先已经登陆过了，再打开恶意攻击者发的链接，那么攻击者就可以获取一些本不应该获取的敏感信息。

[Docker 靶场](https://github.com/alo7/web-security-docker/tree/master/back-end/JSON%20Hijacking)

[^reflected-dom]: `DOM XSS` 也属于 `反射型 XSS`，一般而言统称都叫 `反射 XSS`，只有当需要划分比较细时，才会分开来。

[^htmlspecialchars]: `htmlspecialchars(string,ENT_QUOTES)` 此方法的编码规则为: `&` 编码为 `&amp;`、`"` 编码为 `&quot;`、`'` 编码为 `&#039;`、`<` 编码为 `&lt;`、`>` 编码为 `&gt;`。

[^json-hijacking-principle]: 这里的原理指的是，漏洞产生的原理一样，还是因为标签产生的请求，会携带用户凭证。
