0x01-XSS基础实战-XSSGame 6集全

目录

这是什么?

XSS 系列开篇。WebApp 渗透测试是一个大话题,之前的 SQL 系列之后还会继续深入。本篇的 XSS 系列也一样,在后续的工作学习中,不断记录。

我不再讲 XSS 的基本原理,网上有太多的教程。

最权威的学习资料,列举在下面:

OWASP,Rapid7,Protswigger(Burp Suite)三大家。

另外,W3School 是必备的学习资料

如果对于 XSS 的基本概念不太熟悉,建议先阅读这三篇文章,再继续下面的内容。

今日目标

今天,我们一起把 XSS Game 这个有 6 个等级的 XSS 实战小教程做完。理论结合实践,是最快的学习方式。

所有的 6 个等级,都要求我们使用 XSS 让浏览器弹出一个 alert,即可过关。

在开始之前,先推荐两个个 JSBin HTML JS 线上测试工具JSFiddle。在学习的过程中,如果有代码需要测试,可以很方便地在两个工具上运行。

例如你知道了一个输入框中的用户输入会被直接嵌入到 <strong></strong> 标签中,你想确认在 <strong> 标签中,<script> 是否能被成功执行。那就可以到 JSBin 做个小测试。

在这里插入图片描述

另外,HTML URL Online Encoder Decoder 也是非常有用的工具,可以将需要在地址栏注入的脚本做 url encode。

最后再推荐两个 XSS Payload Cheet Sheet,OWASP XSS Cheet SheetXSS Cheet Sheet From Github

Level 1

Level 1 的页面是这样的。

在这里插入图片描述

XSS Game 模拟了一个浏览器在页面中央。页面底部的 toggle,点击可以查看页面的源代码。

在这里插入图片描述

点击 show,可以显示提示信息。

在这里插入图片描述

Level 1,展示了最基本的反射型 XSS 攻击。这里,有两种方式可以判断开发者有没有对用户输入做校验。

第一种方式,看源码。

在这里插入图片描述

可以看到,没有任何的用户输入校验。后端代码从 request 中获取 query 参数,然后直接拼接到 html <b> tag 中间。

另一种方法,输入测试。

对于网页应用来说,最最敏感的字符,就是两个尖括号,<>。所以我们可以直接输入一个 <test 测试以下是否有过滤机制。

在这里插入图片描述

有点奇怪的结果,但是我觉得这是浏览器的行为,跟环境没有关系。我的浏览器也是 Chrome,可以做个小测试。

我写了一个简单的 html 页面。

页面模拟了Level 1 的情况,在 <b> 标签中间,加上了 <test 作为用户输入。

在这里插入图片描述

我们启动一个 http server。

在这里插入图片描述

我在 Windows WSL 下运行的 Ubuntu,它的 IP 是 192.168.1.108。用浏览其访问该测试页面。

可以看到一样的情况。

在这里插入图片描述

换成其他标签也一样。

在这里插入图片描述

无论怎么说,可以肯定的是,后端没有做用户输入的校验,导致我们可以直接使用 <script> 注入 javascript。

因此,在搜索框中输入:

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

或者在 url 中(?query=)输入:

# html url encode
%3Cscript%3Ealert%281%29%3B%3C%2Fscript%3E

即可。

在这里插入图片描述

Level 2

Level 2 是典型的持久型 XSS。

同样的步骤,我们看一下源码。

存储评论的时候,没有做任何输入校验,直接将用户输入存储到数据库。

在这里插入图片描述

显示评论的时候,同样没有做用户输入的校验处理,直接将用户输入拼接到 DOM。

在这里插入图片描述

根据前一 Level 的经验,我们也输入一个 <script>alert(1);</script> 就可以了。但是这里不行。

innerHTML

innerHTML 可以设置 DOM 树中的节点的内容。

var ele = document.getElementById('id');

ele.innerHTML = 'this is not an attack';

innerHTML的安全问题

innerHTML 将字符串解析为 HTML,意思是,如果字符串中有脚本,浏览器就有可能会执行。

为什么 <script> 在 innerHTML 中无法执行,我们看一下文档。

根据 Mozilla 官方的文档,规定 <script> 在 innerHTML 中是无法执行的,仅此而已。

在这里插入图片描述

同一篇文档中,Mozilla 也给出了我们需要的答案,我们仍旧可以使用 事件(Event) 来触发 javascript 脚本。

在这里插入图片描述

Event

Event,也称 DOM Event,用来通知开发者一些特定的操作或者事件被触发了。比如,onmouseover 事件会在鼠标移入的时候被触发,onload 事件会在页面或者节点被加载的时候被触发,即将使用的 onerror 在有错误发生的时候被触发。

Mozilla 官方的文档十分详细

事件被添加到节点上之后,根据事件性质的不同,会在不同的情况下被触发。

如 Mozilla 官方文档所说,在 Level 2 的情况下,我们只需要使用一个事件,就可以触发 alert 弹窗。

在评论区输入:

<img src=# onerror="javascript:alert(1);" />

# 或者
<img src=# onerror="alert(1);" />

# 或者,连分号都不用
<img src=# onerror="alert(1)" />

持久型的 XSS 意味着每当你访问这个页面,弹窗都会执行。可以尝试刷新以下页面,弹窗会再次出现。

Level 3

看源码找漏洞。

location.hash

图片来自 Mozilla Location 官方文档

在这里插入图片描述

这个 hash 大家都见到过,它就像一个面包屑一样,指出你正在阅读的当前页面上的那一部分内容。

