Práctica 1 — Ataques de Forza Bruta sobre DVWA
Sección
Sección: Prácticas Taller
Módulo: O hacking ético nas aplicacións web
Práctica: 1 — DVWA Brute Force
Aviso legal
Todo o contido desta práctica é para uso exclusivamente educativo nun contorno de laboratorio controlado e legal. Está terminantemente prohibido aplicar estas técnicas en sistemas reais sen autorización explícita e por escrito do propietario do sistema. O uso non autorizado destas técnicas pode constituír un delito penal.
Estrutura da práctica
Esta práctica divídese en dúas partes complementarias:
| Ficheiro | Contido |
|---|---|
| 1a — Ataque manual con hydra | Resolución nos catro niveis usando hydra desde o terminal |
| 1b — Ataque con HexStrike AI | Resolución automatizada usando Claude Code + HexStrike MCP |
Orde recomendada: fai primeiro a Práctica 1a para entender o mecanismo técnico, e despois a Práctica 1b para ver como a IA automatiza o mesmo proceso.
Parámetros do laboratorio
| Parámetro | Valor |
|---|---|
| URL DVWA | http://10.0.2.100/dvwa/ |
| Credenciais DVWA | admin / password |
| IP Atacante (Kali) | 10.0.2.250 |
| IP Obxectivo (DVWA) | 10.0.2.100 |
| Wordlist | /usr/share/wordlists/seclists/Passwords/Common-Credentials/2020-200_most_used_passwords.txt |
Obxectivos de aprendizaxe
Ao rematar esta práctica o alumnado será capaz de:
- Comprender o concepto de ataque de forza bruta contra formularios de autenticación web.
- Entender por que é necesario autenticarse en DVWA antes de atacar a sección Brute Force.
- Executar ataques de forza bruta con hydra nos catro niveis de DVWA.
- Analizar o código fonte PHP vulnerable e identificar os fallos de implementación en cada nivel.
- Comprender por que certas proteccións son insuficientes e como evolucionan de nivel en nivel.
- Comparar o enfoque manual co enfoque automatizado mediante HexStrike AI.
- Propoñer medidas de mitigación axeitadas para un sistema de autenticación robusto.
Conceptos previos
Que é un ataque de Forza Bruta?
Un ataque de forza bruta (brute force) contra un formulario de autenticación consiste en probar sistematicamente pares usuario/contrasinal ata atopar a combinación correcta. Este proceso repetitivo automatízase mediante ferramentas específicas e dicionarios (wordlists) con miles ou millóns de entradas, como os de SecLists ou rockyou.txt.
Tipos de estratexia de ataque
| Estratexia | Descrición | Cando usar |
|---|---|---|
| Sniper | Proba un dicionario nunha única posición (ex: contrasinal coñecendo o usuario) | Usuario coñecido, ataque de contrasinal |
| Battering Ram | Introduce a mesma palabra en todos os campos á vez | Probar o mesmo valor en varios parámetros |
| Pitchfork | Ataca varias posicións con dicionarios diferentes en paralelo | Credential stuffing con pares coñecidos |
| Cluster Bomb | Proba todas as combinacións posibles de varios dicionarios | Enumerar usuario e contrasinal ao mesmo tempo |
Nesta práctica traballaremos coa estratexia Sniper: usuario coñecido (admin) e ataque ao campo contrasinal.
Por que é necesario o PHPSESSID?
A sección de Brute Force de DVWA está dentro da zona protexida da aplicación. Calquera petición sen sesión válida é redirixida automaticamente ao login:
Sen sesión:
GET /dvwa/vulnerabilities/brute/ → 302 Redirect → /dvwa/login.php ✗
Con sesión válida:
GET /dvwa/vulnerabilities/brute/
Cookie: PHPSESSID=abc123; security=low → 200 OK + formulario ✓
Tanto hydra como HexStrike AI necesitan incluír a cookie de sesión PHPSESSID en cada petición para que o servidor as trate como se proviñesen dun usuario autenticado.
Login en DVWA e obtención da sesión
O login en DVWA require token CSRF
O formulario de login de DVWA inclúe un token anti-CSRF que cambia en cada petición. Sen enviar este token, o servidor ignora o POST e non inicia sesión. O mesmo ocorre co formulario de cambio de nivel en security.php. Por iso o proceso require catro pasos.
Proceso completo verificado
# Crear un ficheiro de cookies temporal
COOKIEJAR=$(mktemp /tmp/dvwa_cookies_XXXXXX.txt)
# Paso 1: GET login.php para obter o token CSRF do formulario de login
TOKEN=$(curl -s -c "$COOKIEJAR" \
http://10.0.2.100/dvwa/login.php \
| grep -oP "user_token.*?value='\\K[^']+")
echo "Token login: $TOKEN"
# Paso 2: POST login con credenciais + token CSRF
curl -s -b "$COOKIEJAR" -c "$COOKIEJAR" -X POST \
http://10.0.2.100/dvwa/login.php \
-d "username=admin&password=password&Login=Login&user_token=$TOKEN" \
| grep -o "You have logged in as '[^']*'"
echo "PHPSESSID: $(grep PHPSESSID $COOKIEJAR | awk '{print $7}')"
# Paso 3: GET security.php para obter o token CSRF do formulario de nivel
SEC_TOKEN=$(curl -s -b "$COOKIEJAR" -c "$COOKIEJAR" \
http://10.0.2.100/dvwa/security.php \
| grep -oP "user_token.*?value='\\K[^']+")
echo "Token security: $SEC_TOKEN"
# Paso 4: POST security.php para cambiar o nivel (substitúe "low" polo nivel desexado)
curl -s -b "$COOKIEJAR" -c "$COOKIEJAR" -X POST \
http://10.0.2.100/dvwa/security.php \
-d "security=low&seclev_submit=Submit&user_token=$SEC_TOKEN" \
| grep -oP "Security level is currently: <em>\\K[^<]+"
echo "Ficheiro de cookies: $COOKIEJAR"
Resultado esperado
Variable COOKIEJAR
O ficheiro de cookies só existe na sesión actual do terminal. Se abres un terminal novo debes repetir o proceso ou exportar a variable:
Verificar o acceso á sección Brute Force
curl -s -b "$COOKIEJAR" \
-o /dev/null \
-w "HTTP Status Brute Force: %{http_code}\n" \
http://10.0.2.100/dvwa/vulnerabilities/brute/
Resultado esperado
Un302 indica que a sesión non é válida ou que o nivel non se cambiou correctamente. Repite o proceso completo.
Cambiar o nivel entre seccións
Para cambiar de nivel entre niveis da práctica, repite os pasos 3 e 4:
# Obter token CSRF de security.php
SEC_TOKEN=$(curl -s -b "$COOKIEJAR" -c "$COOKIEJAR" \
http://10.0.2.100/dvwa/security.php \
| grep -oP "user_token.*?value='\\K[^']+")
# Cambiar o nivel (substitúe "medium" polo nivel desexado)
NIVEL="medium"
curl -s -b "$COOKIEJAR" -c "$COOKIEJAR" -X POST \
http://10.0.2.100/dvwa/security.php \
-d "security=${NIVEL}&seclev_submit=Submit&user_token=$SEC_TOKEN" \
| grep -oP "Security level is currently: <em>\\K[^<]+"
Análise dos niveis de seguridade
NIVEL LOW — Sen ningunha medida de seguridade
O formulario usa o método GET sen protección ningunha:
GET /dvwa/vulnerabilities/brute/?username=admin&password=abc123&Login=Login HTTP/1.1
Cookie: security=low; PHPSESSID=ou50vbil75pgsn9qgrj7q929sp8
Código PHP:
<?php
if( isset( $_GET[ 'Login' ] ) ) {
$user = $_GET[ 'username' ]; // Sen sanitización
$pass = $_GET[ 'password' ];
$pass = md5( $pass );
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);
if( $result && mysqli_num_rows( $result ) == 1 ) {
echo "<p>Welcome to the password protected area {$user}</p>";
} else {
echo "<pre>Username and/or password incorrect.</pre>";
// Sen retardo, sen bloqueo, sen ningunha protección
}
}
?>
Fallos: sen sanitización, sen retardo, sen límite de intentos, sen token anti-CSRF, consulta SQL inxectable.
NIVEL MEDIUM — Retardo fixo de 2 segundos
O programador engade sleep(2) en cada intento fallido. Non impide o ataque, pero aumenta o tempo necesario. Con 200 contrasinais e 2 segundos por intento, o ataque pode tardar ata ~400 segundos no peor caso.
Código PHP:
<?php
if( isset( $_GET[ 'Login' ] ) ) {
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $_GET['username']);
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $_GET['password']);
$pass = md5( $pass );
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);
if( $result && mysqli_num_rows( $result ) == 1 ) {
echo "<p>Welcome to the password protected area {$user}</p>";
} else {
sleep( 2 ); // ← Retardo fixo de 2 segundos
echo "<pre>Username and/or password incorrect.</pre>";
}
}
?>
Melloras: sanitización básica, retardo de 2s.
Problemas que persisten: retardo fixo e coñecible, sen límite de intentos, sen bloqueo, sen token anti-CSRF.
NIVEL HIGH — Token anti-CSRF por petición
O formulario pasa a usar POST e inclúe un user_token único que cambia en cada petición. Os ataques simples con hydra fallan porque non poden obter un token fresco antes de cada intento. O retardo pasa a ser aleatorio entre 0 e 3 segundos.
<!-- Token visible no código fonte da páxina -->
<input type='hidden' name='user_token' value='a3f9b2c1d4e5...' />
Código PHP:
<?php
if( isset( $_GET[ 'Login' ] ) ) {
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$user = stripslashes( $_GET[ 'username' ] );
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = stripslashes( $_GET[ 'password' ] );
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$pass = md5( $pass );
$query = "SELECT * FROM `users` WHERE user = '$user' AND password = '$pass';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query);
if( $result && mysqli_num_rows( $result ) == 1 ) {
echo "<p>Welcome to the password protected area {$user}</p>";
} else {
sleep( rand(0, 3) ); // ← Retardo aleatorio 0-3s
echo "<pre>Username and/or password incorrect.</pre>";
}
}
generateSessionToken(); // ← Novo token para a seguinte petición
?>
Melloras: token anti-CSRF por petición, retardo aleatorio, mellor sanitización.
Problemas que persisten: sen límite de intentos, sen bloqueo de conta. O token pode extraerse automaticamente cun script Python.
NIVEL IMPOSSIBLE — Sen vulnerabilidade explotable
Implementa todas as capas de protección necesarias. O bloqueo de conta tras 3 intentos fallidos fai o ataque de forza bruta impracticable.
Código PHP:
<?php
if( isset( $_POST[ 'Login' ] ) ) {
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );
$user = stripslashes( $_POST[ 'username' ] );
$user = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $user);
$pass = stripslashes( $_POST[ 'password' ] );
$pass = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $pass);
$pass = md5( $pass );
// Prepared statement — elimina inxección SQL
$data = $db->prepare('SELECT failed_login, last_login FROM users
WHERE user = (:user) LIMIT 1;');
$data->bindParam(':user', $user, PDO::PARAM_STR);
$data->execute();
$row = $data->fetch();
// Bloqueo de conta: 3 fallos → 15 minutos
$account_locked = false;
if( $row['failed_login'] >= 3 &&
$row['last_login'] + (15 * 60) > time() ) {
$account_locked = true;
}
$data = $db->prepare('SELECT * FROM users WHERE user = (:user)
AND password = (:password) LIMIT 1;');
$data->bindParam(':user', $user, PDO::PARAM_STR);
$data->bindParam(':password', $pass, PDO::PARAM_STR);
$data->execute();
if( !$account_locked && $data->rowCount() == 1 ) {
$db->prepare('UPDATE users SET failed_login = "0"
WHERE user = (:user) LIMIT 1;')
->execute([':user' => $user]);
echo "<p>Welcome to the password protected area {$user}</p>";
} else {
sleep( rand(2, 4) ); // ← Retardo aleatorio 2-4s
echo "<pre>Username and/or password incorrect.</pre>";
$db->prepare('UPDATE users SET failed_login = (failed_login + 1)
WHERE user = (:user) LIMIT 1;')
->execute([':user' => $user]);
}
}
generateSessionToken();
?>
Medidas de seguridade implementadas:
| Medida | Implementación |
|---|---|
| Token anti-CSRF | checkToken() — token único por sesión e petición |
| Prepared statements | PDO::prepare() + bindParam() — elimina SQLi |
| Sanitización completa | stripslashes() + mysqli_real_escape_string() |
| Retardo aleatorio | sleep(rand(2,4)) — dificulta ataques temporais |
| Bloqueo de conta | 3 fallos → bloqueo de 15 minutos |
| Contador de fallos | Persistente en base de datos |
| Método POST | Sen parámetros visibles na URL |
Comparativa de niveis
| Nivel | Retardo | Bloqueo | Token CSRF | Prepared Stmt | Explotable? |
|---|---|---|---|---|---|
| Low | ❌ Non | ❌ Non | ❌ Non | ❌ Non | ✅ Si (trivial) |
| Medium | ⚠️ 2s fixos | ❌ Non | ❌ Non | ❌ Non | ✅ Si (lento) |
| High | ⚠️ 0-3s aleatorio | ❌ Non | ✅ Si | ❌ Non | ✅ Si (script) |
| Impossible | ✅ 2-4s aleatorio | ✅ Si (3/15min) | ✅ Si | ✅ Si | ❌ Non |
Medidas de mitigación recomendadas
1. Bloqueo de contas — Tras 3-5 fallos, bloquear temporalmente a conta.
2. Rate limiting — Retardos aleatorios e límite de intentos por IP nun período de tempo.
3. Política de contrasinais — Contrasinais longos e complexos verificados contra bases de datos de contrasinais comprometidos (HaveIBeenPwned).
4. MFA — Segundo factor de autenticación (TOTP, FIDO2, SMS).
5. CAPTCHA — Retos para distinguir humanos de bots, especialmente tras varios fallos.
6. Tokens anti-CSRF — Token único por sesión e petición.
7. Prepared Statements — PDO con bindParam() para eliminar completamente a inxección SQL.
8. Logging e monitorización — Rexistrar fallos e alertar ante patróns anómalos.
9. WAF — Firewall de aplicación web que detecte e bloquee patróns de brute force.
Glosario
| Termo | Definición |
|---|---|
| Brute Force | Ataque que proba sistematicamente combinacións de credenciais ata atopar a correcta |
| Wordlist | Dicionario de contrasinais usados en ataques de forza bruta |
| PHPSESSID | Identificador de sesión PHP gardado nunha cookie do navegador |
| CSRF | Cross-Site Request Forgery — ataque que fai que un usuario autenticado execute accións non desexadas nun sitio web. O token anti-CSRF prevén este ataque verificando que cada petición provén do formulario lexítimo da aplicación |
| Token anti-CSRF | Valor único e secreto incluído en formularios para previr ataques automatizados |
| Rate Limiting | Limitación do número de peticións permitidas nun período de tempo |
| Prepared Statement | Consulta SQL parametrizada que separa código de datos, prevenindo inxección SQL |
| MFA | Multi-Factor Authentication — autenticación con dous ou máis factores independentes |
| hydra | Cracker de login en rede con módulos para HTTP, SSH, FTP e outros protocolos |
| http-get-form | Módulo de hydra para atacar formularios web con método GET |
| WAF | Web Application Firewall — firewall específico para protexer aplicacións web |
| Credential stuffing | Ataque que usa pares usuario/contrasinal filtrados de brechas anteriores |
| MCP | Model Context Protocol — protocolo que conecta Claude con ferramentas externas |
| HexStrike AI | Servidor MCP con 150+ ferramentas de ciberseguridade para Kali Linux |