6 API Injection Attacks You're Probably Not Testing For

6 API Injection Attacks You’re Probably Not Testing For

Posted in

Most teams do at least some sort of injection attack testing. This testing, however, is typically focused on a small subset of particular vulnerabilities. SQL injection is a popular target, as is command injection. Some teams may even do log injection if they’ve been burned before.

But when it comes to APIs — and especially modern JSON or GraphQL-based systems — the injection landscape is broader, sneakier, and often less-effectively tested than most security testing suites would have you believe.

Today, we’re going to look at a handful of injection paths that most teams aren’t testing for, even though they absolutely should be.

1. JSON Injection

You might think that your API is immune to injection because you’re not directly building with SQL queries. But many backend systems accept a wide range of strings, and one of those string types that is particularly threatening is JSON. JSON can get reparsed, interpolated, and fed into a variety of systems, and with some clever approaches, can be pretty damaging.

Consider for a moment a payload like this:

{
  "user": "{\"$ne\": null }"
}

While this might seem somewhat innocuous, this kind of JSON payload could then be processed via a backend implementation as follows:

const query = { user: req.body.user };
User.find(query);

Now, assuming that this query is then parsed via JSON.parse() or some other dynamic query builder, and assuming you have a MongoDB service, you get this:

{
  "user": { "$ne": null }
}

That’s no longer a string match for “user” — that’s a MongoDB operator that returns all users where user is NOT null, meaning you could, in theory, get an export of all users in a database. While you might have restrictions for return size, the core problem here is that this kind of string in specific injectable environments could sneak an operator into the query logic, resulting in a lot more access than you ever thought possible.

Mitigation:

  • Ensure you are suspicious of fields that contain serialized JSON inside strings. Remove where possible.
  • Ensure that payloads are only parsed once — if they must be parsed multiple times, ensure you apply transform and filter rules to strip out potential injection attacks.
  • If you have to use eval, JSON.parse, or dynamic field resolution, make sure you are validating output and applying rules to prevent data leakage and operant injection.

2. GraphQL Resolver Abuse

GraphQL hides a ton of complexity behind a single endpoint, but every resolver in this flow is a potential injection point if it passes user input into downstream systems. GraphQL is very much a “risk scales with permissibility” situation — depending on how strict your GraphQL resolution is, you might have much more extreme exposure than other providers.

For example, if you use an argument like this:

{
  "searchTerm": "* OR 1=1"
}

And the backend processes this request like this:

const search = `SELECT * FROM users WHERE name LIKE '%${req.body.searchTerm}%'`;

A few things will happen:

  • 1=1 will evaluate as always true
  • All rows will be returned as a result using the * wildcard character.
  • The logical operant might allow additional clauses, up to and including the all-too-infamous ; DROP TABLE users;.

Let’s say you do filter these requests, and you prevent the data from being exported. If this query is passed to Elasticsearch or Solr, there’s good news: you aren’t losing your data to your attacker! But there’s also bad news — the extremely expensive query has been run, and can be re-run ad nauseam.

Mitigation:

  • Make sure that query and mutation arguments with nested input objects are sanitized or checked against an allowlist.
  • Prevent enum values from being cast into code paths, filtering in the input stage itself.
  • Make sure you are applying controls to your field aliases to prevent query shaping or ambiguity-driven attacks.

3. Header Injection

This is a common enough attack, but it is still one that is sometimes missed in testing regimens. The idea is simple: APIs often use headers for critical metadata, and this injection can be used to wreak havoc internally.

Interestingly, teams do often test injection for body content, but not as many test the header content as well. This is largely based on the assumption that much of the header content is going to be auto-generated, containing mechanical validation or data, but with effective capture or man-in-the-middle attacks, these headers can be transformed and transmuted.

Consider, for instance, a system that captures data from a target machine to an endpoint. This header contains valid auth data, which an attacker can then use for their own use. Instead of using the previous valid header data, they might instead inject that header data into the header of their own malformed request, bypassing filtering or denylists to enter the system under the cover of valid data.

This is just one example — these kinds of attacks can make ample use of the header content to do some pretty complex attacks.

Mitigation:

  • Multi-header injection (such as duplicated authorization headers), either through filtering or through character limits, which prevent hybrid header injection.
  • Make sure that you filter CRLF injection to prevent the header parsing from breaking, filtering entries such as %0d%0a.
  • Adopt a zero-trust architecture to prevent host header manipulation for SSRF or cache poisoning. You can use a multi-layered approach and encrypted tokens to prevent a good share of these sorts of attacks.

