El pla de resposta de 04-04 acabava sempre al mateix lloc. La Marta identifica el patró de l'atac —peticions a /buscar amb paràmetres aleatoris, des de 8.400 adreces de 61 països, amb un agent d'usuari falsificat idèntic en el 91 % dels casos— i llavors necessita una eina capaç d'actuar sobre això. No sobre una IP. No sobre un port. Sobre el contingut de la petició HTTP.

Ni sg-mercadofresco-tienda ni la NACL de les subxarxes públiques ho poden fer, i no per falta de funcions: és que treballen en una altra capa. Un grup de seguretat veu un paquet TCP cap al port 443 i no té manera de saber si a dins hi va una consulta d'un client de Getafe o una injecció SQL. Shield, per la seva banda, treballa amb volum i amb paquets malformats: un atac compost per peticions HTTP perfectament vàlides li resulta invisible.

AWS WAF (Web Application Firewall) és el tallafocs que sí que mira a dins. Inspecciona el mètode, la ruta, la cadena de consulta, les capçaleres, les galetes i el cos de cada petició, i decideix. Aquesta lliçó el munta davant de la distribució E2QWERTY123ABC i de l'alb-mercadofresco-tienda, i tanca amb això el mòdul de seguretat.

Advertiment. L'enfocament d'aquesta lliçó és estrictament defensiu: protegir la teva pròpia aplicació. Els exemples són didàctics i estan simplificats. Qualsevol configuració de WAF que hagi d'aplicar-se a un entorn real amb dades de clients —especialment si hi intervenen el RGPD o PCI DSS— ha de ser revisada per un professional de seguretat o de compliment abans de posar-se en producció. Una regla mal calibrada bloqueja clients legítims i pot costar més que l'atac del qual pretén protegir-te; el procediment de desplegament que es descriu aquí no és una recomanació opcional.

Contingut

  1. Què veu WAF que un grup de seguretat no pot veure
  2. On s'associa una Web ACL
  3. Components: Web ACL, regles i grups de regles
  4. Unitats de capacitat (WCU)
  5. Accions i prioritat d'avaluació
  6. El recorregut d'una petició
  7. Declaracions de coincidència
  8. Transformacions de text
  9. Grups de regles gestionades per AWS
  10. El mètode de desplegament: Count primer, Block després
  11. Regles basades en taxa
  12. La Web ACL de MercadoFresco, completa
  13. Associar la Web ACL a CloudFront i a l'ALB
  14. Registres de WAF i la seva anàlisi
  15. Mètriques i mostres de peticions bloquejades
  16. Falsos positius: diagnòstic i exclusions
  17. Cost i càlcul per a MercadoFresco
  18. Neteja
  19. La postura de seguretat de MercadoFresco
  20. El que falta: ningú no està mirant

Què veu WAF que un grup de seguretat no pot veure

Grup de seguretat NACL AWS WAF
Capa OSI 3/4 3/4 7 (aplicació)
Inspecciona IP, protocol, port IP, protocol, port Mètode, ruta, capçaleres, galetes, cos, consulta
Estat Amb estat Sense estat Amb estat per petició
S'aplica a ENI (instància, ALB, RDS) Subxarxa CloudFront, ALB, API Gateway, AppSync, Cognito
Detecta injecció SQL No No
Bloqueja per país No No
Limita peticions per IP No No
Distingeix navegador de bot No No
Cost Gratis Gratis 5 USD/mes + regles + peticions

Un exemple concret que resumeix la diferència. Aquesta petició:

GET /buscar?q=tomate'%20OR%20'1'='1 HTTP/1.1
Host: mercadofresco.example
User-Agent: sqlmap/1.7

Per a sg-mercadofresco-alb això és una connexió TCP al port 443 des d'una IP qualsevol: la permet, perquè és exactament el que ha de permetre. Per a WAF és una petició amb un patró d'injecció SQL a la cadena de consulta i una eina d'atac declarada a l'agent d'usuari: la bloqueja.

Les tres eines són complementàries i cap no en substitueix una altra:

Amenaça Eina
Accés al port 5432 de la base de dades des d'internet Grup de seguretat (03-02)
Inundació volumètrica de 200 Gbps Shield (04-04)
Injecció SQL, XSS, scripts maliciosos WAF
Inundació HTTP contra /buscar WAF amb regla per taxa
Credencials robades i utilitzades correctament IAM (04-01), no hi ha tallafocs que ajudi

On s'associa una Web ACL

Servei Àmbit Notes
CloudFront CLOUDFRONT La Web ACL s'ha de crear a us-east-1
Application Load Balancer REGIONAL A la regió de l'ALB
API Gateway (REST) REGIONAL Per etapa
AppSync REGIONAL GraphQL
Cognito REGIONAL Grups d'usuaris
App Runner, Verified Access REGIONAL Menys habituals

La primera fila amaga el parany que més temps fa perdre: una Web ACL per a CloudFront es crea a us-east-1 amb --scope CLOUDFRONT, sense importar on sigui la resta de la teva infraestructura. És la mateixa regla que ja vam veure amb els certificats d'ACM a 03-04 i amb les mètriques de CloudFront a 04-04. Una Web ACL regional creada a eu-west-1 no es pot associar a una distribució.

Per a MercadoFresco muntem dues Web ACL:

Web ACL Àmbit Regió Protegeix
waf-mercadofresco-cdn CLOUDFRONT us-east-1 Distribució E2QWERTY123ABC
waf-mercadofresco-alb REGIONAL eu-west-1 alb-mercadofresco-tienda

Per què dues, si tot el trànsit passa per CloudFront? Per defensa en profunditat. La de l'ALB és la xarxa de seguretat per si algú aconsegueix saltar-se la vora, i protegeix també el trànsit intern o de proves que arribi directament. És el mateix raonament pel qual a 04-04 vam tancar l'ALB amb la capçalera verificada a més de la llista de prefixos.

Components: Web ACL, regles i grups de regles

flowchart TD
    A["Web ACL<br/>waf-mercadofresco-cdn"] --> B["Regla 1 - prioritat 0<br/>IP set: bloqueig permanent"]
    A --> C["Regla 2 - prioritat 10<br/>Grup gestionat:<br/>AmazonIpReputationList"]
    A --> D["Regla 3 - prioritat 20<br/>Grup gestionat:<br/>CommonRuleSet"]
    A --> E["Regla 4 - prioritat 30<br/>Grup gestionat:<br/>SQLiRuleSet"]
    A --> F["Regla 5 - prioritat 40<br/>Per taxa: /login"]
    A --> G["Regla 6 - prioritat 50<br/>Per taxa: /api/pedidos"]
    A --> H["Accio per defecte:<br/>ALLOW"]

Els quatre conceptes:

  • Web ACL (Web Access Control List): el contenidor. Té una llista ordenada de regles i una acció per defecte que s'aplica al que no coincideix amb cap.
  • Regla: una declaració de coincidència més una acció. Té una prioritat numèrica.
  • Grup de regles: un conjunt reutilitzable de regles. Poden ser gestionats per AWS, gestionats per tercers (del Marketplace) o propis.
  • IP set i regex pattern set: llistes reutilitzables d'adreces o d'expressions regulars que les regles referencien per ARN.

L'acció per defecte defineix el model:

Acció per defecte Model Quan
Allow Llista negra: es permet tot llevat del que coincideix amb una regla Llocs públics com una botiga
Block Llista blanca: es bloqueja tot llevat del que es permet explícitament Taulers interns, API privades

MercadoFresco fa servir Allow per defecte a la botiga pública. Per a admin.mercadofresco.example, el subdomini d'administració que vam crear a 03-05, el model correcte seria Block per defecte amb una regla que només permeti la IP de l'oficina 192.168.10.0/24 i el seu rang de sortida públic.

Unitats de capacitat (WCU)

Cada regla consumeix WCU (Web ACL Capacity Units), una mesura del cost de còmput d'avaluar-la. Una Web ACL té un límit de 1.500 WCU per defecte (ampliable a 5.000 si se sol·licita).

Element WCU aproximades
Coincidència d'IP set 1
Coincidència de cadena simple 1-5
Cada transformació de text +10 per cadascuna
Expressió regular 25-35
Coincidència geogràfica 1
Regla per taxa 2
AWSManagedRulesCommonRuleSet 700
AWSManagedRulesKnownBadInputsRuleSet 200
AWSManagedRulesSQLiRuleSet 200
AWSManagedRulesLinuxRuleSet 200
AWSManagedRulesAmazonIpReputationList 25
AWSManagedRulesAnonymousIpList 50
AWSManagedRulesBotControlRuleSet 50

