In this guide
Your web app works. Users can sign up, post data, upload files. But is it secure? Most student projects have vulnerabilities that would take an attacker minutes to exploit — not because the students are careless, but because nobody taught them what to look for.
The OWASP Top 10 is the industry-standard list of the most critical web application security risks, maintained by the Open Web Application Security Project. This guide explains each one in practical terms: what the vulnerability is, why it happens, and how to prevent it in your project.
Scope note: this guide teaches defense — how to recognize and fix vulnerabilities in your own code. It does not teach attack techniques. Test only applications you own or have explicit permission to test.
1. Broken Access Control
What it is: users can access data or functions they shouldn't. The most common vulnerability in real applications.
How it happens: your app checks "is this user logged in?" but not "does this user own this data?" A student project might have URLs like /orders/123 where changing 123 to 124 shows another user's order. Or an API endpoint that returns all users when it should return only the current one.
Prevention:
- Check authorization on every request, not just authentication. "Logged in" ≠ "allowed."
- Use indirect references: instead of
/orders/123, scope queries to the current user (WHERE user_id = current_user). - Deny by default — explicitly grant access rather than trying to block bad access.
- Never rely on hiding UI elements. If the button is hidden but the API endpoint works, it's not protected.
2. Cryptographic Failures
What it is: sensitive data exposed because it wasn't properly protected — transmitted in plain text, stored unencrypted, or encrypted with weak methods.
How it happens: passwords stored as plain text (or weak hashes like MD5). Credit card numbers in database columns with no encryption. Login forms served over HTTP instead of HTTPS. Sensitive data in URL parameters (which get logged everywhere).
Prevention:
- Passwords: use bcrypt or Argon2 (never MD5, SHA1, or plain text). See the password hashing guide.
- Transit: HTTPS everywhere. Get a free certificate — see the SSL guide.
- Don't put sensitive data in URLs. Use POST bodies and headers.
- Encrypt sensitive data at rest if your threat model requires it.
3. Injection
What it is: untrusted data gets interpreted as code or commands. The most famous variant is SQL injection, but the category includes command injection, LDAP injection, and more.
How it happens: building database queries by concatenating strings with user input. If user input flows directly into a query string, an attacker can alter the query's meaning. The same pattern applies to system commands, template engines, and expression languages.
Prevention:
- Use parameterized queries (prepared statements) — never concatenate user input into queries. This is the single most important fix.
- Use an ORM — they parameterize by default.
- Validate and sanitize input, but treat this as defense-in-depth, not the primary fix.
- Apply the principle of least privilege to database accounts (the app's DB user shouldn't have DROP TABLE rights).
4. Insecure Design
What it is: security flaws baked into the architecture itself — missing threat modeling, no security requirements, insecure business logic.
How it happens: building a password reset that sends the new password in plain text email. Designing a coupon system where the discount is calculated client-side. No rate limiting on login because "we'll add it later."
Prevention:
- Think about abuse cases during design, not after deployment. "How would someone misuse this feature?"
- Write security requirements alongside functional requirements.
- Use established patterns for security-sensitive flows (password reset, payment, access control) instead of inventing your own.
5. Security Misconfiguration
What it is: the app is secure in theory but deployed insecurely — default credentials, verbose error messages, unnecessary features enabled, missing security headers.
How it happens: database with default admin password. Stack traces shown to users (revealing file paths, library versions). Directory listing enabled on the web server. Cloud storage bucket left public.
Prevention:
- Change all default credentials before deployment.
- Disable detailed error messages in production — log them server-side, show generic messages to users.
- Set security headers (Content-Security-Policy, X-Frame-Options, Strict-Transport-Security).
- Remove unused features, sample apps, and debug endpoints from production.
- Keep a deployment checklist and actually follow it.
6. Vulnerable and Outdated Components
What it is: your app is only as secure as its dependencies. Using a library with a known vulnerability exposes you.
How it happens: a package.json with a 3-year-old version of a library that has published CVEs. Copy-pasting a tutorial that installs a deprecated package. Never running npm audit or equivalent.
Prevention:
- Know your dependencies — generate a software bill of materials.
- Run vulnerability scanners regularly (
npm audit,pip audit, Dependabot, Snyk). - Update dependencies on a schedule, not just when something breaks.
- Remove dependencies you don't use.
7. Identification and Authentication Failures
What it is: weaknesses in how users prove their identity — weak passwords allowed, no brute-force protection, session issues.
How it happens: no rate limiting on login (attackers can try unlimited passwords). Sessions that never expire. Password reset tokens that are predictable. Allowing "password123".
Prevention:
- Enforce reasonable password policies (length over complexity).
- Rate-limit login attempts and consider account lockout or CAPTCHA after failures.
- Use secure, HttpOnly, SameSite cookies for sessions.
- Implement proper session timeout and invalidation on logout/password change.
- Support multi-factor authentication for sensitive applications.
The JWT authentication guide and OAuth2 guide cover modern auth patterns.
8. Software and Data Integrity Failures
What it is: trusting code or data without verifying its integrity — insecure updates, compromised build pipelines, deserialization of untrusted data.
How it happens: auto-updating from an unverified source. CI/CD pipeline that any team member can modify without review. Deserializing user-supplied data with pickle (Python) or Java serialization — both can execute arbitrary code.
Prevention:
- Verify signatures/hashes on updates and dependencies.
- Secure your build pipeline — restrict who can modify it, review changes.
- Never deserialize untrusted data with formats that execute code. Use JSON instead.
- Use Subresource Integrity (SRI) hashes for third-party scripts.
9. Security Logging and Monitoring Failures
What it is: you can't detect or respond to attacks because you're not logging the right things — or not looking at the logs.
How it happens: no logs of login failures, so brute-force attacks go unnoticed. Logs exist but nobody reviews them. Logs don't include enough context (which user? which IP? what action?) to investigate.
Prevention:
- Log authentication events (success and failure), access control failures, and input validation failures.
- Include context: timestamp, user, IP, action, outcome.
- Set up alerts for suspicious patterns (many failed logins, unusual access times).
- Protect logs from tampering — attackers delete logs to cover tracks.
10. Server-Side Request Forgery (SSRF)
What it is: the attacker tricks your server into making requests to internal systems — cloud metadata endpoints, internal admin panels, other services on the private network.
How it happens: your app fetches a URL the user provides (profile image URL, webhook, file import). An attacker provides http://169.254.169.254/latest/meta-data/ (AWS metadata endpoint) or http://localhost:8080/admin and your server happily fetches it, returning cloud credentials or internal data.
Prevention:
- Validate and allowlist URLs — don't fetch arbitrary user-supplied URLs.
- Block requests to internal IP ranges (10.x, 192.168.x, 169.254.169.254, localhost).
- Use a dedicated egress proxy with filtering for outbound requests.
- Disable unused URL-fetching features.
Putting it into practice: a student security checklist
Before deploying any web project:
- All database queries use parameterization (no string concatenation)
- Passwords hashed with bcrypt/Argon2
- HTTPS enabled with valid certificate
- Authorization checked on every endpoint (not just authentication)
- Default credentials changed
- Detailed errors disabled in production
- Dependencies scanned for known vulnerabilities
- Login rate-limited
- Security headers set
- User-supplied URLs validated/allowlists applied
Security isn't a feature you add at the end — it's a property of how you build. Start with this checklist on your next project and you'll be ahead of most production applications.
For deeper dives: the SQL injection prevention guide and XSS explained guide cover two specific vulnerability classes in detail. The HTTPS/TLS guide explains the encryption that protects data in transit. More web development topics in the Web Development branch hub.