Cloud

Azure WAF : diagnostiquer un upload de fichier avant d'augmenter les limites

Un runbook de production pour séparer limite d'upload, taille du corps, profondeur d'inspection, règle WAF et refus du backend avant un changement réversible.

05 sept. 2026 azurewafapplication-gatewayfile-uploadrequest-bodykqlsecurityobservabilityrunbookrollbackproduction

Un nouveau parcours de dépôt de documents passe en production derrière Azure Application Gateway WAF. Les fichiers de quelques mégaoctets arrivent bien, mais les plus volumineux reçoivent un 403. L’API ne journalise aucune requête et les équipes proposent d’augmenter immédiatement la limite d’upload ou de désactiver l’inspection du corps.

Ces actions ne répondent pas à la même cause. Le WAF peut appliquer une limite de fichier, une limite globale de corps, une profondeur d’inspection, ou une règle gérée déclenchée par le contenu. Application Gateway et le backend ont aussi leurs propres limites. Le but de ce runbook est d’identifier la couche qui refuse la requête, d’autoriser uniquement le volume métier attendu et de garder une marche arrière vérifiable.

Figer le contrat de l’upload

Commencez par décrire le parcours qui doit fonctionner. Une taille maximale n’est pas un réglage isolé : elle dépend du format HTTP, du type de document, du temps de traitement et de la capacité du backend.

text upload-incident-scope.txt
Incident: inc-20260905-001
Entrée: Application Gateway v2 avec policy WAF
Hostname: documents.example.com
Route: POST /api/documents
Format attendu: multipart/form-data avec un fichier nommé
Taille métier maximale attendue: à confirmer avec le propriétaire produit
Symptôme: petits fichiers acceptés, fichiers plus grands en 403
Dernier changement: application, policy WAF, ruleset, gateway ou backend

Conserver avant action
heure UTC et transaction ID d'une requête en échec
taille exacte du corps et du fichier
Content-Type et présence du filename
réponse observée au client
logs WAF, accès gateway et application
valeurs actuelles de la policy et version du ruleset

Ne pas faire pendant la collecte
désactiver globalement l'inspection du corps
passer toute la policy en Detection sans fenêtre bornée
augmenter plusieurs limites en même temps
rejouer un document sensible comme simple test

Pour le WAF, un fichier est un élément multipart/form-data portant un attribut de nom de fichier. Un corps JSON volumineux ou un binaire envoyé avec un autre Content-Type relève de la limite du corps, pas nécessairement de la limite d’upload. Cette distinction doit être prouvée depuis le client ou une capture synthétique maîtrisée.

Lire les quatre réglages sans les confondre

Sur une policy WAF Application Gateway récente, quatre paramètres racontent des choses différentes : inspection du corps, application de la limite du corps, profondeur inspectée et application de la limite des fichiers. Relevez aussi le mode et le ruleset avant de modifier quoi que ce soit.

bash 01-waf-policy-settings.sh
set -eu

RG="rg-edge-prod"
POLICY="waf-documents-prod"

az network application-gateway waf-policy show --resource-group "$RG" --name "$POLICY" --query '{id:id,provisioningState:provisioningState,policySettings:policySettings,managedRules:managedRules.managedRuleSets}' --output json

requestBodyCheck dit si le contenu du corps est inspecté. requestBodyEnforcement et maxRequestBodySizeInKb gouvernent le refus des corps trop grands. requestBodyInspectLimitInKB indique jusqu’où les règles inspectent le corps. fileUploadEnforcement et fileUploadLimitInMb portent sur les fichiers reconnus comme tels.

Pour CRS 3.2 ou plus récent, ces contrôles peuvent être pilotés séparément. Cela ne signifie pas qu’ils sont interchangeables : relever la limite tout en conservant une profondeur d’inspection faible crée une zone acceptée mais non inspectée. Désactiver l’enforcement WAF ne supprime pas non plus les limites de la gateway ou de l’application.

Prouver quelle couche renvoie le refus

Une réponse vue comme 403 côté client n’est pas encore une preuve WAF. Corrélez les journaux de firewall et d’accès avec la même fenêtre, le même URI et, si disponible, le même transaction ID.

kusto 02-waf-upload-correlation.kql
let Window = 2h;
let Host = "documents.example.com";
let Path = "/api/documents";
AzureDiagnostics
| where TimeGenerated > ago(Window)
| where Category in ("ApplicationGatewayFirewallLog", "ApplicationGatewayAccessLog")
| extend host = tostring(host_s), uri = tostring(requestUri_s)
| extend action = tostring(action_s), ruleId = tostring(ruleId_s)
| extend message = tostring(message_s), detail = tostring(details_message_s)
| extend transactionId = tostring(transactionId_g)
| extend status = toint(httpStatus_d)
| where host == Host and uri startswith Path
| project TimeGenerated, Category, transactionId, action, status, ruleId, message, detail
| order by TimeGenerated asc