El pressupost s'esgota abans del que sembla: CommonRuleSet tot sol en consumeix gairebé la meitat. Convé planificar-ho:

aws wafv2 describe-managed-rule-group \
  --vendor-name AWS --name AWSManagedRulesCommonRuleSet \
  --scope CLOUDFRONT --region us-east-1 \
  --query '{Capacitat:Capacity, Regles:Rules[].Name}' \
  --profile mercadofresco-dev

I consultar quant en portes consumit:

aws wafv2 get-web-acl --name waf-mercadofresco-cdn --scope CLOUDFRONT \
  --id <id> --region us-east-1 --query 'WebACL.Capacity' --profile mercadofresco-dev

Accions i prioritat d'avaluació

Acció Què fa Cost per al client
Allow Permet la petició i atura l'avaluació Cap
Block Rebutja amb 403 (personalitzable) i atura l'avaluació Petició perduda
Count Compta i continua avaluant. No bloqueja res Cap
CAPTCHA Mostra un repte visual; si es resol, emet un testimoni vàlid uns minuts Fricció alta
Challenge Repte silenciós de JavaScript; el navegador el resol sol Fricció gairebé nul·la

Dos comportaments que cal tenir molt clars:

  1. Allow i Block són terminals. Tan bon punt una regla coincideix amb una d'aquestes dues accions, l'avaluació s'atura i no es miren les regles següents. Per això la prioritat importa tant.
  2. Count mai no és terminal. Registra la coincidència i continua. És la base del mètode de desplegament segur.

Sobre CAPTCHA enfront de Challenge: Challenge és gairebé sempre l'opció correcta. Verifica que hi ha un navegador real executant JavaScript sense molestar l'usuari, filtra la pràctica totalitat dels bots simples i no fa mal a la conversió. CAPTCHA reserva'l per a accions crítiques —/login després de diversos intents fallits, o el pas final del pagament si detectes frau— perquè cada CAPTCHA mostrat a un client real és un percentatge de vendes perdudes.

La prioritat s'avalua de menor a major número. No han de ser consecutius: fer servir 0, 10, 20, 30 en lloc de 0, 1, 2, 3 permet inserir regles després sense renumerar-ho tot.

El recorregut d'una petició

flowchart TD
    A["Peticio HTTPS d'un client"] --> B{"Prioritat 0<br/>IP a la llista de bloqueig?"}
    B -->|"Si"| Z["BLOCK 403 - fi"]
    B -->|"No"| C{"Prioritat 10<br/>IP amb mala reputacio?"}
    C -->|"Si"| Z
    C -->|"No"| D{"Prioritat 20<br/>CommonRuleSet: XSS, rutes,<br/>mida, agents dolents?"}
    D -->|"Si"| Z
    D -->|"No"| E{"Prioritat 30<br/>Injeccio SQL?"}
    E -->|"Si"| Z
    E -->|"No"| F{"Prioritat 40<br/>Mes de 100 peticions a /login<br/>en 5 min des d'aquesta IP?"}
    F -->|"Si"| Y["CAPTCHA"]
    F -->|"No"| G{"Prioritat 50<br/>Mes de 2000 a /api/pedidos<br/>en 5 min?"}
    G -->|"Si"| Z
    G -->|"No"| H["Accio per defecte: ALLOW"]
    H --> I["CloudFront serveix<br/>des de memoria cau o origen"]

Fixa't en l'ordre, que no és casual:

  • El més barat i més segur primer: comprovar una IP en un conjunt costa 1 WCU i no té falsos positius.
  • Les regles gestionades al mig: cares en WCU però molt eficaces.
  • Les regles per taxa al final: només té sentit comptar peticions que ja han superat tots els filtres anteriors.

Declaracions de coincidència

Declaració Què compara Exemple d'ús
ByteMatch Una cadena en un component de la petició L'agent d'usuari conté sqlmap
RegexPatternSet Una expressió regular Rutes que encaixen amb ^/admin/.*
SizeConstraint Mida d'un component Cos més gran de 8 KB a /api/pedidos
GeoMatch País d'origen (per GeoIP) Bloquejar països sense clients
IPSet IP o rang en una llista Permetre sempre l'oficina
SqliMatch Patrons d'injecció SQL Cadena de consulta i cos
XssMatch Patrons de scripting entre llocs Formularis
LabelMatch Etiqueta posada per una regla anterior Combinar regles gestionades amb lògica pròpia
RateBased Peticions per finestra de temps Protegir /login
And / Or / Not Combinació lògica «Espanya i ruta /admin»

