0%

[美团CTF2021]wp

[美团CTF2021]wp

四个web,第一题被队友秒了,第二个不会对着复现了,第三个cms找到了可能的源码但是审不出来什么东西,第四个xxe但是没打通

看vn的wp,太牛逼了,四个大一大二的打到第一,估计是一个人就ak了web,我太垃圾了

web1

SQL注入,ban了select,binary大小写区分直接if判断当前表下面的password字段注出来

web2

有前台和后台,前台登录在密码处没过滤引号,后台过滤拉满
前台注入得到admin的password,登录后台
后台有注释admin.rar是源码(一开始没看见测了半天上传。。。。)
源码一个preload.php可以写文件但是超级过滤,upload.php可以上传但是没什么用,cli.php也不知道有什么用,hack.php用来写文件

贴一下preload.php,关键逻辑就这段

<?php
error_reporting(0);
session_save_path('session');
session_start();
class preload{
    public $class;
    public $contents;
    public $method;
    public function __construct(){
        $this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
        $this->contents="new hacker();";
        $this->method="\$hack->hack();";
    }
    public function waf($parm){
        $blacklist="/flag|pcntl|system|exec|fread|file|fpassthru|popen|proc|ld|putenv|passthru|`|\.|\\\|#|\\$|[0-9]|_|get|~|\\^|eval|assert|open|write|include|require/is";
        return preg_match($blacklist,$parm);
    }
    public function write(){
        if($this->waf($this->contents)||strlen($this->contents)>60||preg_match_all('/\\(/i',$this->contents,$matches)>2||preg_match_all('/\\)/i',$this->contents,$matches)>2){
            die("<br>"."no no no");
        }
        if(preg_match_all('/;/i',$this->contents,$matches)>2){
            die("<br>"."try hard");
        }
        if(file_exists(dirname(__FILE__)."/hack.php")){
            unlink(dirname(__FILE__)."/hack.php");
        }
        file_put_contents(dirname(__FILE__)."/hack.php",$this->class);
        file_put_contents(dirname(__FILE__)."/hack.php",$this->contents,FILE_APPEND);
        file_put_contents(dirname(__FILE__)."/hack.php",$this->method,FILE_APPEND);
    }
    public function __wakeup(){
        $this->class="<?php class hacker{public function hack(){echo 'hack the hack!I believe you can!';}}\$hack=";
        $this->method="\$hack->hack();";
    }
    public function __destruct(){
        $this->write();
    }
}
$a=$_POST['a'];
unserialize($a);
$preload=new preload();

?>
<a href="./hack.php">hack.php</a>
<a href="./cli.php">cli.php</a>

wakeup函数控制了class和method,waf限制了字段,且content长度不能超过60,分号和括号对不能超过两个,POST数据反序列化后还会new一个preload,new出来的preload完全不可控,且析构时写入hack.php,一开始还以为是条件竞争。。。

但是还有一个可疑的地方,admin.php是这个样子的

<?php
error_reporting(0);
session_save_path('session');
ini_set('session.serialize_handler', 'php_serialize');
session_start();
if($_SESSION['login']!=1){
    die("<script>window.location.href='./index.php'</script>");
}
if($_POST['sign']){
    if(preg_match('/\\|[OC]:\d+:/i', $_POST['sign'])){
        die("hacker");
    }
    $_SESSION['sign']=$_POST['sign'];
    echo "your sign is: ".$_SESSION['sign']."<br>";
}else{
    echo "please set your sign"."<br>";
}
?>
<a href="./preload.php">write a hack.php</a>
<a href="./upload.php">upload something</a>

其设定了ini_set('session.serialize_handler', 'php_serialize');,而preload中没有设置,那么其使用的session序列化handler就应该是默认的php,且我们还可以控制session的内容~
waf还是个特别直接的|O:1:这种显然是序列化数据类型的过滤。真有意思

打一个session反序列化写文件。

之前已经忘了session反序列化的操作了,一开始还以为和之前那种反序列化逃逸一样要把数据构造的好看一点,没想到php作为handler的时候直接就把|后面内容作为值取出来进行反序列化,只要|后面取出来完整就行,前后还有其他的数据都不管了
session反序列化在session_start的时候开始,而在整个脚本处理结束后析构,所以session反序列化也不会受到之前那个new出来的preload的影响。(一开始还想着靠POST的反序列化进die来阻止后面的new preload的创建)
但是直接塞一个对象好像跑不通?后来乱试试出来把object塞到一个数组里面就又能跑通了,原理不明
塞到数组里也直接过waf了诶。。。

然后就是过preload的waf,这个waf真实超级拉满。。。先扫目录a;echo serialize(scandir('/'));用序列化把数组给输出出来(还挺巧妙的)
flag就在根目录,但是flag这个词ban了,读文件的函数也被通杀完了。。。waf超狠,最后翻出来一个copy函数打通a;copy(strrev('galf/'), '/var/www/html/admin/upload/a');
访问一下hack.php触发然后去upload目录拿flag

踩坑

一开始我居然天真的以为wakeup的绕过是通杀的,只要绕过了wakeup之后想写什么写什么。。。后来才发现对PHP高版本wakeup绕过会直接导致反序列化失败。。。完全不行
反序列化的数据控制的很严格,一开始我还以为session的那个O:1:这种过滤能直接加空格绕过。。结果完全不行嗷,但是可以用加号O:+1:绕过,这种东西居然能百度到,真不错

看wp

我这边条件竞争是没有过那个new的preload的,看了眼vn的wp,直接条件竞争就过了,真牛逼

web3

扫目录扫出来h1nt.php,访问给了db文件路径,能直接访问下载(为什么db文件会和web服务在一个目录下。。。)
然后直接在db文件里找到admin的账户密码,账号是admin888,密码是md5还原一下为attack
登进去是个后台,找到了可能用的框架叫onenav,拉下来源码感觉魔改了一些,审不出来什么东西

看wp

有一个class/showImage.php?file=,这个好像当初也发现了但是发现web4是个xxe之后就全去打web4了。。。
有过滤,但是php://filter会二次解码直接绕过过滤,读到一堆源码之后按照提示审计就能RCE(但是我现在也搞不到源码审不了了呜呜)

web4

xxe,过滤了SYSTEM,用utf16编码打了一遍没打通。不会了,对xxe的认识一直停留在复制粘贴payload环节

看wp

能够用PUBLIC来代替SYSTEM做同样的操作,真玄幻
去查奇怪的文献,得到如下内容

There are two types of external entities: private, and public. Private external entities are identified by the keyword SYSTEM, and are intended for use by a single author or group of authors. Public external entities are identified by the keyword PUBLIC and are intended for broad use.

简单的意思就是SYSTEM声明一个私有外部实体,PUBLIC声明一个公有外部实体,不过PUBLIC在写payload之前还要多谢一个实体名称,用双引号括起来,这点算是新学的了

然后需要ssrf打admin.php,但是不能同时出现httpdtd|ENTITY字样,不能用ENTITY就得引用远程的dtd文件,但是dtd也不允许出现,这里说后缀是.d或者.dtd都行(暂时没找到对应的资料,不太行,本地复现的时候连导入外部实体都成问题。。。

后面是一个phar反序列化打一个匿名函数,有源码应该就好打了呜呜