Classez le résultat avant toute modification :

  • un bloc WAF associé à une taille ou à un parsing du corps oriente vers les réglages de policy ;
  • une règle gérée qui matche une valeur dans le formulaire est un problème de règle ou d’exclusion, pas de taille ;
  • un accès gateway transmis avec un 413, un timeout ou un 5xx sans bloc WAF oriente vers la gateway ou le backend ;
  • aucune trace exploitable impose d’abord de rétablir les diagnostics et la corrélation.

Les logs peuvent contenir des fragments de données envoyées. Limitez les projections, appliquez le scrubbing prévu et utilisez des fichiers synthétiques sans donnée métier pendant le diagnostic.

Construire une matrice de canaris

Un seul fichier qui échoue ne permet pas de trouver la frontière. Préparez une matrice qui ne change qu’une dimension à la fois et exécutez-la sur un environnement représentatif ou dans une fenêtre de production bornée.

yaml upload-canary-matrix.yml
canaries:
common:
  route: POST /api/documents
  identity: test principal with normal upload role
  content: synthetic non-sensitive bytes
  correlation_header: x-test-correlation-id
cases:
  - name: multipart-small
    content_type: multipart/form-data
    file_size: below_business_limit
    expected: accepted_and_scanned
  - name: multipart-near-boundary
    content_type: multipart/form-data
    file_size: near_configured_file_limit
    expected: explicit_boundary_result
  - name: multipart-over-boundary
    content_type: multipart/form-data
    file_size: above_configured_file_limit
    expected: controlled_rejection
  - name: json-same-body-size
    content_type: application/json
    body_size: same_as_near_boundary_case
    expected: classified_by_request_body_policy
collect:
  - client_status_and_duration
  - content_length_and_content_type
  - waf_transaction_and_rule
  - gateway_status_and_latency
  - backend_request_id_or_absence
  - cpu_memory_and_processing_time

Placez les cas juste sous, près de et au-dessus de la limite configurée. Ne cherchez pas une valeur maximale théorique pendant l’incident. Cherchez la plus petite plage qui démontre le besoin métier et le comportement de chaque couche.

Choisir un changement borné

Le bon changement dépend de la preuve collectée. Si les fichiers légitimes dépassent la limite WAF mais restent dans le contrat applicatif, augmentez uniquement fileUploadLimitInMb jusqu’à une valeur justifiée. Si un corps non multipart est concerné, travaillez sur la limite du corps. Si une règle gérée bloque un champ précis, revenez au diagnostic de faux positif et à une exclusion ciblée.

text upload-change-decision.txt
Augmenter une limite
format HTTP et couche de refus prouvés
maximum métier documenté
backend capable de recevoir, analyser et stocker ce volume
profondeur d'inspection cohérente avec le risque
protection anti-abus et concurrence d'upload validées

Refuser le changement
propriétaire métier incapable de borner la taille
règle gérée déclenchée par le contenu
limite backend ou timeout avant traitement
logs absents ou transaction non corrélable
capacité stockage, mémoire ou antivirus non validée

Exception temporaire
route, méthode, identité et fenêtre précisément bornées
contrôle compensatoire côté application
expiration automatique et propriétaire désigné
policy précédente exportée avant action

Évitez de désactiver l’inspection du corps pour résoudre un simple problème de taille. Cette action rend les règles portant sur le corps inopérantes alors que la route accepte davantage de données. Si une exception est indispensable, elle doit rester locale au parcours et vivre moins longtemps que le correctif.

Valider et préparer le rollback

Exportez l’état complet de la policy avant le changement. Après mise à jour, rejouez la matrice, vérifiez que le fichier métier maximal est accepté, qu’un fichier au-dessus du contrat reste refusé et qu’un payload synthétique destiné à une règle de test est toujours inspecté. Surveillez aussi latence, mémoire, erreurs et saturation du backend : déplacer le refus du WAF vers l’application n’est pas un succès.

Le rollback restaure ensemble les anciennes valeurs de fileUploadLimitInMb, maxRequestBodySizeInKb, requestBodyInspectLimitInKB, les deux flags d’enforcement, requestBodyCheck et le mode de policy. Conservez le ruleset et ses exclusions inchangés pendant ce test. Si les erreurs augmentent ou si une partie attendue du corps n’est plus inspectée, revenez à cet état puis maintenez l’upload volumineux désactivé côté produit jusqu’à correction.

Conclusion

Un 403 sur un fichier volumineux ne justifie pas une policy WAF plus permissive. Il faut d’abord distinguer fichier multipart, corps de requête, profondeur d’inspection, règle gérée et limite backend. La matrice de canaris transforme cette ambiguïté en décision mesurable.

La sortie du runbook est explicite : augmenter une seule limite jusqu’au besoin prouvé, corriger une règle ciblée, renforcer le backend, maintenir le refus, ou restaurer la policy précédente. L’upload n’est remis en service que lorsque l’équipe sait quelle taille elle accepte, quelle partie elle inspecte et comment elle revient en arrière.