Back to Learning Center
highServerless Top 10CWE-94CWE-250CWE-311

Serverless Security

Serverless computing has transformed application development by abstracting infrastructure management, but it introduces unique security challenges. AWS Lambda and similar platforms expand the attack surface through event-driven architectures, multiple trigger sources, and ephemeral execution environments that require new security approaches.

What Makes Serverless Different?

Unlike traditional applications with a single entry point, serverless functions can be triggered by dozens of event sources: API Gateway, S3 buckets, SQS queues, DynamoDB streams, IoT events, and more. This overabundance of event sources dramatically expands the attack surface, making comprehensive input validation critical but complex.

Event-Data Injection Attacks

Event-data injection occurs when attackers manipulate input data sent to Lambda functions to exploit vulnerable code. Common injection types include OS command injection, runtime code injection (Node.js, Python, Java), SQL/NoSQL injection, and Pub/Sub message tampering.

Vulnerable Lambda Function

javascript
// VULNERABLE: Command injection via event data
exports.handler = async (event) => {
  const filename = event.queryStringParameters.file;
  
  // Attacker input: file=test.txt; cat /etc/passwd
  const { exec } = require('child_process');
  exec(`cat /tmp/${filename}`, (error, stdout) => {
    // Command injection!
  });
};
python
# VULNERABLE: Python code injection
import json

def handler(event, context):
    expression = event['body']['calc']
    # Attacker input: __import__('os').system('id')
    result = eval(expression)  # Code injection!
    return {'statusCode': 200, 'body': json.dumps(result)}

IAM Misconfigurations

One of the most common serverless security mistakes is using overly permissive IAM roles. If you use a single permission model for all Lambda functions, you inadvertently grant all functions full access, creating significant security risk.

Overly Permissive IAM Role

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:*",
        "dynamodb:*",
        "secretsmanager:*",
        "lambda:*"
      ],
      "Resource": "*"
    }
  ]
}

This policy grants full access to S3, DynamoDB, Secrets Manager, and Lambda. If the function is compromised, attackers can access all resources across the entire AWS account.

Credential Theft and Lateral Movement

Lambda functions are commonly targeted for credential theft that enables lateral movement. Real-world incidents include malware specifically targeting AWS Lambda (discovered by Cado Labs) and attacks like Scarlet Eel where data exfiltration from Lambda was a primary objective.

bash
# Inside a compromised Lambda function:
# Environment variables contain temporary credentials
echo $AWS_ACCESS_KEY_ID
echo $AWS_SECRET_ACCESS_KEY
echo $AWS_SESSION_TOKEN

# Use credentials to enumerate and pivot
aws sts get-caller-identity
aws s3 ls
aws secretsmanager list-secrets
aws lambda list-functions

Vulnerable Dependencies

Software packages used in Lambda function code often contain CVEs with high exploitation potential. Functions running on deprecated runtimes may lack security patches and eventually fail due to certificate expiration.

javascript
// package.json with vulnerable dependencies
{
  "dependencies": {
    "lodash": "4.17.15",      // Prototype pollution CVE-2020-8203
    "axios": "0.21.0",         // SSRF vulnerability
    "jsonwebtoken": "8.5.0",   // Algorithm confusion
    "xml2js": "0.4.19"         // XXE vulnerability
  }
}

Environment Variable Exposure

Storing secrets directly in Lambda environment variables is risky. Anyone with Lambda read access can view these values, and they may be logged accidentally.

javascript
// VULNERABLE: Secrets in environment variables
exports.handler = async (event) => {
  const dbPassword = process.env.DB_PASSWORD;  // Visible in AWS Console
  const apiKey = process.env.API_KEY;          // Logged if debugging enabled
  
  // If error occurs, secrets may appear in CloudWatch logs
  console.log('Config:', process.env);  // Leaks all secrets!
};

Prevention Strategies

Least Privilege IAM Policy

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["dynamodb:GetItem", "dynamodb:PutItem"],
      "Resource": "arn:aws:dynamodb:us-east-1:123456789:table/users"
    },
    {
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue"],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789:secret:db-creds-*"
    }
  ]
}

Secure Input Validation

javascript
const Joi = require('joi');

// Define strict schema for event data
const schema = Joi.object({
  filename: Joi.string().alphanum().max(50).required(),
  action: Joi.string().valid('read', 'write').required()
});

exports.handler = async (event) => {
  // Validate ALL input from ANY event source
  const { error, value } = schema.validate(event.body);
  
  if (error) {
    return { statusCode: 400, body: 'Invalid input' };
  }
  
  // Safe to use validated input
  const { filename, action } = value;
};

Secure Secrets Management

javascript
const { SecretsManager } = require('@aws-sdk/client-secrets-manager');

const client = new SecretsManager();
let cachedSecret = null;

exports.handler = async (event) => {
  // Fetch secrets from Secrets Manager (with caching)
  if (!cachedSecret) {
    const response = await client.getSecretValue({
      SecretId: 'prod/myapp/db-credentials'
    });
    cachedSecret = JSON.parse(response.SecretString);
  }
  
  // Never log secrets
  // Use secrets only for their intended purpose
  const connection = await db.connect({
    password: cachedSecret.password
  });
};

Service Control Policies

json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyLambdaWithoutVPC",
      "Effect": "Deny",
      "Action": "lambda:CreateFunction",
      "Resource": "*",
      "Condition": {
        "Null": {
          "lambda:VpcIds": "true"
        }
      }
    },
    {
      "Sid": "RequireLatestRuntime",
      "Effect": "Deny",
      "Action": "lambda:CreateFunction",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "lambda:Runtime": ["nodejs14.x", "python3.7"]
        }
      }
    }
  ]
}

Security Checklist

• Use separate IAM roles for each Lambda function with minimal permissions

• Validate ALL input from every event source (API Gateway, S3, SQS, etc.)

• Store secrets in AWS Secrets Manager or Parameter Store, never in environment variables

• Regularly scan Lambda functions and layers for vulnerable dependencies

• Keep runtimes updated - deprecated runtimes lack security patches

• Use SCPs to enforce guardrails across the organization

• Enable VPC deployment for functions accessing sensitive resources

• Never log sensitive data - use structured logging with PII filtering

• Implement function-level monitoring for anomalous behavior

• Use reserved concurrency to prevent DoS via function exhaustion

Practice Challenges

View all