（CVE-2020-4054）Sanitize 跨站脚本漏洞
======================================

一、漏洞简介
------------

于2020/06/16，[Ruby
Sanitize](https://github.com/rgrove/sanitize/security/advisories/GHSA-p4x4-rw2p-8j8m)项目官方发布了编号为\`\`CVE-2020-4054`的[漏洞公告](https://github.com/rgrove/sanitize/security/advisories/GHSA-p4x4-rw2p-8j8m),当`Sanitize`模块的过滤规则被配置成`RELAXED`时，利用该漏洞可以绕过该模块的安全过滤功能。具体配置信息可以查看`lib/sanitize/config/relaxed.rb\`文件。

二、漏洞影响
------------

Sanitize 3.0.0及之后版本（5.2.1版本已修复）

三、复现过程
------------

### 漏洞分析

### `HTML`语法过滤基础思路

这部分将讲述恶意HTML文本的检测思路与过滤手法，有相关经验的大佬可以直接跳过。

[`Sanitize`](https://github.com/rgrove/sanitize)是一个用于检测HTML恶意语法并过滤的Ruby模块，其基于白名单的工作方式，提取到输入内容中的HTML标签后，分析并删除不在白名单内的标签，随后生成相对安全的HTML内容，用户可自定自己的白名单规则（例如，只允许`<b>`、`<i>`标签），不过该模块有一些默认的规则内容，接下来我会分析`RELAXED`过滤规则场景下的默认可信标签，列表如下：

    <a> <abbr> <address <article> <aside> <bdi> <bdo> <blockquote> <body> <br> <caption> <cite>
    <code> <col> <colgroup> <data> <dd> <del> <dfn> <div> <dl> <dt> <figcaption> <figure> <footer>
    <h1> <h2> <h3> <h4> <h5> <h6> <head> <header> <hgroup> <hr> <html> <img> <ins> <kbd> <li> <main> <mark> <nav> <ol> <p> <pre <q> <rp> <rt> <ruby> <s> <samp> <section> <small> <span> <strike> <style> <sub> <summary> <sup> <table> <tbody> <td> <tfoot> <th> <thead> <time> <title> <tr> <ul> <var> <wbr>

HTML恶意检测的工作大致分为三步：

-   将HTML解析为DOM树

-   从DOM树中删除不可行的标签和属性

-   将新的DOM树序列化为HTML标签

```{=html}
<!-- -->
```
-   举个例子，当输入内容如下时：

        ABC<script>alert(1)</script><img src=1 onerror=alert(2)>

    首先会被解析为以下DOM树：

![1.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId28.png)

其中

    script

标签和

    onerror

属性不在白名单规则内，随后将被删除。新的DOM树如下：

![2.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId29.png)

反序列化后如下：

    ABC<img src="1">

理想状态下对输入内容进行过滤后其输出内容都是安全的。

### `style`标签的解析与序列化

`Sanitize`模块的安全标签列表中包含`<style>`标签，可以从它入手，因为该标签的处理方式与其它不同。首先，HTML解析器不会解码`<style>`标签中的HTML实体。举个例子：

    <div>I &lt;3 XSS</div>
    <style>I &lt;3 XSS</style>

生成如下DOM树：

![3.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId31.png)

可以看到，`&lt`在`<div>`标签中已被HTML解码，但在`<style>`标签中没有。编码的大致过程为`''<>`等特殊字符被替换成`&"<>`。然后，对于一些特定标签比如`<style>`标签，在反序列化生成新的HTML内容时没有进行HTML实体编码，举个例子：

![4.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId32.png)

反序列化生成的HTML内容为：

    <div>I &lt;3 XSS</div>
    <style>I <3 XSS</style>

可以看到`<`字符在`<div>`标签内进行了HTML编码为`&lt`，但在`<style>`标签中没有。该特性可被恶意利用，比如如下DOM树：

![5.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId33.png)

其由`Sanitize`反序列化生成HTML内容为：

    <style></style><img src onerror=alert(1)>

这将产生XSS漏洞。接下来的问题是：如何构造出一个恶意的DOM树？

Foreign content特性
-------------------

HTML规范中有很多有趣的特性，当存在`<svg>`或者`<math>`标签时，解析规则会产生变化且上述中`<style>`标签的两个特性将不再受用，该特性就是：`<svg>/<math>`标签中的内容会进行HTML实体解码。理想状态下在`Sanitize`场景下就会生成一个恶意的DOM树举个例子：

    <svg><style>I &lt;3 XSS

对应的DOM树为：

![6.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId35.png)

最终输出为如下并产生XSS漏洞：

    <svg><style>I &lt;3 XSS</style></svg>

### 漏洞复现

回到主题，如何绕过`Sanitize`的过滤规则？`RELAXED`配置场景下，`<style>`标签允许输入但是`<svg>/<math>`标签都不行，`Sanitize`使用的是[`Google Gumbo`](https://github.com/google/gumbo-parser)解析器,它支持HTML5中的新特性。`Sanitize`对CSS语法也进行了安全过滤，但是我发现！
使用`/**/`注释的方法可以进行有效的代码注入，举个例子：

    <svg><style>/*&lt;/style>&lt;img src onerror=alert(1)*/

DOM树如下：

![7.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId38.png)

`<svg>`标签由于不在白名单中被删除了。但其内容仍然存在，因此，此时的DOM树如下：

![8.png](./resource/(CVE-2020-4054)Sanitize跨站脚本漏洞/media/rId39.png)

此时已不再需要进行过滤了，因此反序列化生成的HTML代码为：

    <style>/*</style><img src onerror=alert(1)>*/

好了，XSS已经出来了。

参考链接
--------

> https://xz.aliyun.com/t/8226
