php代码注入漏洞PHP中的魔法函数反序列化漏洞原理(2)_php注入代码
2022-04-26
序列化和反序列化简介
() 将对象转换为字符串, () 将字符串恢复为对象。在PHP应用中,序列化和反序列化一般用于缓存,比如缓存等。简单来说,序列化就是将一个对象转换成可以传输的字符串,而反序列化就是用一个对象替换原来的字符。
简单示例
php
class test{
public $suifeng="shuai";
}
$a=new test(); //实例化一个对象
$b=serialize($a); //进行序列化
echo $b; //输出序列化后的字符串
echo '
';
echo "我是分割线";
$c=unserialize($b); //把序列化后的字符串反序列化
echo '
';
echo $c->suifeng;
?>
下面我们来看看反序列化后输出字符的含义
第一个输出是
O:4:"test":1:{s:7:"";s:5:"";}
O->object
4->object的长度
test->object的名称
1->object中变量个数
s->变量名数据类型
7->变量名长度
suifeng->变量名
S->变量值数据类型
5->变量值长度
shuai->变量的值
PHP 其他数据类型
a - array
b - boolean
d - double
i - integer
o - common object
r - reference
s - string
C - custom object
O - class
N - null
R - pointer reference
U - unicode string
PHP 常用魔法函数
() //创建对象时调用
() //在对象被销毁之前调用
() //调用类中不存在的方法时执行
() //调用类中不存在的静态方法方法时执行。
() //反序列化后会立即调用
() //在对象序列化之前调用
() //对象作为字符串时调用
() //用于从不可访问的属性中读取数据
() //用于将数据写入不可访问的属性
() //调用函数的方式,调用对象时的响应方式
() // 在不可访问的属性上调用 () 或 () 来触发
() // 当 () 用于不可访问的属性时触发
,, 序列化对象的区别
php v7.x在反序列化时对访问类型不敏感
变量
直接变量名反序列化
变量
\x00 + * + \x00 + 变量名
您可以使用 S:5:"\00*\00op" 代替 s:5:"?*?op"
变量
\x00 + 类名 + \x00 + 变量名
反序列化漏洞的条件
1、函数的参数可控
2、后台使用PHP中对应的魔术函数
反序列化漏洞原理
让我们先运行下面的代码
';
}
function __destruct(){
echo '调用了析构函数
';
}
function __wakeup(){
echo '调用了苏醒函数
';
}
}
echo '创建对象a
';
$a=new ABC;
echo '序列化
';
$a_ser=serialize($a);
echo '反序列化
';
$a_unser=unserialize($a_ser);
echo '对象快死了!';
?>
PHP语言本身的漏洞
还有一个反序列化漏洞是由于PHP语言本身的一个漏洞遇到了某些特征而导致的
示例:导致失败 (CVE-2016-7124)
php版本
在序列化字符串中,如果代表对象属性个数的值大于实际属性个数,则跳过()的执行
序列化问题
当 () 被调用或 .在php.ini中为1,PHP内部调用会话管理器,将访问用户序列化后存放在指定目录下(默认为/tmp)。
PHP中共有三种序列化处理器,如下:
处理器
对应的存储格式
php
键名+竖线+()函数反序列化的值
键名长度+键名+()函数反序列化的值对应的字符
(php>=5.5.4)
()函数反序列化处理的数组
配置文件php.ini包含这些与存储配置相关的配置项:
.="" -- 设置的存储路径,默认为/tmp
。 --指定会话模块是否在请求开始时启动会话网站开发,默认为0不启动
。 --定义用于序列化/反序列化的处理程序的名称。默认使用php
.="" --设置用户自定义存储函数,如果要使用PHP内置的存储机制php代码注入漏洞,可以使用这个函数(数据库等),比如存储为文件默认情况下
并且PHP中默认使用PHP引擎。如果我们要修改到另一个引擎,我们需要添加代码('.', 'The to be set'),例如:
;
:即元数据、压缩文件的属性等信息,以序列化方式存储
:压缩文件的内容
:签名,放在文件末尾
这里有两个关键点,一个是文件标识符,必须以();?>结尾,但是对前面的内容没有限制,也就是说我们可以很容易的伪造一个图片文件或者其他文件来绕过一些上传限制;二是反序列化。存储在phar中的元数据信息是以序列化的方式存储的。当文件操作函数通过phar://伪协议解析phar文件时,数据会被反序列化,这样的文件操作函数有很多
先决条件
Phar.=Off 在 php.ini 中设置
php >=5.3.0
演示测试
根据文件结构,我们自己构建一个phar文件。 PHP 有一个内置的 Phar 类来处理相关操作
startBuffering();
$phar->setStub(""); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>
很明显是以序列化的形式存储的
如果我们现在通过 phar:// 包装器对现有的 Phar 文件执行文件操作,它的序列化元数据将被反序列化。这意味着我们在元数据中注入的对象被加载到应用程序的范围内。如果这个应用程序有一个命名类并且定义了魔法方法() 或 has(),这些方法将被自动调用。这意味着我们可以在我们的代码库中触发任何析构函数或唤醒方法。更糟糕的是,如果这些方法对我们注入的数据进行操作,这可能会导致更多漏洞。
以下是受影响的功能列表
此时可以使用phar://协议
使用条件
可以上传phar文件
文件操作函数的参数可控,不过滤:,/phar等特殊字符
有一些魔术方法可用作“跳板”
反序列化字符转义
当 PHP 反序列化时,底层代码使用 ;作为字段分隔符,}作为结尾(字符串除外),根据长度判断内容。同时,反序列化过程必须严格按照序列化过程进行。成功实现反序列化的规则。
我们来分析一段代码
';
echo $c->suifeng;
?>
发现序列化为O:4:"test":1:{s:7:"";s:5:"";},反序列化也正常进行。当我们修改序列化结果为 O:4:"test":1:{s:7:"";s:5:"";}i:1;s:4:"test";正常解析。
但是如果我们修改它的长度,就会报错,比如O:4:"test":1:{s:7:"";s:4:"";}
知道了这个特性,下面我们来分析一下代码
可以看到反序列化为a:2:{i:0;s:5:"";i:1;s:4:"1234";},当我们修改参数为 进程首先反序列化$user,然后执行函数中的函数,用它替换test,导致长度不一致,最终导致反序列化失败。
a:2:{i:0;s:9:"";i:1;s:4:"1234";}
a:2:{i:0;s:9:"";i:1;s:4:"1234";}
假设这个代码流是创建账户的代码流,此时$可以被用户控制,那么我们可以通过控制可控参数使反序列化字符转义。它的本质其实和sql注入一样,双引号和大括号的闭合,但是反序列化字符的转义需要满足其特性的一些条件。接下来我们构建它。
因为它是用 ; 严格分隔的作为字段并以 } 结尾(字符串除外),我们可以这样关闭它。
可以看到我们构造了$=";i:1;s:6:"";},它被序列化为a:2:{i:0;s:29:"";i: 1; s:6:"";}";i:1;s:4:"1234";},经过这个序列化后,我们会反序列化它,红色部分不会进入反序列化。但是可以看到替换后反序列化还是没有成功,我们来分析一下。
替换后我们得到
a:2:{i:0;s:29:"";i:1;s:6:"";}";i:1;s:4:"1234";},红色部分is 反序列化时会被忽略,要反序列化的字段是
a:2:{i:0;s:29:"";i:1;s:6:"";}
可以看到这里的 s:29:"" 显然是错误的,所以我们的反序列化会失败,那么我们如何保持正确呢?这是我们在反序列化字符转义特征时需要考虑的。东西。
在 ('test','',$) 代码中,test 被替换为比前一个 test 多一个字符,所以只要我们添加足够多的 test 来替换它相同的长度,这将允许我们的反序列化正常进行。
我们构建
";i:1;s:6:"";}
这样php代码注入漏洞网站开发,我们修改了零钱业务的密码