Si has ejecutado un escáner de vulnerabilidades en tu sitio WordPress y te ha reportado cientos de puntos vulnerables de «Inyección LDAP» relacionados con el parámetro de búsqueda ?s=, no entres en pánico. En este artículo te explico qué significa realmente este reporte y cómo solucionarlo de forma definitiva.
#El problema: 310 vulnerabilidades de Inyección LDAP detectadas
Al analizar un sitio WordPress, el escáner reportó algo como esto:
Criticidad: Alta
Descripción: Se encontraron 310 puntos vulnerables donde la aplicación
no sanitiza correctamente la entrada del usuario antes de usarla
en una consulta LDAP.
Ejemplos:
- https://tusitio.es/?s=%2A%29%29%00nosuchvalue
- https://tusitio.es/author/admin/?%2A%29%29%00nosuchvalueEl patrón %2A%29%29%00 decodificado es *)) seguido de un carácter NUL (nulo), que son caracteres típicos usados para explotar vulnerabilidades en sistemas que utilizan LDAP (Lightweight Directory Access Protocol).
#¿Es realmente una vulnerabilidad crítica?
Probablemente NO, y aquí está el por qué:
- WordPress no usa LDAP de forma nativa – El sistema de búsqueda de WordPress utiliza consultas MySQL, no LDAP.
- El escáner detecta la aceptación de caracteres, no una vulnerabilidad real – Si la URL acepta esos caracteres especiales sin bloquearlos, el escáner lo marca como «vulnerable».
- Sin plugins de directorios activos, no hay LDAP – Solo sitios que integran WordPress con Active Directory o servicios de directorio LDAP serían vulnerables.
Sin embargo, es una buena práctica bloquear estos patrones por varias razones:
- Elimina los falsos positivos del informe de seguridad
- Previene futuros problemas si alguna vez instalas un plugin que use LDAP
- Mejora la higiene general de seguridad de tu sitio
- Bloquea intentos de ataques automatizados
#La solución: Bloquear patrones de inyección LDAP en WordPress
Vamos a implementar una solución completa que:
- Bloquee caracteres típicos de inyección LDAP en todas las URLs
- Sanitice específicamente el parámetro de búsqueda
?s= - Mejore el escapado en las plantillas del tema
#Paso 1: Añadir la función de protección en functions.php
Abre el archivo functions.php de tu tema (preferiblemente un tema hijo) y añade el siguiente código:
<?php
/**
* ==========================================
* PROTECCIÓN CONTRA INYECCIÓN LDAP
* ==========================================
* Bloquea caracteres típicos de inyección LDAP:
* - Paréntesis: ( )
* - Asteriscos: *
* - Caracteres NUL: \x00 o %00
* - Backslash: \
* - Pipe: |
* - Ampersand: &
*/
function bloquear_inyeccion_ldap() {
// Solo procesar si hay parámetros GET
if (empty($_GET)) {
return;
}
// Obtener la query string completa
$query_string = isset($_SERVER['QUERY_STRING']) ? urldecode($_SERVER['QUERY_STRING']) : '';
$request_uri = isset($_SERVER['REQUEST_URI']) ? urldecode($_SERVER['REQUEST_URI']) : '';
// Patrones de inyección LDAP a bloquear
$ldap_patterns = array(
'/\x00/', // Caracteres NUL
'/%00/', // NUL codificado
'/\*\)/', // *) - patrón típico de LDAP injection
'/\(\|/', // (| - patrón OR en LDAP
'/\(\&/', // (& - patrón AND en LDAP
'/\)\)/', // )) - cierre de múltiples filtros
'/\(\*/', // (* - wildcard en filtro
'/\\\\[0-9a-fA-F]{2}/', // Escape hex de LDAP
'/uid=\*/', // Enumeración de usuarios LDAP
'/cn=\*/', // Common name wildcard
'/objectclass=\*/', // Object class enumeration
);
// Verificar query string y URI
foreach ($ldap_patterns as $pattern) {
if (preg_match($pattern, $query_string) || preg_match($pattern, $request_uri)) {
// Log del intento (opcional)
// error_log('LDAP Injection bloqueado desde IP: ' . $_SERVER['REMOTE_ADDR']);
// Responder con error 400 Bad Request
status_header(400);
wp_die(
'<h1>Solicitud no válida</h1>' .
'<p>La solicitud contiene caracteres no permitidos.</p>' .
'<p><a href="' . esc_url(home_url('/')) . '">Volver al inicio</a></p>',
'Solicitud no válida',
array(
'response' => 400,
'back_link' => true
)
);
}
}
// Verificar específicamente el parámetro de búsqueda 's'
if (isset($_GET['s'])) {
$search = $_GET['s'];
// Caracteres prohibidos en búsqueda
$forbidden_chars = array('*', '(', ')', '\\', chr(0), '|', '&', '!');
foreach ($forbidden_chars as $char) {
if (strpos($search, $char) !== false) {
// Limpiar la búsqueda
$clean_search = str_replace($forbidden_chars, '', $search);
$clean_search = trim($clean_search);
if (!empty($clean_search)) {
// Redirigir a búsqueda limpia
wp_safe_redirect(add_query_arg('s', urlencode($clean_search), home_url('/')));
exit;
} else {
// Redirigir al inicio si no queda nada
wp_safe_redirect(home_url('/'));
exit;
}
}
}
}
}
// Ejecutar muy temprano, antes que cualquier otra cosa
add_action('init', 'bloquear_inyeccion_ldap', 0);#Paso 2: Sanitizar las consultas de búsqueda
Añade también esta función para sanitizar las búsquedas antes de que lleguen a la base de datos:
<?php
/**
* Limpiar query de búsqueda para prevenir inyección SQL y LDAP
*/
function sanitizar_busqueda_wordpress($query) {
if ($query->is_search && !is_admin()) {
// Obtener el término de búsqueda
$search_term = get_search_query();
// Eliminar etiquetas HTML
$search_term = strip_tags($search_term);
// Eliminar caracteres de inyección LDAP
$search_term = str_replace(
array('*', '(', ')', '\\', '|', '&', '!', chr(0)),
'',
$search_term
);
// Solo permitir letras, números, espacios, guiones y puntos
$search_term = preg_replace('/[^\p{L}\p{N}\s\-_\.]/u', '', $search_term);
// Limitar longitud (máximo 100 caracteres)
$search_term = substr($search_term, 0, 100);
// Normalizar espacios múltiples
$search_term = preg_replace('/\s+/', ' ', $search_term);
$search_term = trim($search_term);
// Actualizar query
if (!empty($search_term)) {
$query->set('s', $search_term);
} else {
// Cancelar búsqueda si queda vacía
$query->set('s', '');
$query->is_search = false;
}
}
return $query;
}
add_filter('pre_get_posts', 'sanitizar_busqueda_wordpress');#Paso 3: Rate Limiting para búsquedas (opcional pero recomendado)
Para prevenir ataques de fuerza bruta en el buscador, añade un limitador de velocidad:
<?php
/**
* Rate limiting para búsquedas (prevenir spam y ataques)
*/
function limitar_busquedas_por_ip() {
if (!is_search()) {
return;
}
// Obtener IP del usuario
$user_ip = $_SERVER['REMOTE_ADDR'];
$transient_key = 'search_limit_' . md5($user_ip);
// Obtener número de búsquedas realizadas
$search_count = get_transient($transient_key);
// Límite: 10 búsquedas por minuto
$max_searches = 10;
$time_window = 60; // segundos
if ($search_count === false) {
set_transient($transient_key, 1, $time_window);
} elseif ($search_count >= $max_searches) {
wp_die(
'<h1>Demasiadas búsquedas</h1>' .
'<p>Has excedido el límite. Espera un momento antes de intentarlo.</p>' .
'<p><a href="' . esc_url(home_url('/')) . '">Volver al inicio</a></p>',
'Límite de búsquedas excedido',
array('response' => 429, 'back_link' => true)
);
} else {
set_transient($transient_key, $search_count + 1, $time_window);
}
}
add_action('template_redirect', 'limitar_busquedas_por_ip', 1);#Probando la solución
Una vez implementado el código, puedes probar que funciona correctamente:
#Prueba 1: URL con patrón de inyección LDAP
Intenta acceder a:
https://tusitio.es/?s=*))test
Resultado esperado: Deberías ver una página de error «Solicitud no válida» con código 400, o ser redirigido a una búsqueda limpia.
#Prueba 2: Búsqueda normal
Intenta acceder a:
https://tusitio.es/?s=wordpress tutorial
Resultado esperado: La búsqueda debería funcionar normalmente.
#Prueba 3: Volver a ejecutar el escáner
Ejecuta nuevamente el análisis de seguridad. Los 310 puntos de inyección LDAP deberían desaparecer.
#¿Por qué funciona esta solución?
| Capa de protección | Qué hace |
|---|---|
| Hook ‘init’ con prioridad 0 | Se ejecuta antes que cualquier otra cosa en WordPress, bloqueando URLs maliciosas de raíz |
| Detección de patrones regex | Identifica secuencias típicas de inyección LDAP como *), (|, (&, )) |
| Sanitización de parámetro ‘s’ | Limpia específicamente el campo de búsqueda eliminando caracteres peligrosos |
| Redirección inteligente | En lugar de mostrar error, redirige a búsqueda limpia cuando es posible |
| Rate limiting | Previene ataques automatizados limitando búsquedas por IP |
#Mejoras adicionales en el formulario de búsqueda
Si usas un tema personalizado, también puedes añadir validación HTML5 en el formulario de búsqueda (searchform.php):
<input
type="search"
name="s"
placeholder="Buscar..."
value="<?php echo esc_attr(get_search_query()); ?>"
maxlength="100"
pattern="[^*()\|&!]*"
title="No se permiten caracteres especiales como *, (, ), |, &"
required
/>Esto añade:
esc_attr()– Escapado de seguridad para el valormaxlength="100"– Límite de caracterespattern– Validación del lado del cliente que bloquea caracteres prohibidos
Recuerda siempre hacer una copia de seguridad antes de modificar el archivo functions.php y probar los cambios en un entorno de desarrollo antes de aplicarlos en producción.