作者:牛志恒,腾讯互娱开发工程师
商业转载请联系腾讯 WeTest 获得授权,非商业转载请注明出处。
原文链接:https://wetest.qq.com/lab/view/422.html

WeTest 导读

本篇包含了 XSS 漏洞攻击及防御详细介绍,包括漏洞基础、XSS 基础、编码基础、XSS Payload、XSS 攻击防御。


第一部分:漏洞攻防基础知识

XSS 属于漏洞攻防,我们要研究它就要了解这个领域的一些行话,这样才好沟通交流。同时我建立了一个简易的攻击模型用于 XSS 漏洞学习。

1. 漏洞术语

了解一些简单术语就好。

VUL

Vulnerability 漏洞,指能对系统造成损坏或能借之攻击系统的 Bug。

POC

Proof of Concept,漏洞证明;可以是可以证明漏洞存在的文字描述和截图,但更多的一般是证明漏洞存在的代码;一般不会破坏存在漏洞的系统。

EXP

exploit,漏洞利用;利用漏洞攻击系统的代码。

Payload

(有效攻击负载)是包含在你用于一次漏洞利用(exploit)中的攻击代码。

PWN

是一个黑客语法的俚语词 ,是指攻破设备或者系统。

0DAY 漏洞和 0DAY 攻击

零日漏洞或零时差漏洞(Zero-dayexploit)通常是指还没有补丁的安全漏洞。

零日攻击或零时差攻击(Zero-dayattack)则是指利用这种漏洞进行的攻击。

零日漏洞不但是黑客的最爱,掌握多少零日漏洞也成为评价黑客技术水平的一个重要参数。

CVE 漏洞编号

Common Vulnerabilities and Exposures,公共漏洞和暴露,为广泛认同的信息安全漏洞或者已经暴露出来的弱点给出一个公共的名称。

可以在https://cve.mitre.org/CVE 编号搜索该漏洞的介绍。也可以在中文社区 http://www.scap.org.cn/上搜索关于漏洞的介绍网站根据漏洞的

2. 漏洞攻击模型

上图为一个简单的攻击模型。攻击就是将 Payload 通过注入点注入到执行点执行的过程。过程顺畅就表明这个漏洞被利用了。

第二部分:XSS 基础知识

基础知识看完,现在我们可以开始接触了解 XSS 基础了。XSS 基础不好就不用研究了,大家没用共同语言。

1. 什么是 XSS?

XSS 全称 Cross-site scripting,跨站脚本攻击。攻击者通过网站注入点注入恶意客户端可执行解析的 Payload,当被攻击者访问网站时 Payload 通过客户端执行点执行来达到某些目的,比如获取用户权限、恶意传播、钓鱼等行为。

2. XSS 的分类

不了解分类其实很难学好 XSS,大家对 XSS 分类有很多误解,而且很多文章上都解释错的,这里我给出一个相对好的 XSS 分类。

2.1 按照 Payload 来源划分

存储型 XSS

Payload 永久存在服务器上,所以也叫永久型 XSS,当浏览器请求数据时,包含 Payload 的数据从服务器上传回并执行。

过程如图:

存储型 XSS 例子:

发表帖子内容包含 Payload->存入数据库->被攻击者访问包含该帖子的页面 Payload 被执行

反射型 XSS

又称非持久型 XSS,第一种情况:Payload 来源在客户端然后在客户端直接执行。第二种情况:客户端传给服务端的临时数据,直接回显到客户端执行。

过程如图:

反射型 XSS 例子 :

  1. 传播一个链接,这个链接参数中包含 Payload->被攻击者访问这个链接 Payload 在客户端被执行。

  2. 在客户端搜索框输入包含 payload 的内容->服务端回显一个页面提示搜索内容未找到,payload 就被执行了。

2.2 按照 Payload 的位置划分

DOM-based XSS

由客户端 JavaScript 代码操作 DOM 或者 BOM 造成 Payload 执行的漏洞。由于主要是操作 DOM 造成的 Payload 执行,所以叫做 DOM-based XSS,操作 BOM 同样也可以造成 Payload 执行,所以这个名词有些不准确,其实叫 JavaScript-based XSS 更好。