Els components de la petició que es poden inspeccionar: UriPath, QueryString, SingleHeader, AllHeaders, Cookies, Method, Body (primers 8 KB a l'ALB, fins a 64 KB a CloudFront amb configuració), JsonBody (amb anàlisi de JSON) i SingleQueryArgument.

Exemple d'una regla pròpia que bloqueja l'accés a /admin des de fora d'Espanya:

{
  "Name": "AdminSoloDesdeEspana",
  "Priority": 5,
  "Statement": {
    "AndStatement": {
      "Statements": [
        {
          "ByteMatchStatement": {
            "SearchString": "/admin",
            "FieldToMatch": { "UriPath": {} },
            "TextTransformations": [
              { "Priority": 0, "Type": "LOWERCASE" },
              { "Priority": 1, "Type": "URL_DECODE" }
            ],
            "PositionalConstraint": "STARTS_WITH"
          }
        },
        {
          "NotStatement": {
            "Statement": {
              "GeoMatchStatement": { "CountryCodes": ["ES"] }
            }
          }
        }
      ]
    }
  },
  "Action": { "Block": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "AdminSoloDesdeEspana"
  }
}

AndStatement exigeix que es compleixin les dues condicions: la ruta comença per /admin i el país no és Espanya. PositionalConstraint admet EXACTLY, STARTS_WITH, ENDS_WITH, CONTAINS i CONTAINS_WORD.

Un avís sobre GeoMatchStatement: la geolocalització per IP no és infal·lible i una VPN l'esquiva sense esforç. Serveix per reduir soroll de fons, no com a control d'accés seriós. I compte amb bloquejar països: un client espanyol de vacances a França deixaria de poder comprar.

Transformacions de text

Són imprescindibles i s'obliden constantment. Una regla que busca la cadena <script> no coincideix amb cap d'aquestes variants, que fan exactament el mateix:

Variant Tècnica
<SCRIPT> Majúscules
%3Cscript%3E Codificació d'URL
<scr\x00ipt> Byte nul inserit
<script > Espais addicionals
&lt;script&gt; Entitats HTML
<scr<script>ipt> Imbricació

Les transformacions de text normalitzen el valor abans de comparar-lo:

Transformació Què fa
NONE Res
LOWERCASE Tot a minúscules
URL_DECODE Descodifica %3C a <
HTML_ENTITY_DECODE Descodifica &lt; a <
COMPRESS_WHITE_SPACE Col·lapsa espais múltiples en un
REMOVE_NULLS Elimina bytes nuls
CMD_LINE Normalitza sintaxi de línia d'ordres
BASE64_DECODE Descodifica base64
NORMALIZE_PATH Resol ../ i // a les rutes

S'apliquen en ordre de Priority i es poden encadenar. La combinació defensiva estàndard:

"TextTransformations": [
  { "Priority": 0, "Type": "URL_DECODE" },
  { "Priority": 1, "Type": "HTML_ENTITY_DECODE" },
  { "Priority": 2, "Type": "LOWERCASE" },
  { "Priority": 3, "Type": "REMOVE_NULLS" },
  { "Priority": 4, "Type": "COMPRESS_WHITE_SPACE" }
]

Una regla sense transformacions de text és una regla que s'esquiva escrivint en majúscules. Cada transformació costa 10 WCU, i aquest és el preu que la regla serveixi per a alguna cosa.

Els grups de regles gestionades per AWS ja inclouen les transformacions adequades: és una de les raons per començar per ells.

Grups de regles gestionades per AWS

AWS manté i actualitza aquests conjunts. La majoria són gratuïts: només es paguen les WCU que consumeixen dins de la teva Web ACL, no un càrrec a part.

Grup WCU Cost Què protegeix Per a MercadoFresco?
AWSManagedRulesCommonRuleSet 700 Gratis Base OWASP: XSS, rutes malicioses, mides anòmales, agents d'usuari buits Sí, essencial
AWSManagedRulesKnownBadInputsRuleSet 200 Gratis Entrades de vulnerabilitats conegudes (Log4Shell, deserialització) Sí, essencial
AWSManagedRulesSQLiRuleSet 200 Gratis Injecció SQL a consulta, cos i galetes : hi ha PostgreSQL al darrere
AWSManagedRulesLinuxRuleSet 200 Gratis LFI, inclusió d'/etc/passwd, execució d'ordres : les instàncies són Linux
AWSManagedRulesUnixRuleSet 100 Gratis Ordres de shell Redundant amb l'anterior
AWSManagedRulesWindowsRuleSet 200 Gratis PowerShell, ordres de Windows No: no hi ha Windows
AWSManagedRulesPHPRuleSet 100 Gratis Injecció de PHP Només si la botiga fa servir PHP
AWSManagedRulesWordPressRuleSet 100 Gratis Vulnerabilitats de WordPress No
AWSManagedRulesAmazonIpReputationList 25 Gratis IP amb activitat maliciosa coneguda i nodes de xarxes de bots , barata i molt eficaç
AWSManagedRulesAnonymousIpList 50 Gratis VPN, Tor, servidors intermediaris, sortides de núvols Amb compte: hi ha clients legítims amb VPN
AWSManagedRulesBotControlRuleSet 50 10 USD/mes + 1 USD/milió Classifica bots: cercadors, rastrejadors, eines Avaluar més endavant
AWSManagedRulesATPRuleSet 50 10 USD/mes + 1 USD/1.000 intents Account Takeover Prevention: farciment de credencials, credencials filtrades Interessant per a /login
AWSManagedRulesACFPRuleSet 50 De pagament Prevenció de registre fraudulent de comptes No de moment

Dos matisos sobre els de pagament:

  • Bot Control té dos nivells: el comú, que identifica bots que es declaren (cercadors, monitoratge), i el dirigit, que detecta bots que es fan passar per navegadors fent servir empremtes digitals i reptes. El dirigit és notablement més car i només compensa amb un problema real de scraping o de revenda.
  • ATP comprova les credencials de cada intent d'accés contra una base de dades de credencials filtrades, i detecta patrons de farciment. Per a MercadoFresco, amb comptes de clients que guarden adreces de repartiment, és la primera ampliació a considerar quan hi hagi pressupost.

Comença sempre pels gratuïts. CommonRuleSet + KnownBadInputs + SQLi + IpReputationList són 1.125 WCU, gratis, i cobreixen la immensa majoria del que veuràs.

El mètode de desplegament: Count primer, Block després

Aquest apartat és el més important de la lliçó. Activar CommonRuleSet en mode Block un divendres a la tarda i descobrir que una de les seves regles bloqueja el formulari de comanda és un desastre autoinfligit pitjor que qualsevol atac.

El procediment correcte té quatre fases:

flowchart TD
    A["Fase 1: crear la Web ACL amb TOTES<br/>les regles en Count i accio per defecte Allow"] --> B["Fase 2: deixar-la 7-14 dies,<br/>cobrint com a minim dos divendres"]
    B --> C["Fase 3: analitzar els registres<br/>Quines regles compten?<br/>Contra quines peticions reals?"]
    C --> D{"Hi ha falsos<br/>positius?"}
    D -->|"Si"| E["Afegir exclusions de regles<br/>concretes o afinar l'abast"]
    E --> C
    D -->|"No"| F["Fase 4: passar a Block<br/>d'una en una, comencant<br/>per la mes segura"]
    F --> G["Vigilar 24-48 h<br/>despres de cada canvi"]
    G --> H["Web ACL en produccio"]

Per què cada fase:

  • Fase 1. Count registra la coincidència i deixa passar la petició. Risc zero: si t'equivoques, no passa res.
  • Fase 2. Dues setmanes inclouen els dos pics de divendres i, amb sort, alguna campanya. Un desplegament basat en tres dies de trànsit de dimarts no veu els casos rars, i els casos rars són precisament els falsos positius.
  • Fase 3. Els registres diuen exactament quina regla hauria bloquejat quina petició real, amb el seu URI, les seves capçaleres i la seva IP. Aquesta és la informació que no es pot endevinar.
  • Fase 4. D'una en una. Si alguna cosa es trenca, saps exactament quina va ser.

Ordre recomanat per passar a Block, de menys a més risc de fals positiu:

Ordre Regla Risc
1 IP set de bloqueig propi Nul: l'has posat tu
2 AmazonIpReputationList Molt baix
3 SQLiRuleSet Baix, si no tens formularis amb SQL legítim
4 KnownBadInputsRuleSet Baix
5 Regles per taxa Mitjà: cal calibrar el llindar
6 LinuxRuleSet Mitjà
7 CommonRuleSet El més alt: és el més ampli
8 AnonymousIpList Alt: bloqueja clients amb VPN

Una regla en Count dins d'un grup gestionat es configura amb RuleActionOverrides, que permet passar a Count regles individuals del grup sense desactivar-lo sencer:

{
  "Name": "ConjuntoComun",
  "Priority": 20,
  "Statement": {
    "ManagedRuleGroupStatement": {
      "VendorName": "AWS",
      "Name": "AWSManagedRulesCommonRuleSet",
      "RuleActionOverrides": [
        { "Name": "SizeRestrictions_BODY", "ActionToUse": { "Count": {} } },
        { "Name": "NoUserAgent_HEADER",     "ActionToUse": { "Count": {} } }
      ]
    }
  },
  "OverrideAction": { "None": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "ConjuntoComun"
  }
}

Dos camps que es confonen sempre:

  • OverrideAction (només en grups de regles): {"None": {}} respecta les accions del grup; {"Count": {}} posa tot el grup en mode compte. Durant la fase 1 es fa servir Count.
  • RuleActionOverrides: canvia l'acció de regles concretes dins del grup. És l'eina fina, la que es fa servir a la fase 3 per neutralitzar un fals positiu sense renunciar a la resta del grup.

Regles basades en taxa

Compten les peticions que coincideixen amb una condició dins d'una finestra de temps i actuen quan se supera un llindar. Són la resposta directa a l'atac de 04-04.

Paràmetre Valors Comentari
Limit 10 a 2.000.000.000 Peticions per finestra
EvaluationWindowSec 60, 120, 300, 600 Per defecte 300 (5 minuts)
AggregateKeyType IP, FORWARDED_IP, CUSTOM_KEY, CONSTANT Com s'agrupa el recompte
ScopeDownStatement Qualsevol declaració A quines peticions s'aplica el recompte

AggregateKeyType mereix explicació:

  • IP: per adreça d'origen. L'habitual.
  • FORWARDED_IP: fa servir X-Forwarded-For. Imprescindible si el WAF és a l'ALB darrere de CloudFront, perquè si no, totes les peticions semblaran venir de les IP de CloudFront i comptaran com una sola font.
  • CUSTOM_KEY: agrupa per galeta de sessió, capçalera, paràmetre de consulta o combinació. Permet limitar «per compte d'usuari» en lloc de «per IP», molt més precís davant d'atacants distribuïts.
  • CONSTANT: compta tot junt, sense agrupar. Serveix per posar un sostre global a una ruta cara.

ScopeDownStatement és la peça que la fa útil: sense ell, la regla comptaria totes les peticions del lloc, i un client que navega pel catàleg generaria centenars de peticions legítimes. Amb ell, només compten les que van a la ruta protegida.

Protecció de /login amb CAPTCHA en lloc de bloqueig:

{
  "Name": "LimiteLogin",
  "Priority": 40,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 100,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "ByteMatchStatement": {
          "SearchString": "/login",
          "FieldToMatch": { "UriPath": {} },
          "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
          "PositionalConstraint": "STARTS_WITH"
        }
      }
    }
  },
  "Action": { "Captcha": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "LimiteLogin"
  }
}

100 intents d'accés en 5 minuts des d'una mateixa IP és moltíssim per a una persona i poc per a un atac de farciment de credencials. S'escull Captcha i no Block deliberadament: una oficina amb NAT compartida pot superar el llindar legítimament, i un CAPTCHA la deixa passar mentre que un bloqueig li impediria comprar.

Protecció de /api/pedidos, on sí que bloquegem:

{
  "Name": "LimiteApiPedidos",
  "Priority": 50,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 2000,
      "EvaluationWindowSec": 300,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "ByteMatchStatement": {
          "SearchString": "/api/pedidos",
          "FieldToMatch": { "UriPath": {} },
          "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
          "PositionalConstraint": "STARTS_WITH"
        }
      }
    }
  },
  "Action": { "Block": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "LimiteApiPedidos"
  }
}