比如大家到我的个人博客,阅读 从头开始写操作系统-启动扇区与内存的关系及内存寻址的应用 一文,你随便点一个目录,就会在 URL 后面看到一个 hash。

在这里插入图片描述

hash 是 Location 对象的一个属性,代表的,就是上图例子中的 #boosecbyte 部分。

我们结合 Level 3 的源码看一下。

Level 3 中,chooseTab 方法接收 location.hash.substr(1) 作为参数,也就是上 #exampleexample 的部分。

在这里插入图片描述

之后,这个 example ,被直接拼接到了 html 中,然后 jQuery 将 html 设置给 tabContent 节点来展示。

在这里插入图片描述

没有看到任何用户输入的校验。

我首先尝试了以下直接使用 <script> 进行注入,但是没有效果,说明还是要用事件来做。

有了 Level 2 的经验,我们只需要构造一个一样的 <img> tag,在错误发生的时候,执行弹窗即可。

在 URL 的 # 之后输入

whatever.jpg' onerror='alert(1)'/>

即可。

Level 4

看源码。

后端在获取 timer 参数的时候,没有做任何字符校验,获取之后,直接传递给 timer.html 渲染。

在这里插入图片描述

timer.html 中,onload 事件触发后直接将后端获取的 timer 参数的值传递给 startTimer() 方法,作为参数。

在这里插入图片描述

onload 事件,会在图片加载完成的时候调用。所以,可以在 onload 事件中,构造一个 XSS 注入。

我们需要做的是构造一个输入,补全 startTimer(' 部分,然后注入 XSS 代码,例如 onload="startTimer('3'); alert('1');"

黑体加粗的部分就是我们需要构造的输入。

在输入框输入

3'); alert('1

# 或者
3')+alert('1

或者用上面的 HTML URL Encoder Decoder 工具,编码之后,在地址栏输入

?timer=3%27%29%3B+alert%28%271

# 或者
?timer=3%27%29%2Balert%28%271

即可。

Level 5

我们点击 sign up 跳转到注册页面。

看源码,注册页面选的时候,直接使用后端获取的 next 参数,没有任何字符校验和过滤。

在这里插入图片描述

我们输入了 email,点击 Next >> 的时候,实际上是点了一个 <a> 链接,href 属性是上图中获取的 next 参数的值。

在这里插入图片描述

因此,直接在浏览器地址栏输入

javascript:alert(1);

在这里插入图片描述

点击 Next >> 即可。

Level 6

最后 Level 6,还是对 location.hash 进行利用。

跟 Level 3 类似,在 getGadgetName 方法中,获取 # 之后的部分,如果没有用户输入,默认为 /static/gadget.js

在这里插入图片描述

接着,获取到的内容传递给 includeGadget() 方法最为参数。

在这里插入图片描述

includeGadget() 方法中,首先创建了一个 script DOM 节点,接着,上一步获取的 # 之后的的部分,作为 script 节点的 src 属性。

在这里插入图片描述

最后,这个 script 节点被添加为 head 的子节点。

在这里插入图片描述

以默认情况为例,创建的 script 节点为

<script src="/static/gadget.js" />

因此,我们要传递一个恶意的 js 文件的路径,输入到 URL 上 # 的后面,替换掉 /static/gadget.js 即可。

自己没有服务器的同学,可以使用 google 的 callback jsapi。

另外,还有一个点需要注意,如果直接输入地址栏中复制出来的带有 https 或者 http 的 URL,是会报错的。

在这里插入图片描述

因为代码中(见前文第 21行 代码)对 httphttps 进行了过滤,所以,有两种解决方案。

  • 不要协议部分
  • 将协议部分全部大写或者随意大写一个字符

Protocol-relative URLs

在 html 中使用 URL 可以不需要协议名的部分。例如,<a href="https://www.google.com" />,可以写成 <a href="//www.google.com" />。根据 wiki 所述,这样的使用方式叫 Protocol-relative URLs,对目标地址的访问会使用当前页面的协议。

在这里插入图片描述

因此,在地址栏输入

//www.google.com/jsapi?callback=alert

# 或者
httpS://www.google.com/jsapi?callback=alert

即可。

Data URLs

还有另外一种方式,使用 Data URLs

Data URLs 是一种协议,可以让开发者在文档中嵌入文件。

Data URLs 由 4 部分组成:

  • data:,前缀
  • mediatype,数据类型
  • base64 token,非必要,用于嵌入 base64 编码的二进制
  • data,数据本身

详情见 MDN

我先在我本机上做了测试,XSS 脚本不只是在 head 节点中可用,添加到任意节点中均可执行。

在这里插入图片描述

在这里插入图片描述

所以,在地址栏输入

data:text/plain;charset=utf-8,alert%281%29

同样可以弹窗。

总结

  • 最基本的 XSS 漏洞,可直接使用 <script>code</script>
  • <script> 无法执行的时候,可以选择使用事件来触发 XSS 脚本
  • location.hash 等页面地址相关的操作如果使用不当,也会造成 XSS 漏洞
  • Protocol-relative URLs 允许我们省略协议名,使用当前页面的协议访问目标地址
  • Data URLs 允许我们在 DOM 中嵌入并执行 XSS 脚本
  • 所有的 XSS 漏洞,都是由于没有对用户输入做校验和筛选造成的;所以,XSS 防御的第一步,就是对所有的用户输入进行校验,过滤


推荐阅读(参考链接):