Identify Attack Vectors in Code
Learn to identify common attack vectors in code — injection, XSS, and more. Hands-on steps, troubleshooting, and next steps.
Focus: identify common attack vectors in code
You've written code that works, but have you ever stopped to ask: what could go wrong? Every line of code you ship is a potential entry point for an attacker, and without a trained eye, you're leaving the front door unlocked. This lesson is your crash course in identifying common attack vectors in code — the precise, predictable ways attackers break in — so you can spot vulnerabilities before they become headlines.
The problem this lesson solves
Most developers learn security the hard way: after a breach. Maybe you've seen a stack trace from a "malformed input" or noticed a suspicious URL in your logs. The real problem is that vulnerabilities hide in plain sight — a missing check, an unescaped string, a naive trust of user input. Without a systematic way to see these flaws, you're relying on luck. This lesson gives you the vocabulary and mental framework to identify common attack vectors in code quickly and confidently. By the end, you'll be able to scan a codebase and flag the dangerous patterns that lead to SQL injection, cross-site scripting (XSS), and other top threats.
Core concept / mental model
Think of your application as a house. Attack vectors are the doors, windows, and chimneys — every way someone could get in. Your job isn't to build a fortress; it's to know where the openings are and how they're exploited.
The most common attack vectors fall into a few families:
- Injection (SQL, command, LDAP) — untrusted data sent to an interpreter as part of a command or query.
- Cross-site scripting (XSS) — untrusted data rendered as HTML or JavaScript, stealing sessions or actions.
- Broken authentication — weak session handling, missing MFA, or flawed login logic.
- Sensitive data exposure — storing secrets in code, insecure transmission, or overlogging.
- Security misconfiguration — default credentials, verbose errors, or missing headers.
Here's the key insight: every attack vector traces back to a trust boundary. You trust input from a user, an API, or even a library, and when that trust is broken, the attacker gains a foothold. The mental model is simple: map every place where data crosses a trust boundary, then ask, "What could an attacker send here?"
How it works step by step
Identifying attack vectors is a repeatable process, not a magic skill. Follow these steps to systematically audit any codebase:
- Trace the data flow — Start with entry points (forms, API endpoints, file uploads, headers) and follow the data as it moves through your code.
- Identify trust boundaries — Mark each point where external data enters or leaves your system. That's where attacks happen.
- Apply the attack vector checklist — For each boundary, ask: - Is this data ever used in a SQL query, shell command, or template without sanitization? (Injection) - Is this data rendered as HTML without escaping? (XSS) - Is there authentication/authorization logic that can be bypassed? (Broken auth)
- Check for common configuration pitfalls — Are secrets hardcoded? Are error messages too detailed? Are default credentials still in place?
- Test with a proof of concept — Try a simple attack (like sending a single quote in a search box) to confirm the vulnerability.
This method turns vague worry into a concrete, repeatable audit.
Hands-on walkthrough
Let's put this into practice. Here's a small Flask app with several classic vulnerabilities. See if you can spot them before reading the explanations.
# vulnerable_app.py
from flask import Flask, request, render_template_string
import sqlite3
app = Flask(__name__)
DB_PATH = 'app.db'
@app.route('/user')
def user_profile():
username = request.args.get('username', '')
conn = sqlite3.connect(DB_PATH)
query = f"SELECT * FROM users WHERE username = '{username}'"
result = conn.execute(query).fetchone()
conn.close()
if result:
template = f"<h1>Profile: {username}</h1><p>Email: {result[2]}</p>"
return render_template_string(template)
else:
return "User not found."
if __name__ == '__main__':
app.run()
Running this could give an attacker full database access. Try this in your browser:
http://localhost:5000/user?username=' OR '1'='1
The query becomes SELECT * FROM users WHERE username = '' OR '1'='1', which returns the first user in the table. That's SQL injection. Now try:
http://localhost:5000/user?username=<script>alert(document.cookie)</script>
The username is reflected into the HTML unescaped, triggering stored XSS when the page renders.
Now let's fix both vulnerabilities:
from flask import Flask, request, escape
import sqlite3
app = Flask(__name__)
DB_PATH = 'app.db'
@app.route('/user')
def user_profile():
username = request.args.get('username', '')
conn = sqlite3.connect(DB_PATH)
query = "SELECT * FROM users WHERE username = ?"
result = conn.execute(query, (username,)).fetchone()
conn.close()
if result:
safe_username = escape(username)
safe_email = escape(result[2])
return f"<h1>Profile: {safe_username}</h1><p>Email: {safe_email}</p>"
else:
return "User not found."
Using parameterized queries prevents SQL injection, and the escape() function neutralizes XSS. The same pattern applies to any framework — always treat user input as untrusted.
Compare options / when to choose what
There are several strategies for identifying attack vectors, each with trade-offs.
| Approach | Best for | Pros | Cons |
|---|---|---|---|
| Manual code review | Small codebases, critical paths | Catches logic flaws, context-aware | Time-consuming, human error |
| Static analysis tools (SAST) | CI/CD pipelines | Automated, catches common patterns quickly | False positives, misses logic flaws |
| Dynamic analysis (DAST) | Running applications | Tests actual behavior, finds runtime issues | Requires running app, may miss some code paths |
| Penetration testing | High-security apps | Mimics real attacker | Expensive, not continuous |
For a solo developer, starting with a manual review of the OWASP Top 10 patterns is your best bet. Once you scale, add a SAST tool like Bandit (Python) or Semgrep to your CI. Save pen testing for when you're handling sensitive data.
Troubleshooting & edge cases
You know the basics, but here are the pitfalls that trip up even experienced devs:
- Over-relying on input sanitization — Blocklisting bad characters (like
'and<) is fragile. Allowlists are better. Better still, use parameterized queries and escaping frameworks instead of sanitizing. - Missing upload validation — File uploads aren't just for images. An attacker can upload a PHP file that executes on your server. Always validate the file type by content (MIME type and magic bytes), not just the extension.
- Logging sensitive data — A common mistake is logging full request bodies for debugging, which leaks passwords, tokens, and PII. Log only what you need, and redact sensitive fields.
- Trusting headers that can be spoofed — Headers like
X-Forwarded-ForandReferercan be faked. Never make security decisions based on them. - Forgetting error messages — Detailed errors like
MySQL error: duplicate entry 'admin' for key 'username'give attackers a map of your schema. Use generic messages in production and log details server-side.
What you learned & what's next
You've just leveled up your security instincts. You can now explain the core idea behind identifying common attack vectors in code — it's about mapping trust boundaries and applying the OWASP Top 10 to each one. You've completed a practical exercise that turned a vulnerable Flask app into a secure one, spotting SQL injection and XSS along the way. You've also learned to choose the right detection method for your context.
Remember: identifying attack vectors is a skill you build by practicing. Review your own code with fresh eyes, use the checklists, and never assume your input is safe. In the next lesson, you'll move from spotting vulnerabilities to mitigating them systematically, learning defense-in-depth techniques like content security policies, secure session management, and encryption in transit. Keep your hacker hat on — it's the best way to protect your users.
Practice recap
Take a simple function you've written that handles user input — a search box, a form, an API endpoint. Walk through the five-step audit and list every place an attacker could inject malicious data. Write a quick proof of concept (like a single quote or a <script> tag) to see what happens. Then fix it using parameterized queries or escaping, and test again to confirm the vulnerability is gone.
Common mistakes
- Relying on blocklists to sanitize input — they miss novel payloads. Use allowlists and parameterized queries.
- Now, the biggest mistake: trusting client-side validation. An attacker can send requests directly to your API, bypassing any JavaScript checks.
- Forgetting to validate file uploads by content type — always check MIME and magic bytes, not just the extension.
- Logging full request bodies with passwords or tokens — redact sensitive data before logging.
- Skipping security headers like CSP and X-Content-Type-Options — they're easy wins with big impact.
Variations
- Use a SAST tool like Bandit or Semgrep in your CI/CD to automate the hunt for common vulnerabilities.
- Adopt a security linter like pyupgrade or pylint with security plugins to catch issues during development.
- Practice threat modeling with OWASP Threat Dragon to identify vectors before writing code.
Real-world use cases
- A bug bounty hunter finds a SQL injection in a search endpoint and reports it via a crafted payload.
- A pentester tests a web app for XSS to prove a session cookie can be stolen, leading to account takeover.
- A security team audits a legacy codebase for hardcoded secrets before pushing it to production.
Key takeaways
- Attack vectors live at trust boundaries — map every point where external data enters or leaves your code.
- Injection and XSS are the most common vectors — always use parameterized queries and escaping.
- A systematic five-step audit turns a codebase from overwhelming to actionable.
- Manual review suits small projects; SAST tools scale with your CI/CD.
- Never trust input, never trust headers, and never log secrets.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.