the most interesting findings in pentest reports usually aren't the single vulnerabilities. they're the chains — the moments where one mediocre bug feeds the next, and the next, until what started as a probing query is a shell.
this is one of those chains. it anchored my final capstone project, where the target was bWAPP (BeeBox), an intentionally vulnerable PHP/MySQL app used for OWASP Top 10 training. the scope was black-box, browser-accessible surfaces only, no DoS, medium difficulty.
what follows is the path from a URL to a root shell.
step 1 — the numeric parameter that didn't filter quotes
the endpoint was a movie-select page, sqli_2.php at medium security. the parameter was numeric: ?movie=1. sending a single quote produced a SQL error in the response — classic error-based SQLi signal.
the application used addslashes() for sanitisation. that defeats quote-based payloads like ' OR 1=1-- -, but it does nothing for numeric injection that never needs a quote in the first place.
GET /sqli_2.php?movie=0 UNION SELECT 1,2,3,4,5,6,7 -- -seven columns. the page rendered each value where the original movie's fields had been.
step 2 — pivoting from columns to metadata
once you know the column count, the UNION becomes a window into the database. position the functions you care about in the columns the page actually renders:
GET /sqli_2.php?movie=0 UNION SELECT 1,version(),database(),user(),5,6,7 -- -the response told me the MySQL version, the database name, and — critically — that the application was connecting as root. root-level database access from an unauthenticated request.
step 3 — extracting credentials
with root and a confirmed schema name, the users table was an easy walk:
GET /sqli_2.php?movie=0 UNION SELECT 1,login,password,email,secret,6,7 FROM users -- -passwords stored as SHA-1 hashes. that's a finding in itself — SHA-1 is fast, unsalted, and trivially crackable for any password in a wordlist. i dumped the hashes and handed them to john the ripper.
the first crack came back almost immediately: the password was bug.
step 4 — credential reuse
a web user with a cracked password is one thing. the chain only works if those credentials are reused somewhere that matters.
phpMyAdmin was exposed. the same root / bug worked. that gave me an administrative interface to the entire database — a strong finding on its own, but in this case a stepping stone.
step 5 — webdav with listing enabled
while mapping the directory tree i'd noted a webdav path with directory listing turned on. directory listing is usually a low-severity finding. combined with write access, it's RCE waiting to happen.
i uploaded a small PHP reverse shell — the kind of file no legitimate webdav deployment should accept, sitting on a path the server would happily execute. i started a netcat listener on my kali VM:
nc -lvnp 4444i navigated to the uploaded file. the connection came back. root shell.
the shape of the chain
pulled apart, the full chain is:
- error-based sqli on a numeric parameter the input filter didn't cover.
- union-based extraction of database metadata.
- extraction of
userstable, including hashes. - offline password cracking.
- credential reuse against phpMyAdmin.
- credential reuse against webdav.
- php reverse shell upload.
- root shell.
no single step is exotic. the sqli is a textbook case. SHA-1 is a textbook weakness. webdav with listing enabled is a textbook misconfiguration. the lesson is that each one's textbook-ness is the point — defences fail in combinations that nobody designed for.
what i'd tell a developer reading this
if you're shipping php that touches a database, the three controls that would have killed this chain at different points are:
- parameterised queries. not
addslashes(), not regex filters, not "we sanitise on output". prepared statements. they make the chain impossible at step 1. - a real password hashing function —
password_hash()with the default algorithm. step 3 still leaks the hashes, but step 4 becomes computationally expensive enough that the chain stalls. - least-privileged database users. the application does not need to connect as
root. a web user withSELECT,INSERT,UPDATE,DELETEon its own schema closes step 5 entirely.
each one is a decision a developer makes once and forgets about. none of them slow down a project. all three together turn this writeup into something that ends at step 1 instead of step 8.
where it sits
the full engagement is documented in bloodmoonbreach/Bee-Box-TF — methodology, evidence captures, and the dual-audience report. the capstone scored 10/10.
more posts coming on the other vulnerability classes i covered in the same engagement: the reflected XSS that needed an <img onerror> to bypass the script-tag blacklist, and the ZAP-fuzzer setup for the broken-authentication form that rotated an anti-csrf token on every request.