DOM-based 的 Payload 不在 html 代码中所以给自动化漏洞检测带来了困难。

过程如图:

反射型 DOM-based XSS 的例子:

在客户端搜索框输入包含 payload 的内容->服务端回显一个页面提示搜索内容未找到,payload 就被执行了。

存储型 DOM-based XSS 的例子:

从服务端接口中获取包含 Payload 的内容->JavaScript 通过操作 DOM、BOM 造成 Payload 执行

HTML-based XSS

Payload 包含在服务端返回的 HTML 中,在浏览器解析 HTML 的时候执行。这样的漏洞易于做自动化漏洞检测,因为 Payload 就在 HTML 里面。当然 HTML-based XSS 也有反射型和存储型的。

过程如图:

反射型 HTML-based XSS 的例子:

在客户端搜索框输入包含 payload 的内容->服务端回显一个页面提示搜索内容未找到,payload 包含在 HTML 被执行。

存储型 HTML-based XSS 的例子:

发表帖子内容包含 Payload->存入数据库->被攻击者访问包含该帖子的页面 Payload 在 HTML 页面中被执行

3. XSS 的攻击目的及危害

很多写出不安全代码的人都是对漏洞的危害没有清晰的认识,下图是 2017 OWASP 网络威胁 Top10:

可以看到 XSS 在网络威胁中的地位举足轻重。

3.1 目的

  1. cookie 劫持

  2. 篡改网页,进行钓鱼或者恶意传播

  3. 网站重定向

  4. 获取用户信息

3.2 危害

  1. 传播类危害

  2. 系统安全威胁

第三部分:XSS 攻击的 Payload

这部分我们分析下攻击模型中的 Payload,了解 Payload 必须了解编码,学习好 JS 也必须要了解好编码。要想真正做好网络安全编码是最基本的。

1. 编码基础

编码部分是最重要的虽然枯燥但必须要会。后面很多变形的 Payload 都建立在你的编码基础。这里通 16 进制编码工具让你彻底学会编码。

1.1 编码工具

16 进制查看器:方便查看文件 16 进制编码

MAC:HEx Friend

windows: HxD

编辑器 Sublime:可以通过 Sublime 将文件保存不同编码类型

1.2 ASCII

定义:美国信息交换标准代码,是基于拉丁字母的一套计算机编码系统,主要用于显示现代英语和其他西欧语言。