Com es calcula un llindar, amb les dades de MercadoFresco: el pic del divendres són 900 comandes per hora, és a dir 15 per minut o 75 a la finestra de 5 minuts repartides entre tots els clients. Un sol client que fes 2.000 peticions a /api/pedidos en 5 minuts no és un client. El llindar té un marge de més de 25 vegades sobre el trànsit total del lloc: és conservador expressament, perquè un llindar massa ajustat bloqueja abans el client rar que l'atacant.

La Web ACL de MercadoFresco, completa

Aquest és el fitxer de la Web ACL de CloudFront, en la fase 1 del desplegament: tot en Count.

{
  "Name": "waf-mercadofresco-cdn",
  "Scope": "CLOUDFRONT",
  "DefaultAction": { "Allow": {} },
  "Description": "Proteccio de la botiga de MercadoFresco - fase d'observacio",
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "wafMercadofrescoCdn"
  },
  "Rules": [
    {
      "Name": "BloqueoManual",
      "Priority": 0,
      "Statement": {
        "IPSetReferenceStatement": {
          "ARN": "arn:aws:wafv2:us-east-1:111122223333:global/ipset/ipset-mercadofresco-bloqueo/abc123"
        }
      },
      "Action": { "Block": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "BloqueoManual"
      }
    },
    {
      "Name": "ReputacionIp",
      "Priority": 10,
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesAmazonIpReputationList"
        }
      },
      "OverrideAction": { "Count": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "ReputacionIp"
      }
    },
    {
      "Name": "ConjuntoComun",
      "Priority": 20,
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesCommonRuleSet"
        }
      },
      "OverrideAction": { "Count": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "ConjuntoComun"
      }
    },
    {
      "Name": "EntradasMaliciosas",
      "Priority": 25,
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesKnownBadInputsRuleSet"
        }
      },
      "OverrideAction": { "Count": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "EntradasMaliciosas"
      }
    },
    {
      "Name": "InyeccionSql",
      "Priority": 30,
      "Statement": {
        "ManagedRuleGroupStatement": {
          "VendorName": "AWS",
          "Name": "AWSManagedRulesSQLiRuleSet"
        }
      },
      "OverrideAction": { "Count": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "InyeccionSql"
      }
    },
    {
      "Name": "LimiteLogin",
      "Priority": 40,
      "Statement": {
        "RateBasedStatement": {
          "Limit": 100,
          "EvaluationWindowSec": 300,
          "AggregateKeyType": "IP",
          "ScopeDownStatement": {
            "ByteMatchStatement": {
              "SearchString": "/login",
              "FieldToMatch": { "UriPath": {} },
              "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
              "PositionalConstraint": "STARTS_WITH"
            }
          }
        }
      },
      "Action": { "Count": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "LimiteLogin"
      }
    },
    {
      "Name": "LimiteApiPedidos",
      "Priority": 50,
      "Statement": {
        "RateBasedStatement": {
          "Limit": 2000,
          "EvaluationWindowSec": 300,
          "AggregateKeyType": "IP",
          "ScopeDownStatement": {
            "ByteMatchStatement": {
              "SearchString": "/api/pedidos",
              "FieldToMatch": { "UriPath": {} },
              "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
              "PositionalConstraint": "STARTS_WITH"
            }
          }
        }
      },
      "Action": { "Count": {} },
      "VisibilityConfig": {
        "SampledRequestsEnabled": true,
        "CloudWatchMetricsEnabled": true,
        "MetricName": "LimiteApiPedidos"
      }
    }
  ]
}

Observacions sobre aquest document:

  • La regla de prioritat 0 sí que està en Block, i és l'única. És l'IP set de bloqueig manual: el va omplint la Marta a mà durant un incident, així que no hi ha risc de fals positiu.
  • Totes les altres estan en Count. Els grups gestionats amb "OverrideAction": {"Count": {}} i les regles pròpies amb "Action": {"Count": {}}. Són camps diferents per al mateix efecte, i confondre'ls és un error habitual.
  • SampledRequestsEnabled: true a totes. Sense això no es poden veure les mostres de peticions a la consola, que és justament el que cal a la fase d'anàlisi.
  • Capacitat total: 1 (IP set) + 25 + 700 + 200 + 200 + 2 + 2 = 1.130 WCU de les 1.500 disponibles. Queda marge per a unes poques regles més, però no per a LinuxRuleSet (200) i AnonymousIpList (50) alhora sense demanar ampliació.

Creació per CLI:

# 1. L'IP set de bloqueig manual (buit al principi)
aws wafv2 create-ip-set \
  --name ipset-mercadofresco-bloqueo \
  --scope CLOUDFRONT --region us-east-1 \
  --ip-address-version IPV4 --addresses \
  --description "Bloqueig manual durant incidents" \
  --tags Key=Proyecto,Value=mercadofresco Key=Componente,Value=waf \
  --profile mercadofresco-dev

# 2. La Web ACL
aws wafv2 create-web-acl \
  --cli-input-json file:///tmp/waf-mercadofresco-cdn.json \
  --region us-east-1 \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=waf Key=Propietario,Value=marta \
         Key=CentroCoste,Value=tecnologia \
  --profile mercadofresco-dev

Afegir una IP al conjunt de bloqueig durant un incident requereix el LockToken, que actua com a control de concurrència optimista:

TOKEN=$(aws wafv2 get-ip-set --name ipset-mercadofresco-bloqueo \
  --scope CLOUDFRONT --id <id> --region us-east-1 \
  --query 'LockToken' --output text --profile mercadofresco-dev)

aws wafv2 update-ip-set --name ipset-mercadofresco-bloqueo \
  --scope CLOUDFRONT --id <id> --region us-east-1 \
  --addresses 203.0.113.45/32 198.51.100.0/24 \
  --lock-token "$TOKEN" --profile mercadofresco-dev

Compte: update-ip-set reemplaça la llista sencera, no hi afegeix. Cal llegir la llista actual, afegir la nova adreça i enviar el conjunt complet. És un error clàssic que esborra silenciosament bloquejos anteriors.

Associar la Web ACL a CloudFront i a l'ALB

CloudFront: s'associa actualitzant la configuració de la distribució amb l'ARN de la Web ACL. El desplegament triga uns minuts a propagar-se a tots els punts de presència.

ALB: s'associa directament i l'efecte és immediat.

aws wafv2 associate-web-acl \
  --web-acl-arn arn:aws:wafv2:eu-west-1:111122223333:regional/webacl/waf-mercadofresco-alb/abc123 \
  --resource-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:loadbalancer/app/alb-mercadofresco-tienda/50dc6c495c0c9188 \
  --region eu-west-1 --profile mercadofresco-dev

# Comprovar quins recursos protegeix una Web ACL
aws wafv2 list-resources-for-web-acl \
  --web-acl-arn arn:aws:wafv2:eu-west-1:111122223333:regional/webacl/waf-mercadofresco-alb/abc123 \
  --region eu-west-1 --profile mercadofresco-dev

Recorda el detall de les regles per taxa a la Web ACL de l'ALB: com que tot el trànsit arriba des de CloudFront, cal fer servir AggregateKeyType: FORWARDED_IP amb la capçalera X-Forwarded-For, o el recompte no distingirà clients.

Registres de WAF i la seva anàlisi

Sense registres, WAF és una caixa negra i la fase 3 del desplegament és impossible. Hi ha tres destins:

Destí Latència Cost Quan
CloudWatch Logs Segons Més gran per GB Anàlisi immediata, alarmes
S3 Minuts El més barat Retenció llarga, anàlisi amb Athena
Kinesis Data Firehose Segons Mitjà Enviar a un SIEM extern

MercadoFresco fa servir CloudWatch Logs durant les dues setmanes d'observació —perquè necessita consultar de seguida— i després manté l'enviament a mercadofresco-registros-web per a l'històric.

# El grup s'ha d'anomenar obligatoriament aws-waf-logs-*
aws logs create-log-group --log-group-name aws-waf-logs-mercadofresco \
  --region us-east-1 --profile mercadofresco-dev

aws logs put-retention-policy --log-group-name aws-waf-logs-mercadofresco \
  --retention-in-days 30 --region us-east-1 --profile mercadofresco-dev

aws wafv2 put-logging-configuration \
  --logging-configuration '{
    "ResourceArn": "arn:aws:wafv2:us-east-1:111122223333:global/webacl/waf-mercadofresco-cdn/abc123",
    "LogDestinationConfigs": ["arn:aws:logs:us-east-1:111122223333:log-group:aws-waf-logs-mercadofresco"],
    "RedactedFields": [
      {"SingleHeader": {"Name": "authorization"}},
      {"SingleHeader": {"Name": "cookie"}},
      {"SingleQueryArgument": {"Name": "password"}}
    ]
  }' \
  --region us-east-1 --profile mercadofresco-dev

