Hackdef 8 – 2024

El espíritu con el que nació el Hackdef está reflejado en su slogan: «No tools, real hacking only«, así que uno de los principales requerimientos es diseñar un reto que no pueda ser resuelto de manera automática, y, desde la competencia del año pasado con el surgimiento de herramientas adicionales como ChatGPT, este desafío aumentó. Sólo por poner un ejemplo, se dificulta ahora presentar retos que impliquen análisis de código pues es fácil preguntar a algún modelo si observa una vulnerabilidad, que lo explique línea a línea o incluso que desarrolle el exploit. Y no hay nada en contra de esto de mi parte, sólo es algo a tomar en cuenta y que de alguna manera se convierte en una dificultad adicional.

Así que este año decidí optar por un vector que muy probablemente gane popularidad con la presentación en Black Hat y DEF CON de «Splitting the email atom: exploiting parsers to bypass access controls.«, como es costumbre las investigaciones de PortSwigger suelen generar una tendencia. Así que con otro de los objetivos con los que nace el Hackdef en mente, ser educativo, es que espero que este reto sirva como una pequeña introducción a este tipo de problemáticas.

Signflow – Web – 250 puntos

Con las experiencias previas descubrí que crear una aplicación especifica para estos retos es algo desafiante, tienes una vulnerabilidad en el plan y una forma de posible solución pero conforme avanzas te das cuenta que hay muchos otros vectores que defender además de lo planeado. Así que para ahorrar hackekaz decidí implementar un sitio usando WordPress. Podrá no ser reconocido por su seguridad pero si alguien encuentra una vulnerabilidad adicional tendría bien ganada su bandera.

De modo que una vez creados los contenedores de una instalación estándar se procede a crear e instalar un plugin, Email Domain Validator.

<?php
/*
Plugin Name: Email Domain Validator
Description: Plugin para validar el dominio de correo electrónico durante el registro de usuarios y personalizar el mensaje de confirmación.
Version: 1.0
Author: Oniriko
*/

if (!defined('ABSPATH')) {
    exit;
}

// Función para truncar el correo electrónico a 100 caracteres
function truncate_email_to_100_chars($email) {
    if (strlen($email) > 100) {
        $email = substr($email, 0, 100);
    }
    return $email;
}

// Hook para validar y truncar el correo electrónico durante el registro
add_action('register_post', 'validate_email_domain_and_truncate', 10, 3);

function validate_email_domain_and_truncate($sanitized_user_login, $user_email, $errors) {
    $allowed_domain = getenv('ALLOWED_DOMAIN');
    $email_domain = substr(strrchr($user_email, "@"), 1);

    if ($email_domain !== $allowed_domain) {
        $errors->add('invalid_email_domain', __('Error: Solo se permiten registros con el dominio @' . $allowed_domain));
    }

    // Truncar el correo electrónico después de validar el dominio
    $truncated_email = truncate_email_to_100_chars($user_email);
    if ($user_email !== $truncated_email) {
        error_log('El correo electrónico fue truncado a: ' . $truncated_email);
    }
    add_filter('pre_user_email', function() use ($truncated_email) {
        return $truncated_email;
    });
}

// Sobrescribir la función is_email
function my_custom_is_email($email, $deprecated = false) {
    return true; // Considerar cualquier dirección de correo electrónico como válida
}
add_filter('is_email', 'my_custom_is_email', 10, 2);

// Hook para enviar un correo de confirmación después del registro
add_action('user_register', 'send_confirmation_email', 10, 1);

function send_confirmation_email($user_id) {
    $user_info = get_userdata($user_id);
    if ($user_info) {
        $to = $user_info->user_email;
        $subject = 'Confirmación de Registro';
        $message = getenv('FAKE_FLAG'); // Obtener el valor desde las variables de entorno
        $headers = array('Content-Type: text/html; charset=UTF-8');

        // Enviar el correo solo si ENABLE_EMAIL es true
        if (getenv('ENABLE_EMAIL') === 'true') {
            wp_mail($to, $subject, $message, $headers);
        } else {
            error_log('Envío de correos deshabilitado. No se envió correo a: ' . $to);
        }
    }
}

// Habilitar wp_mail en caso de estar deshabilitado
add_action('phpmailer_init', 'setup_phpmailer');
function setup_phpmailer($phpmailer) {
    $phpmailer->isSMTP();
    $phpmailer->Host = getenv('SMTP_HOST'); 
    $phpmailer->SMTPAuth = true;
    $phpmailer->Port = getenv('SMTP_PORT'); // Puerto SMTP
    $phpmailer->Username = getenv('SMTP_USERNAME');
    $phpmailer->Password = getenv('SMTP_PASSWORD');
    $phpmailer->SMTPSecure = getenv('SMTP_SECURE'); 
}

// Hook para redirigir a una página de éxito personalizada después del registro
add_action('user_register', 'custom_registration_redirect', 10, 1);

function custom_registration_redirect($user_id) {
    // Obtener el correo electrónico del usuario recién registrado
    $user_info = get_userdata($user_id);
    if ($user_info) {
        $email = $user_info->user_email;
        // Redirigir a una página de éxito personalizada con el correo electrónico como parámetro
        wp_redirect(add_query_arg('user_email', rawurlencode($email), home_url('/registration-success')));
        exit();
    } else {
        error_log('Error: No se pudo obtener la información del usuario para el ID: ' . $user_id);
    }
}

// Crear un shortcode para mostrar el mensaje de éxito
function registration_success_message_shortcode() {
    if (isset($_GET['user_email']) && !empty($_GET['user_email'])) {
        $email = sanitize_email(rawurldecode($_GET['user_email']));
        $message = 'Registro completo. Por favor, comprueba tu correo electrónico (' . esc_html($email) . ').';
        return '<div class="registration-success-message">' . $message . '</div>';
    }
    return '<div class="registration-success-message">Registro completo. Por favor, comprueba tu correo electrónico.</div>';
}
add_shortcode('registration_success_message', 'registration_success_message_shortcode');