编码方式:属于单子节编码。ASCII 码一共规定了 128 个字符的编码,只占用了一个字节的后面 7 位,最前面的 1 位统一规定为 0。0~31 及 127(共 33 个) 是控制字符或通信专用字符。32~126(共 95 个) 是字符 (32 是空格。

1.3 ISO-8859-1(Latin1)

定义:Latin1 是 ISO-8859-1 的别名,ISO-8859-1 收录的字符除 ASCII 收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在 ISO-8859-1 当中。

编码方式:ISO-8859-1 编码是单字节编码,向下兼容 ASCII,其编码范围是 0x00-0xFF,0x00-0x7F 之间完全和 ASCII 一致,0x80-0x9F 之间是控制字符,0xA0-0xFF 之间是文字符号。

注意:ISO-8859-1 编码表示的字符范围很窄,无法表示中文字符。但是,由于是单字节编码,和计算机最基础的表示单位一致,所以很多时候,仍旧使用 ISO-8859-1 编码来表示。比如,虽然” 中文” 两个字不存在 iso8859-1 编码,以 gb2312 编码为例,应该是” d6d0 cec4” 两个字符,使用 iso8859-1 编码的时候则将它拆开为 4 个字节来表示:” d6 d0 ce c4”(事实上,在进行存储的时候,也是以字节为单位处理的)。所以 mysql 中 latin1 可以表示任何编码的字符。

Latin1 与 ASCII 编码的关系:完全兼容 ASCII。

1.4 Unicode 编码 (UCS-2)

Code Point: 码点,简单理解就是字符的数字表示。一个字符集一般可以用一张或多张由多个行和多个列所构成的二维表来表示。二维表中行与列交叉的点称之为码点,每个码点分配一个唯一的编号,称之为码点值或码点编号。

BOM(Byte Order Mark):字节序,出现在文件头部,表示字节的顺序,第一个字节在前,就是” 大端方式”(Big-Endian),第二个字节在前就是” 小端方式”(Little-Endian)。

在 Unicode 字符集中有一个叫做” ZERO WIDTH NO-BREAK SPACE“的字符,它的码点是 FEFF。而 FFFE 在 Unicode 中是不存在的字符,所以不应该出现在实际传输中。在传输字节流前,我们可以传字符” ZERO WIDTH NO-BREAK SPACE“表示大小端,因此字符” ZERO WIDTH NO-BREAK SPACE“又被称作 BOM。

BOM 还可以用来表示文本编码方式,Windows 就是使用 BOM 来标记文本文件的编码方式的。Mac 上文件有没有 BOM 都可以。

例如:\u00FF :00 是第一个字节,FF 是第二个字节。和码点表示方式一样属于大端方式。

Unicode 编码字符集:旨在收集全球所有的字符,为每个字符分配唯一的字符编号即代码点(Code Point),用 U+ 紧跟着十六进制数表示。所有字符按照使用上的频繁度划分为 17 个平面(编号为 0-16),即基本的多语言平面和增补平面。基本的多语言平面又称平面 0,收集了使用最广泛的字符,代码点从 U+0000 到 U+FFFF,每个平面有 216=65536 个码点;

Unicode 编码:Unicode 字符集中的字符可以有多种不同的编码方式,如 UTF-8、UTF-16、UTF-32、压缩转换等。我们通常所说的 Unicode 编码是 UCS-2 将字符编号(同 Unicode 中的码点)直接映射为字符编码,亦即字符编号就是字符编码,中间没有经过特别的编码算法转换。是定长双字节编码:因为我们 UCS-2 只包括本的多语言平面(U+0000 到 U+FFFF)。

UCS-2 的 BOM:大端模式:FEFF。小端模式:FFFE。

文件保存成 UTF-16 BE with BOM 相当于 UCS-2 的大端模式,可以看到 16 进制开头为 FEFF

Latin1 与 Unicode 编码的关系:Latin1 对应于 Unicode 的前 256 个码位。

1.5 UTF-16

定义及编码:UTF-16 是 Unicode 的其中一个使用方式,在 Unicode 基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用 2 字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个 2 字节的值来储存。是双字节编码。

UTF-16 与 UCS-2 的关系:UTF-16 可看成是 UCS-2 的父集。在没有辅助平面字符(surrogate code points)前,UTF-16 与 UCS-2 所指的是同一的意思。但当引入辅助平面字符后,就称为 UTF-16 了。现在若有软件声称自己支援 UCS-2 编码,那其实是暗指它不能支援在 UTF-16 中超过 2bytes 的字集。对于小于 0x10000 的 UCS 码,UTF-16 编码就等于 UCS 码。

UTF-16 的 BOM:大端模式:FEFF。小端模式:FFFE。

1.6 UTF-8

定义及编码:UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度,当字符在 ASCII 码的范围时,就用一个字节表示,保留了 ASCII 字符一个字节的编码作为它的一部分,注意的是 unicode 一个中文字符占 2 个字节,而 UTF-8 一个中文字符占 3 个字节)。从 unicode 到 utf-8 并不是直接的对应,而是要过一些算法和规则来转换。

