LinkVortex Write-Up - HTB
Recon
Starting off with nmap:
PORTS=$(grep "open" all_syn.txt | awk -F'/' '{print $1}' | tr '\n' ',' | sed 's/,$//'); sudo nmap -sVC -p $PORTS -Pn -n 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-server-header: Apache
|_http-title: Did not follow redirect to http://linkvortex.htb/
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Navigated to the webapp, didn't find anything special.
Running directory and vhost scanning:
VHOST:
gobuster vhost -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://linkvortex.htb -t 100 --exclude-length 226

Directory:
gobuster dir -u http://linkvortex.htb -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt --exclude-length 0 -t 100
Nothing special neither...
So I quickly check the source of the page:

Ghost 5.58

BruteForcing:
Found admin portal:

Great finding:


SQLi:
And trying SQLi:

I tried brute-forcing with custom CeWL list and SQLmap, but I get blacklisted.
I started inspecting the source files and the API endpoints of Ghost CMS better...
<script defer src="https://cdn.jsdelivr.net/ghost/sodo-search@~1.1/umd/sodo-search.min.js" data-key="054f7096476b0e8c7ec591c72c" data-styles="https://cdn.jsdelivr.net/ghost/sodo-search@~1.1/umd/main.css" data-sodo-search="http://linkvortex.htb/" crossorigin="anonymous"></script>
That's the key for the content api, though we can't access the admin api with it..
Going back...:
I must do something wrong. I went back and realized I fkd the vhost command, I added --append-domain now and found:
dev.linkvortex.htb
Ran some sub-directory brute-forcing, and found:
http://dev.linkvortex.htb/.git/
Used git-dumper to dump it.
There are quite some files inside:

Let's do some credential hunting:
find . -type f -exec grep -l "password" {} \;
Many files! Let's use AI, so I will:
grep -r -i "password" /home/czr/HTB/LinkVortex/git-dumper/
And feed the enormous output to AI.
File: /extracted-repo/ghost/core/test/utils/fixtures/data-generator.js
// Password = Sl1m3rson99
password: 'Sl1m3rson99',
This appears frequently as a test user password throughout the codebase.
2. Hashed Passwords for Test Users
File: /extracted-repo/ghost/core/test/regression/api/content/authors.test.js & /posts.test.js
const hashedPassword = '$2a$10$FxFlCsNBgXw42cBj0l1GFu39jffibqTqyAGBz7uCLwetYAdBYJEe6';
3. Database Connection Password in CI Workflow
File: /extracted-repo/.github/workflows/ci.yml
mysql root password: 'root'
4. MySQL Authentication in CI
File: /extracted-repo/.github/workflows/ci.yml
authentication plugin: 'caching_sha2_password'
5. Test User "Bad Password" in Regression Tests
File: /extracted-repo/ghost/core/test/regression/api/admin/authentication.test.js
const password = 'OctopiFociPilfer45';
6. Client Certificate Password in Git Dumper Script
File: /home/czr/HTB/LinkVortex/git-dumper/git_dumper.py
client_cert_p12_password=None
This appears to be a parameter in the git_dumper.py script you're using, rather than an actual credential.
7. Various Generated Passwords in Tests
File: /extracted-repo/ghost/core/test/e2e-api/admin/users.test.js
newPassword: '1234abcde!!',
Foothold
Found a match - admin@linkvortex.htb:OctopiFociPilfer45
Honestly, this is the worst box ever! It's super random, feels like there is no intended path. Even this password is in a "test" folder.
Using: https://github.com/godylockz/CVE-2023-40028/tree/main

It seems that we are running in a docker container...
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"
}
}
}
}
We got the creds:
"user": "bob@linkvortex.htb",
"pass": "fibber-talented-worth"

PrivEsc
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
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
Super basic. We will nest 2 symlinks...

Not so cool box, but nice enumeration.
Last updated