// Crear una página de éxito personalizada al activar el plugin
register_activation_hook(__FILE__, 'create_custom_registration_success_page');

function create_custom_registration_success_page() {
    // Verificar si la página ya existe
    $page = get_page_by_path('registration-success');
    if (!$page) {
        // Crear la página de confirmación
        wp_insert_post(array(
            'post_title' => 'Registro Completo',
            'post_name' => 'registration-success',
            'post_content' => '[registration_success_message]',
            'post_status' => 'publish',
            'post_type' => 'page',
        ));
    }
}

// Debug: Registrar los valores de entrada y salida de wp_insert_user
add_filter('wp_insert_user_data', 'debug_wp_insert_user_data', 10, 1);
function debug_wp_insert_user_data($data) {
    error_log('Intentando registrar usuario con los siguientes datos:');
    error_log('Nombre de usuario: ' . $data['user_login']);
    error_log('Correo electrónico: ' . $data['user_email']);
    return $data;
}
?>

Pistas

Aunado a los requerimientos adicionales del Hackdef, están los implícitos en cualquier Capture the Flag (CTF): ser un reto entretenido, con un objetivo claro, que no conste de adivinanzas, etcétera. Como siempre, se espera un buen nivel de parte de los 44 equipos de las diversas universidades participantes que conforman esta edición y se plantea ofrecer la siguiente imagen como pista de forma inicial, con la intención de que los mensajes dentro de la aplicación sean suficientes para guiar los intentos de obtener la bandera.

Quizás de momento no diga mucho sin haber interactuado con la aplicación pero se espera que cobre suficiente sentido después.

Al ingresar al sitio aparece el siguiente mensaje:

Con esto el objetivo debería ser suficientemente claro: si quieres la bandera, registrate.

Una vez en el formulario de registro, el plugin entra en acción, solicitando que todo aquel que quiera registrarse posea una dirección en el dominio ocelot.apt, el cual sería imposible de adquirir -esa debe ser otra pista- pero es un escenario suficientemente realista, es decir: una empresa , grupo o gobierno restringiendo el acceso a sólo sus miembros ya registrados.

Al interactuar vemos el siguiente mensaje


Pero ¿qué pasa al usar el dominio esperado? sencillo, el registro se completa y tu confirmación es enviada al correo electrónico ingresado.

- "De acuerdo, ¡ya está claro!"

Como puede verse, el correo usado se imprime en pantalla. En este punto ya se tiene la información necesaria: Hay que registrar un correo electrónico al que se tenga acceso para poder recibir la confirmación de registro (acceso/bandera) pero que a la vez cumpla con el formato esperado.

Es hora de volver a revisar la imagen de la pista ya conociendo lo anterior (y probablemente después de haber intentado buscar inyecciones, usar WPScan y con suerte un corto etcétera). Si se observa con cuidado debe de saltar a la vista el parámetro que se está intentando burlar, user_email

Y en realidad no hay mucho que observar, más allá del nombre sólo hay otras cinco características, la más importante el tipo y tamaño. «¿Qué pasa si la dirección usada excede los 100 caracteres?» es una de las preguntas que espero se hagan los participantes. Vamos a ver…

//cadena de 122 caracteres con dominio válido
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongsstring@ocelot.apt

Bueno, ¡el correo ya no aparece en la respuesta! pero si se observa la barra de dirección la cadena fue cortada a vee…ry (exactamente 100 caracteres). La siguiente pregunta debería ser si es posible usar una dirección de correo en ese espacio, lo que implica que la validación permita usar dos signos de @.

// dirección de correo con 100 caracteres + dominio válido
veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongsstring@gmail.com@ocelot.apt

Una vez que se verifique esta posibilidad resta usar una dirección de correo a la que se tenga acceso y conste de 100 caracteres exactos, y para ello existe el sub-addresing (si no se quiere crear una cuenta con estas características). Esto implica el uso del símbolo «+» en la parte local del correo. Así, por ejemplo, cuentapersonal@gmail.com puede perfectamente recibir los mensajes de cuentapersonal+veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeerylongstring@gmail.com

…y con eso se puede completar el registro, y, por lo tanto, obtener la bandera.

Resultados

Otro de los puntos a tomar en cuenta durante el diseño es que al ser una ronda clasificatoria hay una diferencia importante -y entendible- entre el nivel de los 44 equipos participantes. Algunos participan por primera vez en un CTF (como yo en la segunda edición) en su acercamiento a la ciberseguridad y otros cuentan ya con bastante experiencia tanto en este tipo de competencias como en el hacking en general. De modo que el reto no puede ser tan fácil ni tan difícil de resolver.

En fin, el primer equipo en obtener la bandera, AHAU YUCATÁN, del Instituto Tecnológico de Mérida, tardó únicamente 17 minutos y 2 segundos. Y esto sin considerar el tiempo que tardaron en los 2 retos web que precedían al mio si fueron secuenciales. !impresionante!

"¿Competición?" Est non

Al final de la competencia la mitad de los equipos fue capaz de resolver este reto

¡Felicidades a todos! Espero que se lleven algo de conocimiento con este reto.

Recursos

También espero que durante su búsqueda por resolverlo se hayan encontrado al menos con estos tres sitios:

  • https://medium.com/@fs0c131y/tchap-the-super-not-secure-app-of-the-french-government-84b31517d144
  • https://modzero.com/en/blog/beyond_the_at_symbol/
  • https://hackerone.com/reports/2224
When you come out
Your shit is gone

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *