---
title: "Contournement de WAF par Confusion de Charset : L'histoire de la CVE-2026-21876 | DevSense"
description: "Découvrez comment la vulnérabilité CVE-2026-21876 permet de contourner l'OWASP ModSecurity Core Rule Set (CRS) via la confusion de charset dans les requêtes multipart et l'écrasement de variable dans la règle 922110."
faq:
    - { question: 'Quelle est la cause racine de la vulnérabilité CVE-2026-21876 ?', answer: "La cause racine est un problème d'écrasement de variable dans la règle OWASP CRS 922110. Lors de l'analyse d'une requête multipart, le WAF enregistre le Content-Type et le charset de chaque partie dans la même variable globale de transaction. Comme la validation par rapport à la liste blanche n'a lieu qu'après le traitement de toutes les parties, seul le Content-Type de la dernière partie est réellement validé, laissant les parties précédentes non vérifiées." }
    - { question: 'Pourquoi la confusion de charset avec IBM037 ou UTF-7 permet-elle de contourner le WAF ?', answer: "Les attaquants peuvent encoder leurs charges utiles malveillantes dans des encodages non standard comme IBM037 (basé sur EBCDIC) ou UTF-7. Le WAF ne décode ni n'inspecte ces parties car la validation de leur Content-Type est contournée en raison de l'écrasement de variable et ses règles de signature sont écrites pour ASCII/UTF-8. Cependant, les serveurs d'arrière-plan (comme Undertow ou ASP.NET Core) prennent automatiquement en charge et décodent ces charsets, restaurant la charge utile malveillante en clair sur le backend." }
    - { question: "Comment l'OWASP Core Rule Set corrige-t-il cette vulnérabilité ?", answer: "La vulnérabilité a été corrigée en réécrivant le jeu de règles. Au lieu d'écraser une seule variable, la nouvelle logique initialise un compteur, stocke le Content-Type de chaque partie multipart dans une variable distincte nommée de manière unique, puis itère sur toutes les variables enregistrées pour s'assurer que chacune est validée par rapport à la liste blanche." }
published: '2026-07-09'
---
# Contournement de WAF par Confusion de Charset : L'histoire de la CVE-2026-21876

Les pare-feu d'application Web (WAF) sont des composants essentiels de la sécurité Web moderne. Placés entre les clients et les serveurs d'arrière-plan (backend), ils inspectent le trafic entrant pour détecter et bloquer les requêtes malveillantes telles que les injections SQL (SQLi) et le cross-site scripting (XSS). Cependant, un WAF n'est efficace que si ses règles le sont.

Dans cet article, nous analysons la **CVE-2026-21876**, une vulnérabilité de sévérité élevée affectant l'**OWASP ModSecurity Core Rule Set (CRS)**. Cette faille permettait aux attaquants de contourner complètement les contrôles de sécurité en exploitant un bug d'écrasement de variable combiné au décodage automatique des jeux de caractères (charsets) par les serveurs d'arrière-plan modernes.

---

## ModSecurity et l'OWASP Core Rule Set (CRS)

**ModSecurity** est un moteur WAF open-source basé sur des signatures, intégré aux serveurs Web populaires comme Nginx, Apache et IIS. ModSecurity n'étant qu'un moteur, il dépend d'un ensemble de règles pour détecter les attaques.

L'**OWASP ModSecurity Core Rule Set (CRS)** est l'ensemble standard de règles génériques de détection des attaques. Il est largement déployé chez les grands fournisseurs de cloud et de CDN, notamment Azure WAF, les configurations AWS WAF, Cloudflare, Fastly et Google Cloud Armor. Avec des millions d'installations actives dans le monde, une vulnérabilité dans le jeu de règles CRS a un impact en cascade sur un grand nombre d'infrastructures Web.

---

## Mécanique de la vulnérabilité CVE-2026-21876

La faille réside dans la règle **922110**, conçue pour valider strictement les en-têtes `Content-Type` des différentes parties d'une requête multipart (`multipart/form-data`). Plus précisément, elle s'assure que chaque partie utilise un type de contenu et un charset autorisés (sur liste blanche).

### Le bug d'écrasement de variable

Lorsqu'un client envoie une requête multipart, ModSecurity parcourt chaque partie de la charge utile. Durant ce processus, la règle 922110 extrait le `Content-Type` de chaque partie et le stocke dans une variable globale de transaction (par exemple, `TX:content_type`).

Le bug est simple mais critique :
1. **La boucle :** ModSecurity itère à travers toutes les parties, extrait et écrit le Content-Type de la partie courante dans la même variable, écrasant ainsi la valeur précédente.
2. **La validation :** La vérification par rapport à la liste blanche n'est exécutée qu'**après** la fin de la boucle, et non à l'intérieur de la boucle pour chaque partie individuelle.
3. **La conséquence :** Seul le Content-Type de la **toute dernière partie** de la requête multipart est validé par rapport à la liste blanche. Les parties précédentes sont ignorées par la règle 922110.

Représentée sous forme de pseudo-code de type Python, la logique vulnérable ressemble à ceci :

```python
# Logique vulnérable (CVE-2026-21876)
content_type_var = None

# Extraction des types de contenu dans une boucle
for part in request.multipart_parts:
    content_type_var = part.headers.get("Content-Type")  # Écrase la variable globale

# La validation est effectuée en dehors de la boucle
if not is_whitelisted(content_type_var):
    block_request()
```

En ajoutant un paramètre factice (dummy) avec un Content-Type autorisé à la toute fin de la requête, un attaquant peut écraser la variable globale avec une valeur "sûre", contournant ainsi complètement la vérification.

---

## Le vecteur d'exploitation : Confusion de Charset

Pour exploiter cette faille, un attaquant combine le bug d'écrasement de variable avec une technique appelée **Confusion de Charset**.

Les règles de signature des WAF (comme celles recherchant `UNION SELECT` ou `<script>`) sont conçues pour analyser les charges utiles encodées dans des charsets standards comme UTF-8 ou ASCII. Si un attaquant soumet une charge utile encodée dans un charset non standard, le WAF ne parviendra pas à faire correspondre les signatures car la séquence d'octets brute ne ressemblera pas aux mots-clés malveillants.

Normalement, la règle 922110 bloquerait toute requête contenant un charset non autorisé. Cependant, en raison du bug d'écrasement, le WAF autorise des charsets non approuvés dans les premières parties tant que la dernière partie est légitime.

### Construction de la charge utile d'attaque

Un attaquant structure une requête `multipart/form-data` avec deux parties :
1. **Partie 1 (Charge utile malveillante) :** Utilise un charset non standard comme `IBM037` (un encodage basé sur EBCDIC). Le WAF contourne la validation du Content-Type (à cause de l'écrasement) et n'inspecte pas la charge utile car elle est encodée en EBCDIC.
2. **Partie 2 (Paramètre factice) :** Utilise un charset standard comme `utf-8`. Cela écrase la variable de suivi du WAF avec une valeur valide, permettant à l'ensemble de la requête de passer.

### Requête HTTP brute démontrant le contournement

```http
POST /vulnerable-endpoint HTTP/1.1
Host: target-app.com
Content-Length: 432
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarySafeAndBypass

------WebKitFormBoundarySafeAndBypass
Content-Disposition: form-data; name="id"
Content-Type: text/plain; charset=ibm037

K@ñ%ÈÊÈÀñKñ
------WebKitFormBoundarySafeAndBypass
Content-Disposition: form-data; name="legitimate_param"
Content-Type: text/plain; charset=utf-8

safe-value
------WebKitFormBoundarySafeAndBypass--
```

> [!NOTE]
> Dans l'exemple ci-dessus, la valeur `K@ñ%ÈÊÈÀñKñ` représente la charge utile d'injection SQL `' OR 1=1 --` encodée en IBM037 (EBCDIC). Pour le WAF, ces octets apparaissent comme des données binaires aléatoires et ne correspondent à aucune signature d'attaque.

---

## Comportement de décodage du Backend

Un contournement de WAF n'est dangereux que si l'application d'arrière-plan (backend) traite réellement la charge utile malveillante.

De nombreux serveurs et frameworks backend modernes analysent automatiquement les requêtes `multipart/form-data`. Lorsqu'ils rencontrent une partie avec un paramètre `charset` spécifique dans son en-tête `Content-Type`, ils décodent automatiquement les octets de cette partie en utilisant le charset spécifié.

Certains backends notables présentant ce comportement par défaut incluent :
- **Spring Boot s'exécutant sur Undertow :** Undertow résout et décode automatiquement les valeurs multipart en fonction du charset spécifié dans le Content-Type de chaque partie.
- **ASP.NET Core :** L'analyseur multipart intégré respecte le paramètre charset, traduisant automatiquement les chaînes IBM037 ou UTF-7 en chaînes standard UTF-8/Unicode avant qu'elles n'atteignent les contrôleurs de l'application.

Lorsque la requête parvient au backend :
1. Le backend analyse la Partie 1, voit `charset=ibm037`, et décode les octets EBCDIC en texte standard : `' OR 1=1 --`.
2. L'application traite le paramètre, déclenchant l'injection SQL ou la faille XSS.
3. Le WAF reste complètement ignorant de l'attaque car il a seulement validé le `charset=utf-8` de la Partie 2 et n'a vu que des octets EBCDIC incompréhensibles dans la Partie 1.

---

## Correction du Core Rule Set et détails du correctif

Une fois la vulnérabilité divulguée, l'équipe OWASP CRS a publié un correctif qui a restructuré la manière dont la règle 922110 valide les requêtes multipart. Au lieu d'une unique variable globale, la validation a été divisée en trois phases distinctes :

1. **Initialisation du compteur :** Initialise un compteur unique pour suivre les différentes parties.
2. **Stockage distinct :** Extrait et écrit le Content-Type et le charset de *chaque* partie dans une variable distincte associée à son index (par exemple, `TX:multipart_content_type_0`, `TX:multipart_content_type_1`, etc.).
3. **Validation complète :** Parcourt toutes les variables enregistrées et valide chacune individuellement par rapport à la liste blanche. Si une seule partie échoue au contrôle, le WAF bloque la requête.

La logique corrigée se comporte comme le pseudo-code suivant :

```python
# Logique corrigée
content_types = []

# Collecte de tous les Content-Types dans une liste
for part in request.multipart_parts:
    content_types.append(part.headers.get("Content-Type"))

# Validation de chaque partie
for ct in content_types:
    if not is_whitelisted(ct):
        block_request()
        break
```

Ce correctif élimine définitivement la possibilité d'écrasement de variable, garantissant que les attaques par confusion de charset sont interceptées et bloquées au niveau du WAF.