Evasión del Desafío JavaScript de AWS WAF para Automatización de Peticiones con Python
Aprende cómo sortear la validación de JavaScript en AWS WAF utilizando Python y Playwright para automatizar solicitudes a páginas protegidas. Descubre cómo obtener el token de AWS WAF de manera automática y realiza web scraping sin ser bloqueado por medidas de seguridad.
Las aplicaciones web siempre están expuestas a bots, ataques automáticos y gente colándose donde no debe. Para frenar estos problemas, muchas plataformas usan Firewalls de Aplicaciones Web (WAF, en inglés), que básicamente son filtros de seguridad para asegurarse de que solo los usuarios legítimos puedan acceder. AWS (Amazon Web Services) tiene su propio WAF, que utiliza varias técnicas para proteger las aplicaciones.
Lo más interesante de este WAF es que usa JavaScript para validar que las solicitudes vienen de usuarios reales. Cuando entras a una página desde un navegador, todo pasa sin que te des cuenta: el navegador ejecuta un script JavaScript que confirma que no eres un bot. Pero cuando intentas hacerlo con herramientas automatizadas, el servidor no se lo cree y te bloquea.
Esto me hizo pensar, “¿cómo puedo evadir esto con Python?” En este artículo vamos a ver cómo AWS WAF valida las solicitudes usando JavaScript y cómo podemos saltarnos esa validación con Python para automatizar peticiones a páginas protegidas sin que nos pillen.
¿Cómo Funciona el WAF de AWS y el Desafío de Verificación?
Una de las técnicas principales del AWS WAF es meter mecanismos de “Challenge” o “Verificación”, que sirven para asegurarse de que las solicitudes vienen de usuarios reales. Y lo hacen usando JavaScript, que se ejecuta directamente en el navegador del usuario.
Todo esto pasa en segundo plano, y si el WAF ve que la verificación pasa, te deja entrar.
Pero, claro, cuando intentas hacerlo con herramientas como curl, el sistema se da cuenta de que no estás usando un navegador y te “bloquea” por así decirlo.
1
2
3
4
5
walxom in 🌐 kali in ~
❯ curl https://builtwith.com/relationships/tesla.com
walxom in 🌐 kali in ~
❯
Si miramos los encabezados nos encontraremos que el servidor nos respondio con código de estado 202, con un encabezado x-amzn-waf-action que tiene de valor challenge.
1
curl https://builtwith.com/relationships/tesla.com -I
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
HTTP/2 202
server: CloudFront
date: Sat, 22 Mar 2025 01:33:17 GMT
content-length: 0
x-amzn-waf-action: challenge
cache-control: private
content-type: text/html; charset=UTF-8
access-control-allow-origin: *
access-control-max-age: 86400
access-control-allow-methods: OPTIONS,GET,POST
access-control-allow-headers: x-amzn-waf-action
x-cache: Error from cloudfront
via: 1.1 2d8216898001f8ce3fde38c8796d2fa6.cloudfront.net (CloudFront)
x-amz-cf-pop: AMS1-P2
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: YDEcBO6PhBLCaiL5itvPIVnevTLkf6aMd4hX5CzWAKk58IHbkfCKrA==
Este encabezado te dice que el acceso queda bloqueado hasta que pases el desafío. Si la solicitud lleva el encabezado Accept: text/html, el servidor no solo te manda el código de estado 202, sino que además te suelta la página con el script del desafío que el navegador tiene que ejecutar para la validación.
1
curl https://builtwith.com/relationships/tesla.com -H 'Accept: text/html'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title></title>
<style>
body {
font-family: "Arial";
}
</style>
<script type="text/javascript">
window.awsWafCookieDomainList = [];
window.gokuProps = {
"key":"AQIDAHjcYu/GjX+AN1f3oQDnql.../2bFaQZ+m5FKCMDnO+/prjgPKqLa8icywRA==",
"iv":"EkQCRQDjSAAABMjZ",
"context":"kQoXe8t7AJE1qzYFtsAN1f3hfs9...BSFxDtMiVo/jcCooQDnqlQOxn41lKWLnMh3Fw=="
};
</script>
<script src="https://fc61f82ef7a7.2c4df062.sa-east-1.token.awswaf.com/.../challenge.js"></script>
</head>
<body>
<div id="challenge-container"></div>
<script type="text/javascript">
AwsWafIntegration.saveReferrer();
AwsWafIntegration.checkForceRefresh().then((forceRefresh) => {
if (forceRefresh) {
AwsWafIntegration.forceRefreshToken().then(() => {
window.location.reload(true);
});
} else {
AwsWafIntegration.getToken().then(() => {
window.location.reload(true);
});
}
});
</script>
<noscript>
<h1>JavaScript is disabled</h1>
In order to continue, we need to verify that you're not a robot.
This requires JavaScript. Enable JavaScript and then reload the page.
</noscript>
</body>
</html>
Breve análisis al script
Cuando AWS WAF nos lanza el desafío, la página que devuelve contiene un script JavaScript que carga datos en un objeto llamado window.gokuProps. Dentro de ese objeto, podemos encontrar algunas claves que son esenciales para pasar el desafío:
key: Una clave cifrada que se usa para identificar el desafío específico que estás enfrentando.iv: Un vector de inicialización (IV) que se usa en el proceso de cifrado para proteger la clave.context: Información contextual sobre la sesión actual, como el origen de la solicitud o el historial de navegación, que ayuda a AWS WAF a asegurarse de que no hay algo raro ocurriendo.
El script no solo maneja esos datos, sino que también carga otro archivo JavaScript desde un servidor de AWS donde contiene la lógica para gestionar el desafío real de verificación, que es el que realmente te permite pasar el control de validación.
1
<script src="https://fc61f82ef7a7.2c4df062.sa-east-1.token.awswaf.com/.../challenge.js"></script>
Este archivo es el que se encarga de varias tareas críticas. Por ejemplo, guarda la URL de referencia, obtiene un token de validación y, si todo va bien, le permite al navegador continuar con la solicitud original. Sin este paso, el acceso seguiría bloqueado y el desafío no se resolvería.
Superando el Desafío
Usando Curl (Manual)
Una forma sencilla de superar el desafío de AWS WAF usando curl es obtener la cookie aws-waf-token después de que el desafío haya sido resuelto en el navegador, y luego con esa cookie en mano, podemos hacer la misma solicitud usando curl, pero esta vez añadiendo la cookie que hemos conseguido previamente.
1
2
3
4
walxom in 🌐 kali in ~
❯ curl https://builtwith.com/relationships/tesla.com \
-H 'Accept: text/html' \
-b 'aws-waf-token=3864336b-412d-12fc-ab83-6e229a91783c:EAoAgesyXl4+AAA...GhV9dBxUtqJSwb=' -I
Si todo ha salido bien, la respuesta debería ser un código 200, lo que significa que has pasado el desafío y ahora tienes acceso al contenido protegido de la página.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
HTTP/2 200
content-type: text/html; charset=utf-8
content-length: 871072
cache-control: private
server: Microsoft-IIS/10.0
cross-origin-opener-policy: same-origin
cross-origin-embedder-policy: require-corp
set-cookie: BWSTATE=; expires=Fri, 22-Mar-2024 21:58:10 GMT; path=/; secure
x-ua-compatible: IE=Edge,chrome=1
cross-origin-resource-policy: same-site
yarpt: 1
x-powered-by: ASP.NET
date: Sat, 22 Mar 2025 21:58:10 GMT
vary: Accept-Encoding
x-cache: Hit from cloudfront
via: 1.1 42f34cc29143ced2bc45346515811014.cloudfront.net (CloudFront)
x-amz-cf-pop: LIM50-P2
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: hsakt8ENP7Prnu2JcbZ4Nz3o2amntUvmyQn9JhRgsIshAmunsrfmbw==
age: 120
Aunque este método usando curl con la cookie es bastante efectivo, aun depende de un proceso manual para obtener la cookie desde el navegador. Así que este enfoque se vuelve limitado para automatización pura. Pero para pruebas rápidas o peticiones esta bien.
Usando Python y Playwright (Automatizado)
Para no tener que hacer todo manualmente en el navegador, podemos usar Playwright que es una librería en Python que nos permitirá para manejar esta interacción con el navegador y obtener el token aws-waf-token de una forma mas automática.
Primero, necesitamos instalar Playwright y de navegador web en este caso utilizaremos Chromium:
1
2
pip install playwright
playwright install chromium
Con eso ya tendrás todo listo para empezar a automatizar.
Comenzamos importando las librerías necesarias:
1
2
import asyncio
from playwright.async_api import async_playwright
asyncio es necesario porque Playwright se maneja de manera asíncrona, lo que significa que no bloquea el proceso mientras espera por las acciones. async_playwright es la interfaz asíncrona que nos permitirá controlar el navegador de manera eficiente.
Luego, definimos una variable para guardar el token:
1
aws_waf_token = None
A continuación, creamos una función asíncrona que va a lanzar el navegador, navegar a la página protegida por AWS WAF, y obtener la cookie aws-waf-token:
1
2
async def getCookie():
global aws_waf_token
Dentro de esta función, usamos async with async_playwright() as p: para iniciar Playwright y crear una instancia del navegador:
1
2
3
4
async with async_playwright() as p:
try:
browser = await p.chromium.launch()
page = await browser.new_page()
Después de abrir el navegador y crear una nueva página, vamos a navegar a la URL que está protegida por el WAF:
1
await page.goto("https://builtwith.com/relationships/tesla.com")
Una vez que se cargue la página, el desafío JavaScript de AWS WAF se ejecutará en segundo plano. Ahora, entramos en un bucle que se sigue ejecutando hasta que encontramos la cookie aws-waf-token:
1
2
while aws_waf_token is None:
current_cookies = await page.context.cookies()
Dentro de este bucle, obtenemos todas las cookies actuales usando await page.context.cookies(). Luego recorremos las cookies para buscar la que tiene el nombre aws-waf-token:
1
2
3
4
5
for cookie in current_cookies:
if cookie['name'] == 'aws-waf-token':
aws_waf_token = cookie['value']
print(f"Token: {aws_waf_token}")
break
Si no encontramos la cookie, esperamos un segundo antes de seguir buscando:
1
2
if aws_waf_token is None:
await asyncio.sleep(1)
Una vez que obtenemos el token, cerramos el navegador:
1
2
3
4
5
except Exception as e:
print(f"Error: {e}")
finally:
if browser:
await browser.close()
Finalmente, ejecutamos la función getCookie de manera asíncrona:
1
asyncio.run(getCookie())
Código completo: getToken_AWS_WAF.py
Este script hace todo de forma automática. Una vez que el aws-waf-token se obtiene, podemos hacer solicitudes a la página protegida sin ser bloqueados, lo que nos permitirá realizar por ejemplo un web scraping como lo veremos en la siguiente sección.
Web Scraping en Python (Adicional)
Para este caso, instalaremos y importamos adicionalmente la librería lxml para analizar el HTML y requests para hacer la solicitud HTTP
1
pip install lxml requests
1
2
3
4
from lxml import html
import asyncio
from playwright.async_api import async_playwright
import requests
Al final una vez que obtenemos el token (aws-waf-token), podemos utilizar la librería requests que importamos anteriormente para realizar la solicitud HTTP, adjuntando el token en las cookies para superar el desafío del WAF y acceder al contenido protegido
1
2
3
if aws_waf_token:
cookies = {'aws-waf-token': aws_waf_token}
response = requests.get(url, cookies=cookies)
Una vez que tenemos la respuesta de la solicitud HTTP, necesitamos analizar el contenido HTML de la página. Usamos lxml para convertir la respuesta en un árbol de elementos que podemos explorar fácilmente.
1
tree = html.fromstring(response.content)
Ahora que tenemos el árbol de elementos HTML, podemos usar XPath para buscar y extraer los datos que necesitamos. En este caso, estamos buscando todos los enlaces dentro de una tabla.
1
elements = tree.xpath('/html/body/form/div/div[2]/div[2]/div[2]/div/div/table/tbody/tr[*]/td[2]/a')
El
[*]en el XPath asegura que seleccionamos todos los enlaces dentro de cada fila de la tabla.
Finalmente, recorremos los elementos encontrados y mostramos el texto de cada enlace. Esto nos da el contenido de los enlaces en la tabla, que es lo que queremos extraer.
1
2
3
4
5
if elements:
for i, element in enumerate(elements, 1):
print(element.text)
else:
print("No se encontraron los elementos con el XPath proporcionado.")
1
2
else:
print("No se obtuvo el token de AWS WAF.")
Código completo: example_webScraping.py
Conclusión
A través de este artículo, hemos explorado cómo AWS WAF utiliza JavaScript para realizar la verificación de las solicitudes y cómo es posible superar este desafío de manera manual y automatizada. El uso de Playwright para interactuar con el navegador y obtener el token necesario para eludir el desafío de WAF ofrece una solución eficiente para aquellos que necesiten realizar tareas de automatización, como el web scraping, en aplicaciones protegidas.