El nom del grup ha de començar per aws-waf-logs- o la configuració es rebutja sense explicar per què. I RedactedFields no és opcional en un entorn amb dades de clients: sense això, les galetes de sessió i les capçaleres d'autorització acabarien en text pla als registres, creant exactament el problema que vam resoldre a 04-03. És un punt de compliment del RGPD.

Consultes de la fase 3 amb CloudWatch Logs Insights:

-- Quines regles estan comptant i quantes vegades?
fields @timestamp, terminatingRuleId, action, httpRequest.uri
| filter action = "COUNT" or terminatingRuleId != "Default_Action"
| stats count(*) as coincidencies by terminatingRuleId
| sort coincidencies desc
-- Detall de les peticions que una regla concreta hauria bloquejat
fields @timestamp, httpRequest.clientIp, httpRequest.uri, httpRequest.country,
       httpRequest.headers.0.value
| filter @message like /SizeRestrictions_BODY/
| sort @timestamp desc
| limit 100
-- Les 20 IP amb mes coincidencies
fields httpRequest.clientIp
| filter action = "COUNT"
| stats count(*) as intents by httpRequest.clientIp
| sort intents desc
| limit 20

Aquesta primera consulta és la que decideix el desplegament: si SQLiRuleSet compta 4.000 vegades al dia i totes les peticions són intents evidents d'injecció, passa a Block sense dubtar. Si CommonRuleSet compta 300 vegades i 280 són peticions legítimes del teu propi formulari de comanda, tens un fals positiu per resoldre abans.

Mètriques i mostres de peticions bloquejades

WAF publica a l'espai de noms AWS/WAFV2:

Mètrica Què mesura
AllowedRequests Peticions permeses
BlockedRequests Peticions bloquejades
CountedRequests Coincidències en mode Count
CaptchaRequests Reptes CAPTCHA servits
PassedRequests Que van passar un repte
aws cloudwatch get-metric-statistics \
  --namespace AWS/WAFV2 --metric-name BlockedRequests \
  --dimensions Name=WebACL,Value=waf-mercadofresco-cdn Name=Rule,Value=ALL Name=Region,Value=CloudFront \
  --start-time 2026-08-01T00:00:00Z --end-time 2026-08-02T00:00:00Z \
  --period 3600 --statistics Sum \
  --region us-east-1 --profile mercadofresco-dev

I una alarma que avisa quan alguna cosa canvia bruscament, cap al mateix tema alertas-mercadofresco de 04-04:

aws cloudwatch put-metric-alarm \
  --alarm-name mercadofresco-waf-bloqueos-anomalos \
  --alarm-description "Pic de bloquejos al WAF: possible atac o fals positiu nou" \
  --namespace AWS/WAFV2 --metric-name BlockedRequests \
  --dimensions Name=WebACL,Value=waf-mercadofresco-cdn Name=Rule,Value=ALL Name=Region,Value=CloudFront \
  --statistic Sum --period 300 --evaluation-periods 2 \
  --threshold 5000 --comparison-operator GreaterThanThreshold \
  --alarm-actions arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco \
  --region us-east-1 --profile mercadofresco-dev

Fixa't que un pic de bloquejos té dues lectures possibles: t'estan atacant, o acabes d'introduir un fals positiu. Totes dues requereixen mirar, i per això l'alarma és útil en els dos casos.

A més, la consola ofereix les mostres de peticions: fins a 100 peticions de les últimes 3 hores que van coincidir amb cada regla, amb les seves capçaleres completes. És l'eina més ràpida per diagnosticar un fals positiu, i només funciona si SampledRequestsEnabled està a true.

aws wafv2 get-sampled-requests \
  --web-acl-arn arn:aws:wafv2:us-east-1:111122223333:global/webacl/waf-mercadofresco-cdn/abc123 \
  --rule-metric-name ConjuntoComun --scope CLOUDFRONT \
  --time-window StartTime=2026-08-02T08:00:00Z,EndTime=2026-08-02T10:00:00Z \
  --max-items 100 --region us-east-1 --profile mercadofresco-dev

Falsos positius: diagnòstic i exclusions

Un fals positiu és una petició legítima que una regla bloqueja. Els quatre casos que veuràs:

Símptoma Regla habitualment responsable Causa
No es poden pujar fotos grans de producte SizeRestrictions_BODY El cos supera el límit per defecte
Un producte amb cometes o apòstrofs al nom falla SQLi_QUERYARGUMENTS L'apòstrof sembla injecció
Una integració interna deixa de funcionar NoUserAgent_HEADER El client no envia agent d'usuari
Clients amb VPN corporativa no poden entrar AnonymousIpList La seva sortida està catalogada com a anònima

Procediment de diagnòstic:

  1. Identificar la regla exacta. El camp terminatingRuleId dels registres, o la columna de la consola, dona el nom concret dins del grup.
  2. Veure la petició completa a les mostres: URI, capçaleres, mida.
  3. Decidir l'abast mínim de l'excepció. No desactivis mai el grup sencer.
  4. Aplicar i verificar.

Tres maneres de resoldre-ho, de menys a més àmplia:

a) Passar només aquesta regla a Count:

"RuleActionOverrides": [
  { "Name": "SizeRestrictions_BODY", "ActionToUse": { "Count": {} } }
]

b) Excloure aquesta regla només en una ruta concreta, combinant amb ScopeDownStatement. És l'opció correcta: la regla continua protegint la resta del lloc.

{
  "Name": "ConjuntoComunSalvoSubidas",
  "Priority": 20,
  "Statement": {
    "ManagedRuleGroupStatement": {
      "VendorName": "AWS",
      "Name": "AWSManagedRulesCommonRuleSet",
      "ScopeDownStatement": {
        "NotStatement": {
          "Statement": {
            "ByteMatchStatement": {
              "SearchString": "/admin/productos/subir",
              "FieldToMatch": { "UriPath": {} },
              "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
              "PositionalConstraint": "STARTS_WITH"
            }
          }
        }
      }
    }
  },
  "OverrideAction": { "None": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "ConjuntoComunSalvoSubidas"
  }
}

c) Permetre explícitament abans, amb una regla de prioritat menor i acció Allow. És la més perillosa: Allow és terminal, així que aquesta petició se salta totes les regles següents, incloses les d'injecció SQL. Fes-la servir només per a trànsit plenament fiable, com el de la IP de l'oficina.

I la regla d'or: documenta cada excepció. Una Web ACL amb quinze exclusions que ningú no recorda per què hi són és una Web ACL que no protegeix res. Cada RuleActionOverride hauria de tenir associat un comentari al repositori amb la data, el motiu i una data de revisió.

Cost i càlcul per a MercadoFresco

Concepte Preu
Web ACL 5,00 USD al mes
Cada regla o grup de regles 1,00 USD al mes
Peticions 0,60 USD per milió
Grups gestionats gratuïts 0 USD (només compten com a regla)
Bot Control 10 USD/mes + 1 USD per milió analitzat
ATP 10 USD/mes + 1 USD per cada 1.000 intents d'accés
Registres a CloudWatch Logs / S3 Cost del servei de destí

Càlcul per a MercadoFresco. Punt clau: la major part del trànsit se serveix des de la memòria cau de CloudFront, i aquestes peticions sí que passen per WAF. Comptem 4 milions de peticions al mes a la distribució i 400.000 que arriben a l'ALB:

Concepte Quantitat Cost mensual
Web ACL de CloudFront 1 5,00 USD
Regles a la Web ACL de CloudFront 7 7,00 USD
Peticions (CloudFront) 4.000.000 2,40 USD
Web ACL de l'ALB 1 5,00 USD
Regles a la Web ACL de l'ALB 5 5,00 USD
Peticions (ALB) 400.000 0,24 USD
Registres a CloudWatch Logs ~3 GB ~1,50 USD
Total 26,14 USD/mes

Val la pena posar aquesta xifra en context:

Opció Cost mensual Què cobreix
Shield Standard 0 USD Volumètrics L3/L4
WAF amb aquesta configuració 26 USD Injecció SQL, XSS, bots, inundació L7
Shield Advanced 3.000 USD L'anterior més automatització i crèdits

26 dòlars al mes cobreixen l'amenaça més probable per a MercadoFresco. És la decisió de seguretat amb millor relació cost-benefici del mòdul després de KMS, i confirma l'avaluació de 04-04: els diners estaven molt més ben invertits aquí que en Shield Advanced.

Si el pressupost estrenyés, es pot retallar: fer servir una sola Web ACL a CloudFront i prescindir de la de l'ALB estalvia 10,24 USD al mes a canvi de perdre la defensa en profunditat. És un compromís defensable si l'ALB està ben tancat amb la capçalera verificada de 04-04.

