Dog
Executive summary — Dog is an Easy Linux box built on a chain of failures that's extremely realistic and depressingly common in sloppy CMS deployments: an exposed
.gitrepository on the web server lets you reconstruct the full source of a Backdrop CMS install (a Drupal 7 fork). Inside that source sit the CMS's database credentials, which turn out to be reused as the system user's SSH password. Once in,sudo -lreveals full permission onbee, Backdrop's official admin CLI, whoseevalsubcommand runs arbitrary PHP — and since it runs viasudo, that's instant root. Path: Web → exposed .git → credentials → SSH → bee eval → Root.
| Platform | Hack The Box |
| Operating system | Linux |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.231.223 |
Attack map
[80] Backdrop CMS (fuzzing → /core /modules /themes /sites /files)
│ exposed .git/ → git-dumper → repository dump
▼
[CREDS] password in the CMS config → reused
▼
[SSH] johncusack (user.txt)
│ sudo -l → NOPASSWD /usr/local/bin/bee
▼
[BEE] bee eval "system('/bin/bash')" (as root)
▼
[ROOT]
1. Reconnaissance
We always start with a full port sweep, not just the top-1000: on web-focused "Easy" boxes the attack surface is often deliberately small, so confirming there are only two services already points the rest of the assessment toward HTTP.
nmap -sC -sV -p- 10.129.231.223
22/tcp open ssh OpenSSH
80/tcp open http Apache
Two open ports: SSH (likely the final destination of a foothold, not the entry point) and HTTP, which is where we focus.
Resumen ejecutivo — Dog es una máquina Linux Easy construida sobre una cadena de fallos muy realista y muy común en despliegues descuidados de CMS: un repositorio
.gitexpuesto en el servidor web permite reconstruir el código fuente completo de un Backdrop CMS (fork de Drupal 7). Dentro del código aparecen las credenciales de base de datos del CMS, que resultan estar reutilizadas como contraseña del usuario del sistema por SSH. Una vez dentro,sudo -lrevela permiso total sobrebee, la CLI oficial de administración de Backdrop, cuyo subcomandoevalejecuta PHP arbitrario — y al correr porsudo, eso es root directo. Recorrido: Web → .git expuesto → credenciales → SSH → bee eval → Root.
| Plataforma | Hack The Box |
| Sistema operativo | Linux |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.231.223 |
Mapa del ataque
[80] Backdrop CMS (fuzzing → /core /modules /themes /sites /files)
│ .git/ expuesto → git-dumper → volcado del repositorio
▼
[CREDS] contraseña en la config del CMS → reutilizada
▼
[SSH] johncusack (user.txt)
│ sudo -l → NOPASSWD /usr/local/bin/bee
▼
[BEE] bee eval "system('/bin/bash')" (como root)
▼
[ROOT]
1. Reconocimiento
Empezamos siempre por un barrido de puertos completo, no solo el top-1000: en máquinas «Easy» centradas en web es habitual que la superficie de ataque sea deliberadamente pequeña, así que confirmar que solo hay dos servicios ya orienta el resto de la auditoría hacia HTTP.
nmap -sC -sV -p- 10.129.231.223
22/tcp open ssh OpenSSH
80/tcp open http Apache
Dos puertos abiertos: SSH (probablemente el destino final de un foothold, no el punto de entrada) y HTTP, que es donde vamos a centrarnos.
2. Web enumeration
We browse the site and run directory fuzzing to map it out:
http://10.129.231.223/
wfuzz -c -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
--hc 404 http://10.129.231.223/FUZZ
/core
/modules
/themes
/sites
/files
Why this layout matters:
/core,/modules,/themesand/sitesare the classic directory layout of the Drupal family (and its forks). We don't yet know the exact CMS, but we already know which family it belongs to — which tells us where to look next (e.g.sites/default/settings.phpis where Drupal/Backdrop keep the database config).
3. Exposed .git repository
We check whether the version-control directory was left reachable over HTTP — a classic mistake when a deploy is done by git clone-ing straight into the server's DocumentRoot:
http://10.129.231.223/.git/
HTTP/1.1 200 OK
Content-Type: text/plain
Why it's serious: the
.gitdirectory doesn't just hold the current state of the code, it holds its entire history: objects, blobs and past commits. If a secret was ever committed and then "removed" in a later commit, that secret is still in the history. With read-only HTTP access to the directory (no directory listing, but the internal files themselves are served) it's possible to reconstruct the whole repository from the outside.
4. Dumping the repository
There's no directory listing at /.git/, but Git's internal object files (HEAD, index, objects/, refs/...) live at predictable paths. git-dumper exploits exactly that: it requests the known internal Git files one by one, discovers the object hashes they reference, and recursively downloads until it can reconstruct a complete, working .git.
git clone https://github.com/internetwache/GitTools.git
cd GitTools/Dumper
python3 git_dumper.py http://10.129.231.223/.git/ ~/dog_repo
With the repository reconstructed locally, a simple git log also lets us see the commit history and its authors — useful for spotting candidate system usernames.
5. Code analysis
With the full source now local, we grep it recursively for secrets:
grep -R -i "password" .
grep -R -i "database" .
Database credentials show up in the CMS configuration (sites/default/settings.php, the standard config file across the Drupal/Backdrop family). That database password later turns out to be the key to system access.
Why a CMS stores the DB password in plaintext inside a code file: that's the normal way Drupal/Backdrop resolve their MySQL connection — the problem isn't that the file exists, it's that it ended up tracked in Git, and that Git repo was exposed to the public.
6. CMS fingerprinting
The directory layout and the dumped code confirm Backdrop CMS, a Drupal 7 fork aimed at simplifying administration for small teams. The installed version is 1.27.1, already outdated at the time of this box.
Backdrop, like the rest of the Drupal family, lets an authenticated administrator install/enable modules — and a module is nothing more than PHP code the CMS executes. That means the admin panel of this CMS family is, in general, a code-execution primitive in itself once you have an admin session. This box doesn't require going that route: the leaked credential takes us straight in over SSH, so that alternate path is only mentioned as context.
7. SSH access
With candidate usernames (pulled from the repository's commit authors, e.g. johncusack) and the password leaked in settings.php, we try credential reuse against SSH — a mistake as simple as it is common: the same password serves both the CMS's database and the developer's system account.
ssh johncusack@10.129.231.223
johncusack@dog:~$
8. User flag
cat ~/user.txt
9. Privilege enumeration
As always right after landing, we check what the user can run as root:
sudo -l
User johncusack may run the following commands on dog:
(ALL : ALL) NOPASSWD: /usr/local/bin/bee
A single line, but decisive:
johncusackcan run/usr/local/bin/beeas any user (including root) and without a password. We still need to know exactly whatbeedoes to know if that's a road to root.
10. Analysing bee
bee is Backdrop CMS's official admin CLI (the equivalent of Drush in Drupal): it manages modules, themes, cache, users, and more from the command line. Among its subcommands are eval and php-script, meant to let an admin run PHP snippets inside the CMS's already-"bootstrapped" context — with access to its whole internal API.
This isn't a bug in
bee: it's by design. Any tool whose explicit job is to evaluate arbitrary code is, by definition, an interpreter running with the privileges of whoever invokes it. Allowing that viasudowith no argument restriction is the same as handing out a root shell: PHP'ssystem()calls straight into the OS shell.
11. CMS location
To run eval, bee needs to locate a valid Backdrop install and complete its bootstrap process; without one, it fails with an insufficient-bootstrap error. We locate the install:
ls /var/www/html
core files index.php LICENSE.txt modules README.md robots.txt settings.php sites themes
bee resolves the install root two ways: from the current directory (cd into it, as we do here) or via an explicit --root=/var/www/html flag. We move into the directory so the automatic detection works:
cd /var/www/html
12. Privilege escalation
With bee now able to bootstrap the CMS, we invoke eval through sudo so it runs as root, and from PHP we spawn a system shell:
sudo bee eval "system('/bin/bash');"
whoami
root
Interactive shell as root, with no additional password and no CVE in the CMS itself required: the entire privesc rests solely on that NOPASSWD sudoers line.
13. Root flag
cat /root/root.txt
System fully compromised. 🏴☠️
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Recon | Web server | Directory layout typical of Drupal/Backdrop revealed by fuzzing | CMS family identification |
| Foothold | Web server / Git | .git/ reachable over HTTP → full repo dump with git-dumper | Complete source code and history, incl. committed secrets |
| Credentials | sites/default/settings.php | Plaintext database password inside the repository | Reusable credential discovered |
| User | SSH / credential reuse | The CMS DB password is also valid for the developer's system account | Interactive access as johncusack |
| PrivEsc | bee (Backdrop CLI) via sudo NOPASSWD | The eval subcommand runs arbitrary PHP (and thus shell commands) as root | Full root |
Lessons learned
- Never deploy with
.gitreachable in theDocumentRoot: ship only the built/exported artifacts, never the version-control repository. - Git history never forgets: "deleting" a secret in a later commit doesn't remove it from history; if it ever leaked, rotate it and scrub the history (
git filter-repo/ BFG), not just delete it from HEAD. - Never reuse passwords across services: an application database password should never also work for a system account over SSH.
- Audit what you allow via
sudo: any binary that can evaluate code (PHP, Python, Perl, Lua...) granted with NOPASSWD is equivalent to handing out a root shell, CVE or not. - Keep the CMS updated: an outdated version (here, Backdrop 1.27.1) widens the attack surface even though, in this specific case, the exploitation path didn't rely on a CMS-specific CVE.
2. Enumeración web
Visitamos la web y lanzamos fuzzing de directorios para mapear el sitio:
http://10.129.231.223/
wfuzz -c -z file,/usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
--hc 404 http://10.129.231.223/FUZZ
/core
/modules
/themes
/sites
/files
Por qué importa esta estructura:
/core,/modules,/themesy/sitesson la disposición de directorios clásica de la familia Drupal (y de sus forks). Todavía no sabemos qué CMS exacto es, pero ya sabemos de qué familia es — y eso condiciona dónde buscar después (por ejemplo,sites/default/settings.phpes donde Drupal/Backdrop guardan la configuración de base de datos).
3. Repositorio .git expuesto
Comprobamos si el directorio de control de versiones quedó accesible por HTTP, un descuido clásico cuando se despliega haciendo git clone directamente en el DocumentRoot del servidor:
http://10.129.231.223/.git/
HTTP/1.1 200 OK
Content-Type: text/plain
Por qué es grave: el directorio
.gitno contiene solo el estado actual del código, sino toda su historia: objetos, blobs y commits pasados. Si en algún momento se commiteó un secreto y luego se "borró" en un commit posterior, ese secreto sigue en el historial. Con acceso HTTP de solo lectura al directorio (sin listado de directorios, pero con los ficheros internos servidos) es posible reconstruir el repositorio completo desde fuera.
4. Volcado del repositorio
No hay listado de directorio en /.git/, pero los objetos internos de Git (HEAD, index, objects/, refs/...) tienen rutas predecibles. git-dumper se aprovecha de eso: pide uno a uno los ficheros conocidos de la estructura interna de Git, descubre los hashes de objetos referenciados y va descargando recursivamente hasta poder reconstruir un .git completo y funcional.
git clone https://github.com/internetwache/GitTools.git
cd GitTools/Dumper
python3 git_dumper.py http://10.129.231.223/.git/ ~/dog_repo
Con el repositorio ya reconstruido en local, un simple git log permite además ver el historial de commits y sus autores — información útil para identificar posibles nombres de usuario del sistema.
5. Análisis del código
Con el código fuente completo en local, buscamos secretos con grep recursivo:
grep -R -i "password" .
grep -R -i "database" .
Aparecen credenciales de base de datos en la configuración del CMS (sites/default/settings.php, el fichero de configuración estándar de la familia Drupal/Backdrop). Esa contraseña de base de datos es la que más adelante resulta clave para el acceso al sistema.
Por qué un CMS guarda la contraseña de la BD en texto plano en un fichero de código: es la forma habitual en que Drupal/Backdrop resuelven la conexión a MySQL — el problema no es que exista ese fichero, sino que terminase versionado en Git y ese Git quedase expuesto al público.
6. Identificación del CMS
La estructura de directorios y el código volcado confirman Backdrop CMS, un fork de Drupal 7 orientado a simplificar la administración para equipos pequeños. La versión instalada es la 1.27.1, ya desactualizada en el momento de la máquina.
Backdrop, como el resto de la familia Drupal, permite a un administrador autenticado instalar/activar módulos — y un módulo no es más que código PHP que el CMS ejecuta. Eso significa que, en general, el panel de administración de este tipo de CMS es en sí mismo una vía de ejecución de código si se consigue una sesión de administrador. En esta máquina no hace falta llegar a eso: la credencial filtrada nos lleva directamente por SSH, así que ese camino alternativo queda solo como contexto.
7. Acceso SSH
Con nombres de usuario candidatos (extraídos de los commits del repositorio, p. ej. johncusack) y la contraseña filtrada en settings.php, probamos reutilización de credenciales contra SSH — un fallo tan simple como extendido: la misma contraseña sirve tanto para la base de datos del CMS como para la cuenta de sistema del desarrollador.
ssh johncusack@10.129.231.223
johncusack@dog:~$
8. User flag
cat ~/user.txt
9. Enumeración de privilegios
Como siempre nada más entrar, comprobamos qué puede ejecutar el usuario como root:
sudo -l
User johncusack may run the following commands on dog:
(ALL : ALL) NOPASSWD: /usr/local/bin/bee
Una sola línea, pero decisiva:
johncusackpuede ejecutar/usr/local/bin/beecomo cualquier usuario (incluido root) y sin contraseña. Falta ver qué hace exactamentebeepara saber si eso es una puerta a root.
10. Análisis de bee
bee es la CLI oficial de administración de Backdrop CMS (el equivalente de Drush en Drupal): permite gestionar módulos, temas, caché, usuarios... desde línea de comandos. Entre sus subcomandos están eval y php-script, pensados para que un administrador ejecute fragmentos de PHP dentro del contexto ya "arrancado" (bootstrapped) del CMS — con acceso a toda su API interna.
El problema no es un bug de
bee: es su diseño. Cualquier herramienta cuya función explícita sea evaluar código arbitrario es, por definición, un intérprete con privilegios de quien la ejecuta. Permitir eso porsudosin restricción de argumentos equivale a dar una shell de root: elsystem()de PHP invoca directamente al shell del sistema operativo.
11. Ubicación del CMS
Para ejecutar eval, bee necesita localizar una instalación válida de Backdrop y completar su proceso de arranque (bootstrap); si no la encuentra, falla con un error de nivel de bootstrap insuficiente. Localizamos la instalación:
ls /var/www/html
core files index.php LICENSE.txt modules README.md robots.txt settings.php sites themes
bee resuelve la raíz de la instalación de dos formas: situándose en el propio directorio (cd hasta él, como hacemos aquí) o pasando explícitamente --root=/var/www/html. Nos movemos al directorio para que la detección automática funcione:
cd /var/www/html
12. Escalada a root
Con bee ya capaz de arrancar el CMS, invocamos eval con sudo para que corra como root, y desde PHP lanzamos una shell de sistema:
sudo bee eval "system('/bin/bash');"
whoami
root
Shell interactiva como root, sin necesidad de contraseña adicional ni de explotar ningún CVE del propio CMS: todo el privesc depende exclusivamente de la línea NOPASSWD en el sudoers.
13. Root flag
cat /root/root.txt
Sistema comprometido por completo. 🏴☠️
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Reconocimiento | Servidor web | Estructura de directorios típica de Drupal/Backdrop expuesta por fuzzing | Identificación de la familia de CMS |
| Foothold | Servidor web / Git | Directorio .git/ accesible por HTTP → volcado completo del repo con git-dumper | Código fuente e historial completos, incl. secretos commiteados |
| Credenciales | sites/default/settings.php | Contraseña de base de datos en texto plano dentro del repositorio | Credencial reutilizable descubierta |
| User | SSH / reutilización de credenciales | La contraseña de la BD del CMS también es válida para la cuenta de sistema del desarrollador | Acceso interactivo como johncusack |
| PrivEsc | bee (CLI de Backdrop) vía sudo NOPASSWD | El subcomando eval ejecuta PHP arbitrario (y por tanto comandos de shell) como root | Root completo |
Lecciones aprendidas
- Nunca despliegues con
.gitaccesible en elDocumentRoot: expón solo los artefactos compilados/exportados, nunca el repositorio de control de versiones. - El historial de Git no olvida: "borrar" un secreto en un commit posterior no lo elimina del historial; si se filtró alguna vez, hay que rotarlo y purgar el historial (
git filter-repo/ BFG), no solo eliminarlo del HEAD. - Nunca reutilices contraseñas entre servicios: la contraseña de una base de datos de aplicación no debería servir jamás para una cuenta de sistema por SSH.
- Audita qué permites por
sudo: cualquier binario con capacidad de evaluar código (PHP, Python, Perl, Lua...) concedido con NOPASSWD equivale a entregar una shell de root, tenga o no CVE asociado. - Mantén el CMS actualizado: una versión desfasada (aquí, Backdrop 1.27.1) amplía la superficie de ataque aunque, en este caso concreto, la ruta de explotación no dependiese de un CVE específico del CMS.