4. Template Injection via Custom Fields

One injection attack vector that is not always obvious is the use of custom fields. If you’re using templates for emails, PDFs, or dynamic rendering, and you’re inserting user-supplied content into them, you may be opening a huge injection attack vector.

For example, let’s say you provide a service that allows users to register and generate an email signature. You might use a custom field like this:

{
  "endMessage": "{{user.signOff}}"
}

This code is simple enough — it allows a user to define the sign-off at the end of their signature. If this is unfiltered, however, a user might be able to inject code into that value. When the dynamic rendering happens using something like Handlebars or Liquid, it might allow arbitrary code execution. If you’re limiting the dynamic engine to just what the user sees, then great — no real risk! If you’re saving state in a common store, well, you might be opening up a database export with a single custom field.

Mitigation:

  • Escape or sanitize input fully before interpolation via dynamic systems.
  • Wherever possible, use safe-mode template rendering and isolate/sandbox only the relevant user to the current data.
  • Fully restrict or validate dynamic field content in API inputs — don’t assume that just because you’re moving to a CLI, you don’t need to be as wary of low-level attacks!

5. Injection via Webhook or Callback URLs

APIs that accept URLs for webhook callbacks, redirect URIs, or fetch instructions are prime targets for a variety of attacks.

For example, let’s assume an API accepts URLs for a webhook callback. An attacker feeds a custom field containing this domain: //attacker.com (a protocol-relative URL).

Many parsers will assume HTTP if not explicitly stated, but because HTTP is not stated within the URL provided, filters may not catch the schemaless domain or validate it. Using a custom field to set fetch instructions can result in this domain being fed as a webhook while the request fed to that webhook is then used as part of an open redirect attack.

Mitigation:

  • Make sure that you are only accepting a specific URL schema like https://, and that you are validating this traffic against an allowlist internally.
  • Make sure you are not allowing internal IPs to route externally via calls – never allow something like 127.0.0.0 to come from an external service or users.
  • Make ample use of IP allowlists and timeouts!

6. Deserialization Bombs

APIs that accept complex input objects — especially via JSON, XML, or YAML — may be vulnerable to an attack called deserialization. This attack uses DoS attacks via data bombs to essentially crash the receiving service in a way that is difficult to mitigate once it happens.

For example, a recursive JSON structure can be sent in a form like this:

{
  "a": {
    "b": {
      "c": {
        "d": {
          "e": {
            "f": {
              "g": {
                "h": {
                  "i": {
                    "j": {
                      "k": {
                        "l": {
                          "m": {
                            "n": {
                              "o": {
                                "p": {
                                  "q": {
                                    "r": {
                                      "s": {
                                        "t": {
                                          "u": {
                                            "v": {
                                              "w": {
                                                "x": {
                                                  "y": {
                                                    "z": "boom"
                                                  }
                                                }
                                              }
                                            }
                                          }
                                        }
                                      }
                                    }
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

This example is only 26 layers deep, but when parsed as a payload, the JSON itself is quite small. Even when there are 10,000 layers, simple compression will make this a very small payload. The attack occurs when the parser — either by default or by config injection — attempts to recursively parse this JSON object. A single attack might slow down a system, but hundreds of requests a minute with these compressed bombs could very well eat up CPU and memory resources, generating excessive expense and denying the service to other users.

Mitigation:

  • Make sure that you are not recursively rendering JSON, and also ensure that you are not allowing custom configurations to be injected.
  • Identify libraries using yaml.load() or other loader systems, and ensure that they are limited in total addressable or usable memory.
  • Use and review safe parsing limits and libraries. Apply adequate depth and size limits.

Final Thoughts on Injection Attack Types

The reality is that you can’t scan what you don’t see. Injection isn’t just about SQL or XML — modern APIs are full of secondary parsing paths, dynamic type coercion, and indirect data flows. These are the kinds of blind spots that attackers love, and the kinds of faults that scanners typically miss.

To harden your API against these attacks, you should think about your data lifecycle, and not just the controller input. Validate everything, even headers and nested fields, and adopt a zero-trust approach: treat template URLs and schema inputs as high-risk regardless of where they originate.

The good news is that most of these are fixable with better input validation, test coverage, and an updated threat model. The bad news is that these attack vectors are just a small sampling of the common injection attacks that you are probably not testing for — so you must remain vigilant!