Neteja

L'ordre importa: no es pot eliminar una Web ACL associada a un recurs.

# 1. Desassociar de cada recurs
aws wafv2 disassociate-web-acl \
  --resource-arn arn:aws:elasticloadbalancing:eu-west-1:111122223333:loadbalancer/app/alb-mercadofresco-tienda/50dc6c495c0c9188 \
  --region eu-west-1 --profile mercadofresco-dev

# A CloudFront: treure el WebACLId de la configuracio de la distribucio
#    i esperar que acabi el desplegament

# 2. Treure la configuracio de registres
aws wafv2 delete-logging-configuration \
  --resource-arn arn:aws:wafv2:us-east-1:111122223333:global/webacl/waf-mercadofresco-cdn/abc123 \
  --region us-east-1 --profile mercadofresco-dev

# 3. Eliminar la Web ACL (necessita el LockToken)
TOKEN=$(aws wafv2 get-web-acl --name waf-mercadofresco-cdn --scope CLOUDFRONT \
  --id abc123 --region us-east-1 --query 'LockToken' --output text --profile mercadofresco-dev)

aws wafv2 delete-web-acl --name waf-mercadofresco-cdn --scope CLOUDFRONT \
  --id abc123 --lock-token "$TOKEN" --region us-east-1 --profile mercadofresco-dev

# 4. Eliminar l'IP set i el grup de registres
aws wafv2 delete-ip-set --name ipset-mercadofresco-bloqueo --scope CLOUDFRONT \
  --id <id> --lock-token <token> --region us-east-1 --profile mercadofresco-dev

aws logs delete-log-group --log-group-name aws-waf-logs-mercadofresco \
  --region us-east-1 --profile mercadofresco-dev

Si deixes la Web ACL creada però sense associar, continua costant 5 USD al mes més 1 USD per regla. És un dels càrrecs fantasma més comuns a les factures d'AWS.

La postura de seguretat de MercadoFresco

Amb aquesta lliçó es tanca el mòdul. Aquesta és l'arquitectura de seguretat completa:

flowchart TD
    A["Client a internet"] --> B["Route 53 mercadofresco.example<br/>Shield Standard"]
    B --> C["CloudFront E2QWERTY123ABC<br/>Shield Standard + memoria cau + TLS d'ACM"]
    C --> D["AWS WAF waf-mercadofresco-cdn<br/>Regles gestionades + per taxa"]
    D --> E["ALB alb-mercadofresco-tienda<br/>Capcalera verificada + prefixos CloudFront<br/>waf-mercadofresco-alb"]
    E --> F["ASG asg-mercadofresco-tienda<br/>Subxarxes privades app-a/-b<br/>rol-mercadofresco-tienda: minim privilegi"]
    F --> G["Secrets Manager<br/>mercadofresco/produccion/rds/mfadmin<br/>rotacio cada 30 dies"]
    F --> H["Parameter Store<br/>/mercadofresco/produccion/*"]
    G --> I["RDS mercadofresco-pedidos<br/>Multi-AZ, xifrada amb KMS<br/>Subxarxes datos-a/-b sense sortida"]
    F --> I
    C -.->|"OAC oac-mercadofresco-catalogo"| J["S3 mercadofresco-catalogo-fotos<br/>SSE-KMS + clau de bucket"]
    K["KMS alias/mercadofresco-datos<br/>Marta administra, els rols usen<br/>rotacio anual"] -.-> I
    K -.-> J
    K -.-> G

Les quatre capes, i el que aporta cadascuna:

Capa Serveis Què garanteix
Identitat IAM, rols, grups, MFA Ningú no té més permisos dels que necessita; res no porta claus permanents
Dades KMS, SSE-KMS, EBS i RDS xifrats Un disc, una instantània o un bucket exposat no revelen res
Secrets Secrets Manager, Parameter Store Cap credencial viu en un fitxer; roten soles cada 30 dies
Vora Shield Standard, WAF, OAC, SG El trànsit maliciós es descarta lluny i la superfície exposada és mínima

I el cost total del mòdul 4:

Servei Cost mensual
IAM, Identity Center, Access Analyzer extern 0,00 USD
KMS (1 clau + peticions) 1,00 USD
Secrets Manager (2 secrets) + Parameter Store 0,82 USD
Shield Standard 0,00 USD
WAF (2 Web ACL, 12 regles, 4,4 M peticions) 26,14 USD
Alarmes de CloudWatch 0,40 USD
Total 28,36 USD/mes

Menys de 30 dòlars al mes per identitats mínimes, dades xifrades, secrets rotats i una vora protegida. Comparat amb els 3.000 de Shield Advanced —o amb el cost d'una bretxa notificable sota el RGPD— és la millor decisió d'inversió de tot el curs.

Errors Habituals i Consells

Crear la Web ACL de CloudFront a la regió equivocada. Ha de ser us-east-1 amb --scope CLOUDFRONT. Una Web ACL regional no es pot associar a una distribució, i l'error no ho diu de manera evident.

Desplegar directament en Block. L'error més car d'aquesta disciplina. Dues setmanes en Count, anàlisi dels registres, exclusions i només llavors Block, d'una regla en una.

Oblidar les transformacions de text. Una regla que busca <script> sense LOWERCASE ni URL_DECODE s'esquiva amb %3CSCRIPT%3E. Costen 10 WCU cadascuna i són el que fa que la regla serveixi per a alguna cosa.

Confondre OverrideAction amb Action. Els grups de regles fan servir OverrideAction; les regles pròpies fan servir Action. Posar el camp equivocat fa que l'API ho rebutgi o, pitjor, que el mode Count que creies haver posat no hi sigui.

Fer servir AggregateKeyType: IP al WAF de l'ALB darrere de CloudFront. Totes les peticions semblaran venir de les IP de CloudFront i la regla per taxa comptarà tot el trànsit del lloc com una sola font. Allà cal fer servir FORWARDED_IP.

Esgotar les WCU sense adonar-se'n. CommonRuleSet consumeix 700 de 1.500. Planifica el pressupost abans d'afegir grups, i consulta la capacitat amb describe-managed-rule-group.

Reemplaçar l'IP set en lloc d'ampliar-lo. update-ip-set substitueix la llista sencera. Llegeix primer, afegeix i envia el conjunt complet.

Posar una regla Allow àmplia al capdamunt. Allow és terminal: aquesta petició se salta totes les regles d'injecció SQL i de taxa que vinguin després. Reserva l'Allow explícit per a trànsit plenament fiable.

No configurar RedactedFields als registres. Les galetes de sessió i les capçaleres d'autorització acabarien en text pla, desfent la feina de 04-03 i creant un problema de RGPD.

Deixar una Web ACL creada i sense associar. Continua costant 5 USD al mes més 1 USD per regla.

Consell: versiona la Web ACL a Git. El JSON complet al repositori, revisat per una altra persona abans d'aplicar-se, amb un comentari per cada exclusió explicant data, motiu i revisió prevista. A 09-01 i 09-02 passarà a ser una plantilla de CloudFormation o un constructe de CDK.

Consell: revisa les exclusions cada trimestre. S'acumulen, i cadascuna és un forat que algú va obrir per una raó que probablement ja no existeix.

Consell: prefereix Challenge a CAPTCHA. Filtra bots simples sense fricció per al client. Guarda el CAPTCHA per a les rutes realment crítiques.

Consell: tingues preparada per endavant la regla d'emergència. Un JSON a punt amb una regla per taxa agressiva sobre tot el lloc, que la Marta pugui aplicar en un minut durant un incident, estalvia mitja hora d'escriure sota pressió.

Exercicis

Exercici 1: dissenyar la Web ACL d'admin.mercadofresco.example

El subdomini d'administració que vam crear a 03-05 només ha de ser accessible des de l'oficina (192.168.10.0/24 interna, amb sortida pública 203.0.113.10/32) i des del teletreball de la Marta, amb IP dinàmica espanyola. Requisits: ningú més no hi ha d'arribar; els intents d'accés des d'altres països ni tan sols han de tocar l'aplicació; cal registrar tots els intents rebutjats; i la solució no pot impedir que la Marta treballi des de casa quan li canviï la IP.

Dissenya la Web ACL: model d'acció per defecte, llista de regles amb prioritats i accions, i justifica el compromís entre seguretat i usabilitat de l'última condició.

Exercici 2: analitzar la fase d'observació i decidir el pas a Block

Després de 14 dies amb waf-mercadofresco-cdn en Count, els registres donen aquests resultats:

Regla Coincidències Mostra de peticions
AmazonIpReputationList 12.400 Escanejos a /wp-login.php, /.env, /admin.php
SQLiRuleSet 3.100 3.050 amb ' OR '1'='1; 50 són cerques de "L'Escala"
KnownBadInputsRuleSet 890 Totes amb cadenes de Log4Shell
CommonRuleSet / SizeRestrictions_BODY 420 415 són pujades de fotos des de /admin/productos/subir
CommonRuleSet / NoUserAgent_HEADER 310 295 del monitor intern de la Marta; 15 d'escàners
CommonRuleSet / CrossSiteScripting_BODY 45 Totes malicioses
LimiteLogin 8 6 d'una IP amb 400 intents; 2 de l'oficina en hora punta
LimiteApiPedidos 0

Per a cada regla, decideix: passar a Block, mantenir en Count, o aplicar una exclusió concreta. Per a les que requereixin exclusió, escriu el JSON. Justifica cada decisió i digues en quin ordre aplicaries els canvis.

Exercici 3: escriure la resposta a un incident en calent

Un divendres a les 18:40, en ple pic, MercadoFresco rep un atac de capa 7: 30.000 peticions per minut a /buscar?q=<aleatori> des de 5.000 IP de 40 països, amb agents d'usuari variats i realistes. La taxa d'encerts de memòria cau s'ha enfonsat al 8 % i DatabaseConnections és a 185 de 200. La Web ACL està desplegada i en Block per a les regles gestionades.

Escriu la seqüència exacta d'accions de la Marta durant els primers 15 minuts, incloent-hi el JSON de les regles que aplicaria, el risc de cada acció sobre els clients legítims que estan comprant en aquell moment, i què comprovaria després de cada pas. Tingues en compte que és divendres a les 18:40: és el pitjor moment possible per bloquejar trànsit real.

Solucions

Solució 1

Model: acció per defecte Block (llista blanca). És un tauler d'administració, no un lloc públic: el correcte és negar-ho tot i permetre l'explícit.

Prioritat Regla Declaració Acció
0 OficinaPermitida IPSetReferenceStatementipset-mercadofresco-oficina (203.0.113.10/32) Allow
10 SoloEspana NotStatement(GeoMatchStatement ES) Block
20 MartaConDesafio ByteMatchStatement sobre /admin Challenge
30 ProteccionesBase AWSManagedRulesCommonRuleSet + KnownBadInputs Block
Per defecte Block

Com funciona el recorregut: el trànsit de l'oficina coincideix a la prioritat 0 i es permet de seguida, saltant-se tota la resta. El de fora d'Espanya es bloqueja a la 10. El que queda —trànsit espanyol que no és de l'oficina, és a dir, potencialment la Marta des de casa— rep un Challenge silenciós a la 20, que un navegador real resol sol i un script no. Tota la resta cau al Block per defecte.

El compromís de l'última condició. L'opció màximament segura seria permetre només les IP de l'IP set, però això obliga la Marta a trucar a algú cada vegada que el seu operador li canvia la IP, cosa que acaba sempre amb algú afegint 0.0.0.0/0 «temporalment» un diumenge. La solució proposada accepta un risc controlat: qualsevol amb IP espanyola que arribi a /admin supera el Challenge si fa servir un navegador. Però això no li dona accés: continua havent-hi autenticació amb MFA al darrere, i WAF és només la primera barrera. El que s'aconsegueix és eliminar el 99,9 % del soroll automatitzat sense bloquejar la persona que administra el sistema.

L'alternativa millor, si es vol apujar el llistó sense perdre usabilitat, és posar el tauler darrere d'una VPN o d'un client de Verified Access amb IP de sortida fixa, i tornar al model de llista blanca estricta. Registre: la configuració de registres de la Web ACL captura tots els bloquejos, amb RedactedFields sobre authorization i cookie.

Solució 2

Regla Decisió Justificació
AmazonIpReputationList Block 12.400 coincidències, cap fals positiu: escanejos purs contra rutes que MercadoFresco ni tan sols té
SQLiRuleSet Block amb exclusió El 98 % són atacs reals, però 50 són cerques legítimes de topònims amb apòstrof
KnownBadInputsRuleSet Block 890 intents de Log4Shell, cap fals positiu
SizeRestrictions_BODY Exclusió de ruta 415 de 420 són pujades legítimes de fotos de producte
NoUserAgent_HEADER Exclusió o arreglar el monitor 295 de 310 són el monitor intern
CrossSiteScripting_BODY Block 45 coincidències, totes malicioses
LimiteLogin Block... no: Captcha 6 de 8 són un atac clar, però 2 són l'oficina
LimiteApiPedidos Block Cap coincidència en 14 dies: el llindar de 2.000 és folgat i no hi ha risc

Exclusió per a SizeRestrictions_BODY (opció b, la correcta: la regla continua activa a la resta del lloc):

{
  "Name": "ConjuntoComun",
  "Priority": 20,
  "Statement": {
    "ManagedRuleGroupStatement": {
      "VendorName": "AWS",
      "Name": "AWSManagedRulesCommonRuleSet",
      "ScopeDownStatement": {
        "NotStatement": {
          "Statement": {
            "ByteMatchStatement": {
              "SearchString": "/admin/productos/subir",
              "FieldToMatch": { "UriPath": {} },
              "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
              "PositionalConstraint": "STARTS_WITH"
            }
          }
        }
      },
      "RuleActionOverrides": [
        { "Name": "NoUserAgent_HEADER", "ActionToUse": { "Count": {} } }
      ]
    }
  },
  "OverrideAction": { "None": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "ConjuntoComun"
  }
}

Per a SQLiRuleSet, les 50 cerques de "L'Escala" no justifiquen desactivar la protecció contra injecció SQL en una botiga amb PostgreSQL al darrere. Hi ha dues sortides i la bona no és a WAF:

  • Correcta: arreglar el frontal perquè codifiqui correctament l'apòstrof abans d'enviar-lo, o fer servir POST amb JSON en lloc de la cadena de consulta. El fals positiu desapareix sol.
  • Acceptable com a pedaç temporal: aplicar RuleActionOverrides només sobre SQLi_QUERYARGUMENTS i únicament a la ruta /buscar, deixant actiu la resta del grup. Amb data de revisió anotada.

Per a NoUserAgent_HEADER, la solució de fons no és una excepció a WAF sinó arreglar el monitor perquè enviï un agent d'usuari identificatiu (MercadoFresco-Monitor/1.0). Mentrestant, l'excepció de dalt el manté en Count.

Per a LimiteLogin, Captcha en lloc de Block: les 2 coincidències de l'oficina són una NAT compartida en hora punta, i un bloqueig impediria treballar a tot l'equip. El CAPTCHA deixa passar les persones i atura l'atac de 400 intents.

Ordre d'aplicació, seguint la taula de risc:

  1. AmazonIpReputationListBlock. Vigilar 24 h.
  2. KnownBadInputsRuleSetBlock. Vigilar 24 h.
  3. LimiteApiPedidosBlock i LimiteLoginCaptcha. Vigilar 24 h.
  4. Arreglar el monitor i el frontal de cerca (canvis d'aplicació, no de WAF).
  5. SQLiRuleSetBlock, amb l'exclusió de /buscar si el frontal encara no està arreglat.
  6. CommonRuleSetBlock amb l'exclusió de ruta per a les pujades. L'últim i el més vigilat: és el grup més ampli.

I cap d'aquests canvis s'aplica un divendres.

Solució 3

Context que condiciona tot: divendres 18:40, pic de 900 comandes/hora, clients reals comprant. Cada minut de bloqueig indiscriminat són comandes perdudes. La prioritat és mantenir el servei, no guanyar el combat.

Minuts 0-2: confirmar i caracteritzar. No es toca res encara.

curl -sS -o /dev/null -w "%{http_code} %{time_total}s\n" https://mercadofresco.example/salud

Es miren les tres mètriques que decideixen: PedidosPorHora de l'espai MercadoFresco/Tienda (si continua a ~900, hi ha clients reals comprant ara mateix), CacheHitRate (8 %, confirmat) i DatabaseConnections (185/200, crític). Amb agents d'usuari realistes i 5.000 IP repartides, el patró identificable no és l'origen sinó la ruta.

Minuts 2-4: alleujar la base de dades, que és el que caurà. És l'acció de menor risc i major efecte, i no bloqueja ningú:

  • Augmentar el TTL mínim de les respostes de /buscar a CloudFront a 60 segons.
  • Reduir la clau de memòria cau de /buscar perquè només inclogui q normalitzat, ignorant els altres paràmetres aleatoris. Això converteix part de l'atac en encerts de memòria cau.

Risc per a clients legítims: resultats de cerca fins a un minut desactualitzats. Irrellevant. Comprovar després: CacheHitRate i DatabaseConnections en els 3 minuts següents.

Minuts 4-7: regla per taxa sobre /buscar, primer en Count.

{
  "Name": "EmergenciaBuscar",
  "Priority": 45,
  "Statement": {
    "RateBasedStatement": {
      "Limit": 300,
      "EvaluationWindowSec": 60,
      "AggregateKeyType": "IP",
      "ScopeDownStatement": {
        "ByteMatchStatement": {
          "SearchString": "/buscar",
          "FieldToMatch": { "UriPath": {} },
          "TextTransformations": [{ "Priority": 0, "Type": "LOWERCASE" }],
          "PositionalConstraint": "STARTS_WITH"
        }
      }
    }
  },
  "Action": { "Count": {} },
  "VisibilityConfig": {
    "SampledRequestsEnabled": true,
    "CloudWatchMetricsEnabled": true,
    "MetricName": "EmergenciaBuscar"
  }
}

Risc: cap, Count no bloqueja. Comprovar: CountedRequests de la regla i les mostres. Si les peticions comptades són les de l'atac i no clients normals, es passa al pas següent. Sí, es compten dos minuts fins i tot en una emergència: és exactament el moment en què un error costa més car.

Minuts 7-9: passar a Challenge, no a Block.

Es canvia "Action": { "Count": {} } per "Action": { "Challenge": {} }. Un navegador real resol el repte de JavaScript de manera invisible i continua comprant; un bot que no executa JavaScript no passa.

Risc: mínim. Només afectaria clients amb JavaScript desactivat, pràcticament inexistents en una botiga que ja el requereix. Comprovar: PassedRequests enfront de BlockedRequests, DatabaseConnections i, sobretot, PedidosPorHora: si continua a 900, no estem fent mal al negoci.

Minuts 9-12: si l'atac persisteix, endurir només amb els pitjors.

Si DatabaseConnections continua per damunt de 180, s'afegeix una segona regla per taxa amb llindar molt alt (per exemple 1.000 per minut per IP sobre /buscar) en Block. Aquest llindar és impossible d'assolir per a una persona, així que el risc de fals positiu és gairebé nul.

Comprovar: que PedidosPorHora no cau.

Minuts 12-15: capacitat i comunicació.

  • Derivar les lectures de cerca a la rèplica mercadofresco-pedidos-lectura si l'aplicació ho admet per configuració, per descarregar la instància principal.
  • Apujar temporalment el màxim de l'ASG.
  • Avisar el Luis i la Sara de l'estat, i anotar l'hora de cada acció per al post-mortem.

El que la Marta NO fa, i és tan important com el que fa:

  • No bloqueja per país. 40 països, i alguns tindran clients espanyols de viatge.
  • No bloqueja les 5.000 IP. Tornaran amb unes altres i bloquejaria clients que comparteixen NAT.
  • No passa CommonRuleSet a mode agressiu ni toca regles que no ha provat: un fals positiu nou a les 18:40 d'un divendres és pitjor que l'atac.
  • No apaga la cerca sencera, que és una funcionalitat central d'una botiga d'alimentació.
  • No desactiva CloudFront per «veure si és cosa seva»: eliminaria l'única capa que la protegeix.
  • No aplica cap canvi permanent en calent. Les regles d'emergència es marquen com a temporals i es revisen el dilluns amb dades, no el divendres amb adrenalina.

Conclusió

MercadoFresco té per fi la capa que faltava. Saps què veu AWS WAF que ni un grup de seguretat ni Shield poden veure —el mètode, la ruta, la cadena de consulta, les capçaleres, les galetes i el cos de cada petició HTTP— i per què les tres eines són complementàries i cap no en substitueix una altra. Coneixes l'anatomia d'una Web ACL: regles amb prioritat, grups gestionats, IP sets, el pressupost de WCU que CommonRuleSet consumeix gairebé a la meitat, i les cinc accions —amb Allow i Block terminals, Count mai terminal, i Challenge com l'opció que filtra bots sense cobrar fricció al client.

Saps que la Web ACL de CloudFront es crea a us-east-1 amb --scope CLOUDFRONT, el mateix parany que ACM i les mètriques de la CDN. Domines les declaracions de coincidència i, sobretot, les transformacions de text: sense LOWERCASE ni URL_DECODE, una regla contra <script> s'esquiva escrivint %3CSCRIPT%3E, i per això cada transformació costa 10 WCU ben gastades. Coneixes els grups gestionats per AWSCommonRuleSet, KnownBadInputs, SQLi, Linux, AmazonIpReputationList, AnonymousIpList, i els de pagament BotControl i ATP— i que els quatre primers són gratis i cobreixen la immensa majoria del que veuràs.

I per damunt de tot domines el mètode de desplegament, que és el que separa una Web ACL útil d'una apagada autoinfligida: tot en Count, dues setmanes incloent-hi dos divendres, analitzar els registres, excloure amb l'abast mínim, i només llavors passar a Block d'una regla en una, començant per la que menys falsos positius produeix. Has muntat les regles per taxa que protegeixen /login amb Captcha i /api/pedidos amb Block, sabent calcular el llindar a partir del trànsit real i fer servir FORWARDED_IP quan el WAF és a l'ALB darrere de CloudFront. Tens els registres a aws-waf-logs-mercadofresco amb RedactedFields sobre galetes i capçaleres d'autorització, les consultes d'Insights que decideixen el pas a Block, les mètriques d'AWS/WAFV2 amb la seva alarma cap a alertas-mercadofresco, i el procediment per diagnosticar i acotar un fals positiu sense desactivar un grup sencer. Tot per 26,14 USD al mes, enfront dels 3.000 de Shield Advanced.

Amb això es tanca el mòdul 4. La postura de seguretat de MercadoFresco descansa sobre quatre capes: identitats mínimes amb rols sense claus permanents, grups humans i MFA obligatori; dades xifrades amb alias/mercadofresco-datos, separació de funcions i rotació anual; secrets custodiats a mercadofresco/produccion/rds/mfadmin amb rotació automàtica cada 30 dies; i una vora protegida per Shield Standard, dues Web ACL i una superfície exposada reduïda al mínim. Tot plegat per 28,36 USD mensuals, i amb una decisió raonada i documentada de no contractar Shield Advanced.

I tanmateix, hi ha alguna cosa profundament incompleta en tot això, i és el que dona nom al mòdul següent. Hem construït alarmes que envien avisos a un tema d'SNS, però ningú no ha comprovat mai que aquest avís arribi de debò a un telèfon a les quatre de la matinada. Els registres de WAF, de la VPC, de l'ALB i de les funcions Lambda s'estan acumulant en cinc llocs diferents sense que ningú els correlacioni. Quan un client escrigui dient que la seva comanda triga vuit segons a confirmar-se, la Marta no tindrà manera de saber si el problema és a la botiga, a la Lambda d'estat de comanda o a la base de dades. Ningú no sap qui va desxifrar l'última còpia de la base de dades, ni quan, encara que KMS ho hagi registrat escrupolosament. I no existeix cap mecanisme que avisi si demà algú desactiva el xifratge d'un bucket o obre un grup de seguretat al món.

Dit d'una altra manera: està tot muntat, i ningú no està mirant. Al mòdul 5, «Monitoratge i gestió», començant per la lliçó 05-01 «Amazon CloudWatch», construirem aquesta mirada: mètriques, taulers, registres centralitzats i alarmes que funcionen de debò; després el rastreig d'una petició d'extrem a extrem amb X-Ray, l'auditoria de cada crida a l'API amb CloudTrail —on per fi veurem qui va fer servir kms:Decrypt i qui va llegir quin secret—, la vigilància contínua del compliment amb AWS Config, que avisa tan bon punt una configuració es desvia del que acabem de construir, i les recomanacions automàtiques de Trusted Advisor.

Curs d'AWS

Mòdul 1: Introducció a AWS

Mòdul 2: Serveis principals d'AWS

Mòdul 3: Xarxes i lliurament de contingut

Mòdul 4: Seguretat i identitat

Mòdul 5: Monitoratge i gestió

Mòdul 6: Bases de dades

Mòdul 7: Integració d'aplicacions

Mòdul 8: Eines per a desenvolupadors

Mòdul 9: Infraestructura com a codi i govern de comptes

Mòdul 10: Contenidors a AWS

Mòdul 11: Millors pràctiques i gestió de costos

© Copyright 2026. Tots els drets reservats