5 minute read Security

Welcome back to this OWASP Top 10 series. So far, we’ve looked at Broken Access Control and Cryptographic Failures — both dangerous, both preventable. Now we’re moving on to one of the most well-known and still widely exploited risks in web security:

Injection.

Whether it’s SQL, OS commands, LDAP, or even SMTP, injection flaws let attackers feed malicious input into your system — and trick it into executing unintended commands.

Let’s break it down: where injection comes from, how attackers use it, and what you should do to prevent it.

What is Injection?

Injection occurs when untrusted input is interpreted as code or a command by the application. Instead of treating the input as data, the app runs it—often with disastrous results.

Common types of injection include:

  • SQL Injection (SQLi)
  • Command Injection (Shell)
  • LDAP Injection
  • XPath Injection
  • NoSQL Injection

Why Does This Happen?

Injection is usually the result of two things:

  1. Unvalidated, unsanitised input, and
  2. Dynamic code execution or query construction using that input.

If your app builds SQL queries like this:

SELECT * FROM users WHERE username = '$input';

…then $input = ' OR '1'='1' – becomes an attacker’s backdoor to your database.

How Attackers Exploit Injection Flaws

SQL Injection Example

An attacker enters this into a login form:

' OR 1=1 --

It turns this:

SELECT * FROM users WHERE username = '$input';

Into:

SELECT * FROM users WHERE username = '' OR 1=1 --';

Now they’re logged in without knowing any username or password.

Command Injection Example

ping -c 4 $host

If $host = 8.8.8.8; rm -rf /, and the app executes it, the attacker just wiped your server.

Even modern applications with APIs and NoSQL databases aren’t safe. MongoDB, for example, has its own flavor of injection:

{ "username": { "$ne": null } }

This matches every user. And yes, people still ship apps that allow this.

LDAP Injection

LDAP (Lightweight Directory Access Protocol) is used to query and manage directory services (like Active Directory). Injection occurs when unsanitised input is included in the LDAP query string.

For example, let’s say you authenticate users like this:

String filter = "(uid=" + inputUsername + ")";

If an attacker inputs:

*) (|(uid=*)

Then the query becomes:

(uid=*) (|(uid=*))

This can return all users or allow the attacker to bypass login or access unauthorised data, depending on how your directory is configured.

Fix: Use parameterised LDAP APIs if available, or sanitise special characters like *, (, ), |, &, =, and null bytes (\00).

XPath Injection

XPath is used to navigate XML documents. Just like SQL, if user input is included in an XPath expression without sanitisation, it can be manipulated.

For example, here’s a simplified XML login system:

admin secret

The app runs this query:

xpath = f"//user[username/text()='{username}' and password/text()='{password}']"

An attacker enters:

  • Username: admin' or '1'='1
  • Password: anything

This becomes:

//user[username/text()='admin' or '1'='1' and password/text()='anything']

If the logic isn’t strict, this returns true—bypassing authentication.

Fix: Escape user input properly and consider using XML libraries that support secure XPath queries with parameter binding.

NoSQL Injection

Modern apps use NoSQL databases like MongoDB, which often build queries using flexible JSON structures. But if you treat input like data without constraints, you’re still at risk.

For example, in MongoDB, this is a typical login check:

db.users.findOne({ username: inputUsername, password: inputPassword });

An attacker submits:

{ "username": { "$ne": null }, "password": { "$ne": null } }

Now the query becomes:

db.users.findOne({ username: { $ne: null }, password: { $ne: null } });

This returns any user with a non-null username and password — likely the first user in the collection. If that’s an admin, the attacker just got access.

Fix:

  • Use input validation: make sure username and password are strings, not objects.
  • Whitelist expected fields and types.
  • Use ORM-like libraries (e.g., Mongoose) that offer built-in protections.

How to Prevent Injection

The best part? Injection is preventable. Here’s how to harden your application:

  1. Use Parameterised Queries (a.k.a. Prepared Statements)
    • Don’t dynamically construct SQL queries.
    • Use safe query APIs that keep input as data, not executable code.

Examples:

  • ? placeholders in SQL (JDBC, Python’s psycopg2, etc.)
  • $1, $2 positional parameters in PostgreSQL
  • ?params in ORMs like SQLAlchemy, Hibernate, Sequelize, etc.
  1. Use ORMs and Query Builders Where Possible
    • Let the framework handle escaping and query composition.
    • Still validate input, but avoid raw queries unless absolutely necessary.
  2. Escape Output in Context
    • If you’re inserting input into HTML, JavaScript, or command-line output, escape it according to the context.
    • For example, use HTML entity encoding when displaying data on web pages.
  3. Validate and Sanitise Input
    • Enforce strict input validation (length, type, pattern).
    • Reject or clean anything that doesn’t meet expectations.
  4. Avoid Dangerous Functions
    • Don’t use eval(), exec(), system(), or similar functions unless there’s no alternative—and even then, lock them down hard.
  5. Use Security Linters and Scanners
    • Use tools like Semgrep, SonarQube, or Snyk to detect potentially injectable code patterns before they hit production.

Real-World Example: The Login Bypass

You’re maintaining a legacy admin panel with a login form that directly interpolates user input into a query string. One day, someone reports unusual access logs. You investigate and find logins from strange IPs—into accounts that should have been locked.

Turns out the attacker ran a basic ' OR 1=1 – injection into the username field and bypassed auth completely. The attack was simple, but the impact was huge—especially since those admin accounts had access to customer data.

Final Thoughts

Injection flaws are old-school, but they’re not going away. Why? Because developers still write raw queries, skip validation, and trust input that should never be trusted.

Here’s the mindset shift: input from users should never be treated as code. Ever.

Validate it. Escape it. Parameterise it. Then sleep easier at night.

Next up: A04: Insecure Design → We’ll talk about the kinds of architectural decisions that lead to security failures—and how to spot and fix them before you write a single line of code.

Leave a comment