Unicode 符号范围 UTF-8 编码方式 (十六进制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF8 的 BOM:EFBBBF。UTF-8 不存在字符序列的问题,但是可以用用 BOM 表示这个文件是一个 UTF-8 文件。
文件保存成 UTF-8 BE with BOM,可以看到 16 进制开头为 EFBBBF

1.7 GBK/GB2312

定义及编码:GB2312 是最早一版的汉字编码只包含 6763 汉字,GB2312 只支持简体字而且不全,显然不够用。GBK 编码,是对 GB2312 编码的扩展,完全兼容 GB2312 标准,支持简体字繁体字,包含全部中文字符。GBK 编码采用单双字节编码方案,单字节和 Latin1 一致,双字节是汉字部分,其编码范围:8140-FEFE,剔除 xx7F 码位,共 23940 个码位。

GBK 与 Latin1 的关系:GBK 单字节编码区和 Latin1 编码一致。

GBK 与 Unicode 的关系:GBK 与 Unicode 字符集编码不同但是兼容的。如"汉"的 Unicode 值与 GBK 虽然是不一样的,假设 Unicode 为 a040,GBK 为 b030,但是可以对应转化的。汉字的 Unicode 区:4E00-u9FA5。

GBK 与 UTF-8:GBK 汉字采用双字节编码比在 UTF-8 中的三字节要小。但是 UTF-8 更通用。GBK 与 UTF-8 转化:GBK—> Unicode —> UTF8

2. 前端中的编码

有了编码基础就可以来认识一下前端中的编码,这样你才能真正认识 Payload。我这里的应该是总结最全的。

2.1 Base64

Base64 可以用来将 binary 的字节序列数据编码成 ASCII 字符序列构成的文本。使用时,在传输编码方式中指定 Base64。使用的字符包括大小写拉丁字母各 26 个、数字 10 个、加号 + 和斜杠/,共 64 个字符及等号=用来作为后缀用途。所以总共 65 个字符。

将 3 字节的数据,先后放入一个 24 位的缓冲区中,先来的字节占高位。数据不足 3 字节的话,于缓冲器中剩下的比特用 0 补足。每次取出 6bit 对原有数据用 Base64 字符作为编码后的输出。编码若原数据长度不是 3 的倍数时且剩下 1 个输入数据,则在编码结果后加 2 个=;若剩下 2 个输入数据,则在编码结果后加 1 个=。可以看出 Base64 编码数据大约是原来数据的 3/4。

标准的 Base64 并不适合直接放在 URL 里传输,因为 URL 编码器会把标准 Base64 中的/和 + 字符变为形如%XX 的形式,而这些% 号在存入数据库时还需要再进行转换,因为 ANSI SQL 中已将% 号用作通配符。为解决此问题,可采用一种用于 URL 的改进 Base64 编码,它不在末尾填充=号,并将标准 Base64 中的 + 和/分别改成了 - 和_,这样就免去了在 URL 编解码和数据库存储时所要做的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。

window.btoa/window.atob base64 编码 (binary to ascii) 和解码仅支持 Latin1 字符集。

2.2 JS 转义字符

js 字符字符串中包含一些反斜杠开头的特殊转义字符,用来表示非打印符、其他用途的字符还可以转义表示 unicode、Latin1 字符。

转义字符 含义
\’ 单引号
\” 双引号
& 和号
\ 反斜杠
\n 换行符
\r 回车符
\t 制表符
\b 退格符
\f 换页符
\n … \nnn 由一位到三位八进制数(1 到 377)指定的 Latin-1 字符
\xnn 以 16 进制 nn(n:0~F) 表示一个 Latin1 字符。\x41 表示字符 A
\unnnn 以 16 进制 nnnn(n:0~F) 表示一个 Unicode 字符。只限于码点在\u0000~\uFFFF 范围内
\u{n} … \u{nnnnnn} Unicode 码点值表示一个 Unicode 字符

特别注意:

  1. 换行符\n 在 innerHTML 使用只会展示一个空格并不会换行。

  2. 通过\n、\u 和\x 可以代表任意 unicode 字符和 Latin1 字符。通过这个可以对 js 加密保证 js 安全和进行隐蔽攻击。

例子:

function toUnicode(theString) { //字符串转换为 unicode 编码字符串,切记这个字符串是复制用的,不是让你拿来直接执行的。
var unicodeString = '';
for (var i = 0; i < theString.length; i++) {
var theUnicode = theString.charCodeAt(i).toString(16).toUpperCase();
while (theUnicode.length < 4) {
theUnicode = '0' + theUnicode;
}
theUnicode = '\u' + theUnicode;
unicodeString += theUnicode;
}
return unicodeString;
}
var xssStr = "alert('xss')";
var xssStrUnicode = toUnicode(xssStr);
//输出:"\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"
eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"); //弹出 xss 弹窗

2.3 URL 编码

RFC 1738 做出规定” 只有字母和数字 [0-9a-zA-Z]、一些特殊符号”$-_.+!*’(),”[不包括双引号]、以及某些保留字,才可以不经过编码直接用于 URL”。所以当链接中包含中文或者其他不符合规定的字符的时候都需要经过编码的。然而由于浏览器厂商众多,对 url 进行编码的形式多种多样,如果不对编码进行统一处理,会对代码开发造成很大的影响,出现乱码现象。

URL 编码规则:需要编码的字符转换为 UTF-8 编码,然后在每个字节前面加上%。

例如:

'牛'-->UTF-8 编码 E7899B-->URL 编码是%E7%89%9B

JS 为我们提供了 3 个对字符串进行 URL 编码的方法:escape ,encodeURI,encodeURIComponent

escape:由于 eccape 已经被建议放弃所以大家就不要用了

encodeURI:encodeURI 不编码的 82 个字符:!#$&’()*+,/:;=?@-._~0-9a-zA-Z,从中可以看不会对 url 中的保留字符进行编码,所以适合 url 整体编码

encodeURIComponent:这个对于我们来说是最有用的一个编码函数,encodeURIComponent 不编码的字符有 71 个:!, ‘,(,),*,-,.,_,~,0-9,a-z,A-Z。

可以看出对 url 中的保留字进行的编码,所以当传递的参数中

包含这些 url 中的保留字(@,&,=),就可以通过这个方法编码后传输

这三个方法对应的解码方法: unescape、decodeURI、decodeURIComponent

2.4 HTML 字符实体

HTML 中的预留字符必须被替换为字符实体。这样才能当成字符展示,否则会当成 HTML 解析。

字符实体编码规则:转义字符 = &#+ascii 码; = &实体名称;

XSS 字符串需要防御字符的实体转换表:

转化方法:

function encodeHTML (a) {

return String(a)

.replace(/&/g, "&")

.replace(/</g, "<")

.replace(/>/g, ">")

.replace(/"/g, """)

.replace(/'/g, "'");

};

2.5 页面编码

页面编码设置:


脚本编码设置:

注意:要想 JS 即可在 UTF-8 中正常使用又可以在 GBK 中正常使用,可以对 JS 中所有包含中文的字符串做字符转义。

例子:

alert("网络错误"); //弹出网络错误

alert("\u7f51\u7edc\u9519\u8bef"); //弹出网络错误

3. Payload 的分类

现在可以认识 Payload 的了,我不得不说这里对 Payload 的分类可以很好的让你认识 Payload。也帮助你更好的对应到执行点。

3.1 原子 Payload

最低层级的 Payload。

javascript 代码片段

可在 eval、setTimeout、setInterval 中直接执行,也可通过 HTML 等构成高阶 Payload

javascript:javascript 伪协议

结构:javascript:+js 代码。可以在 a 标签的 href 属性被点击和 window.location.href 赋值的时候执行。

DATA URI 协议

DATA URI 结构:data:[][;base64], 。DATA URI 数据在包含在 iframe 的 src 属性和 object data 属性中将会变成可执行的 Payload.

字符串转义变种 javascript 代码片段

unicode 或者 Latin-1 表示字符串。

eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u002"); //可执行的 JS

3.2 纯 HTMLPayload

这种 Payload 特点不具有可执行的 JS,但是存在传播风险,可以把别的站点注入到被攻击网站。

包含链接跳转的 HTML 片段

主要是传播危害

哈哈,我来钓鱼了

3.3 包含原子 Payload 的 HTML 片段 Payload

script 标签片段

script 标签片段这种 Payload 可以引入外部 JS 或者可直接执行的 script。这种 Payload 一般不能通过直接复制给 innerHTML 执行,不过在 IE 上可以。不过通过 document.write 是可以执行。

例子:

// Payload 原始值:data:text/html,

var inputStr ="

//var inputStr = '

xssDom.innerHTML = inputStr; //弹出 alert("xss")

这里只是介绍了主要的 Payload,还有很多不常见的 Payload。

第四部分:XSS 攻击模型分析

这部分我们根据漏洞攻击模型分析一下 XSS 的执行点和注入点。分析这两点其实就是找漏洞的过程。

1. XSS 漏洞执行点

  1. 页面直出 Dom

  2. 客户端跳转链接: location.href / location.replace() / location.assign()

  3. 取值写入页面:innerHTML、document.write 及各种变种。这里主要会写入携带可执行 Payload 的 HTML 片段。

  4. 脚本动态执行:eval、setTimeout()、setInterval()

  5. 不安全属性设置:setAttribute。不安全属性前面见过:a 标签的 href、iframe 的 src、object 的 data

  6. HTML5 postMessage 来自不安全域名的数据。

  7. 有缺陷的第三方库。

2. XSS 漏洞注入点

看看我们可以在哪些位置注入我们的 Payload

  1. 服务端返回数据

  2. 用户输入的数据

  3. 链接参数:window.location 对象三个属性 href、search、search

  4. 客户端存储:cookie、localStorage、sessionStorage

  5. 跨域调用:postMessage 数据、Referer、window.name

上面内容基本包含了所有的执行点和注入点。对大家进行 XSS 漏洞攻防很有帮助。

第五部分 XSS 攻击防御策略

1. 腾讯内部公共安全防御及应急响应

  1. 接入公共的 DOM XSS 防御 JS

  2. 内部漏洞扫描系统扫描

  3. 腾讯安全应急响应中心:安全工作者可以通过这个平台提交腾讯相关的漏洞,并根据漏洞评级获得奖励。

  4. 重大故障应急响应制度。

2. 安全编码

2.1 执行点防御方法

执行点 防御
页面直出 Dom 服务端 XSS 过滤
客户端跳转链接 域名白名单(例如:只允许 qq.com 域)、链接地址 XSS 过滤
取值写入页面 客户端 XSS 过滤
脚本动态执行 确保执行 Js 字符串来源可信
不安全属性设置 内容 XSS 过滤,包含链接同客户端跳转链接
HTML5 postMessage origin 限制来源
有缺陷的第三方库 不使用

2.2 其他安全防御手段

  1. 对于 Cookie 使用 httpOnly

  2. 在 HTTP Header 中使用 Content Security Policy

3. 代码审查

总结 XSS 检查表做代码自测和检视

4. 自动化检测 XSS 漏洞的工具

手工检测 XSS 漏洞是一件比较费时间的事情,我们能不能写一套自动检测 XSS 自动检测工具。竟然我知道了注入点、执行点、Payload 自动化过程是完全有可能的。

XSS 自动化检测的难点就在于 DOM 型 XSS 的检测。因为前端 JS 复杂性较高,包括静态代码分析、动态执行分析都不容易等。

第六部分 总结

上面内容文字比较多,看完还是很累的,总结起来就一句话:安全大于一切,不要心存侥幸,希望以上内容对您有帮助,不过以上内容仅代表个人理解,如有不对欢迎指正讨论。

关于 WeTest 安全防护

腾讯 WeTest 安全团队在安全领域经历了多年的探索和技术沉淀,覆盖了腾讯所有手游以及大量应用, 打造出了业界领先的安全测试技术方案。

为了能够让更多的用户体验到安全服务,目前手游与应用的 “安全扫描” 服务,限时免费中!详情请登录腾讯 WeTest 官方网站(wetest.qq.com)。


目前腾讯 WeTest 更有各类折扣优惠提供,点击:https://wj.qq.com/s/2689175/cd9b/ 填写问卷,即可获取折扣优惠。

如果使用当中有任何疑问,欢迎联系腾讯 WeTest 企业 QQ:2852350015


↙↙↙阅读原文可查看相关链接,并与作者交流