Introducción
Core-Admin permite limitar desde qué países se acepta el inicio de sesión en un WordPress
concreto. La idea es simple: si tu sitio solo lo gestionan personas que están en España, Argentina
e Italia, puedes indicar ES, AR, IT y cualquier intento de login desde otro país será rechazado
en el momento de autenticarse, aunque la contraseña sea correcta.
Esta característica complementa a otros dos mecanismos que ya ofrecía la plataforma —la limitación
de acceso por IP (limit_admin_to_ips) y la limitación de peticiones POST por IP
(limit_post_to_ips)— y al sistema de detección de cuentas WordPress comprometidas. Más adelante
explicamos cómo se relacionan y cuándo conviene cada uno.
La protección es preventiva (bloquea el login, no se limita a avisar después) y está diseñada
con un principio de seguridad clave: fail-open. Ante cualquier duda (no hay política, fallo de
resolución GeoIP, dirección no resoluble…) se acepta el acceso, de forma que la política nunca
pueda dejar a nadie fuera por un problema técnico.
Cómo funciona
El control lo aplica el plugin Core-Admin Wordpress Login Tracker (WLT) que la plataforma
despliega en cada WordPress. El plugin se engancha al filtro authenticate de WordPress con la
máxima prioridad, de modo que ve el resultado final de cualquier intento de autenticación y
puede convertir un login correcto en un rechazo.
Puntos importantes del diseño:
-
Solo se geolocaliza en logins correctos. Los intentos fallidos (la inmensa mayoría en un
ataque de fuerza bruta) no se geolocalizan: ya fallan por sí solos. Esto evita penalizar el
rendimiento en el camino caliente y solo añade una resolución GeoIP cuando alguien acierta las
credenciales. -
Cubre todas las vías de login: formulario
wp-login.php, XML-RPC y REST con application
passwords. Son precisamente los vectores que más se atacan. -
Resolución GeoIP local mediante la base de datos de Core-Admin (no consulta servicios
externos en cada login). -
Las sesiones por cookie no se ven afectadas: quien ya entró desde un país permitido y se
desplaza a otro país mantiene su sesión; solo se bloquean logins nuevos.
Protección de la red local (LAN)
Además del filtrado por país, la política protege frente a accesos desde localhost y direcciones
de red interna (127.0.0.1, ::1, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, etc.).
El objetivo de seguridad es evitar que un atacante que haya conseguido ejecutar código en el propio
servidor (o en una máquina de la red interna) pueda hacer login “desde dentro” haciendo binding a
la IP local de la máquina, saltándose el filtro por país.
Comportamiento por defecto: cuando la política de países está activa, los accesos desde
direcciones locales/privadas se rechazan. Si necesitas permitir el acceso interno (por ejemplo,
administración desde la propia LAN o desde una VPN que sale por IP privada), añade a la lista de
países permitidos uno de estos tokens (no distinguen mayúsculas/minúsculas):
| Token | Efecto |
|---|---|
LocalLan |
Permite logins desde direcciones locales/privadas/loopback |
LAN |
Igual que LocalLan (alias) |
PrivateIp |
Igual que LocalLan (alias) |
Ejemplo de configuración que acepta Italia y acceso desde la red local:
IT, LAN
Administración desde la interfaz web
La política se configura por WordPress, en la pestaña Security de su ficha.
Seleccionamos el wordpress en cuestión:
Luego:
Campos disponibles:
- Enable country policy — activa/desactiva la política para este WordPress.
-
Allowed countries — lista de códigos de país ISO-3166 de 2 letras separados por espacios
o comas (ej.ES, AR, IT). Aquí también se añaden los tokensLocalLan/LAN/PrivateIp
si quieres permitir la red local. -
Country policy extra ips — lista opcional de IPs que siempre se permiten,
independientemente del país (útil para oficinas o VPN en el extranjero con IP pública fija).
Requisitos y validaciones
-
Requiere WLT activado. La política la aplica el plugin de login, así que al guardar con la
política activa pero sin WLT, la operación se rechaza indicándolo. -
Códigos válidos. Si activas la política sin ningún código de país válido, la operación se
rechaza (para no acabar, por un error tipográfico, con una política vacía que aceptaría todo). Los
códigos no reconocidos se ignoran y se notifica.
Dónde se guarda la configuración
La configuración es un fichero JSON desplegado junto al WordPress (no se guarda en base de datos),
que es la única fuente de verdad que lee el plugin:
wp-content/.core-admin-wp-country-policy.json
Contenido de ejemplo (IT permitido, LAN denegada por defecto):
{"enabled": true, "mode": "allow", "countries": ["IT"], "extra_allowed_ips": [], "local-lan-ips-allowed": false}
Este planteamiento es el mismo que sigue la limitación de IP de administración
(wp-admin/.htaccess): el fichero desplegado manda, la interfaz lo escribe y el listado lo vuelve
a leer.
Qué ve el usuario bloqueado
Cuando se rechaza un login, WordPress muestra el error correspondiente en la pantalla de acceso:
- Bloqueo por país: “Access denied: logins to this site are not allowed from your country.”
- Bloqueo por red local: “Access denied: logins to this site are not allowed from your network.”
A partir de ahí, el usuario legítimo puede usar el enlace de “He olvidado mi contraseña” o contactar
con el administrador.
Registros y eventos que genera
Toda decisión no trivial deja traza en syslog, de modo que siempre se puede ver por qué la
política actuó o por qué se ignoró (fail-open). Todas las líneas las emite el plugin con el formato
de bloque genérico de Core-Admin y el agente las recoge en syslog_handler.
Bloqueos (deny)
Login rechazado por país no permitido:
Login geo-blocked detected for username=usuario, country=ES, allowed-countries=IT, remote-addr=203.0.113.10, domain=ejemplo.com, service=Wordpress Login, ...
Login rechazado por acceso desde red local (país sintético local-lan):
Login geo-blocked detected for username=usuario, country=local-lan, allowed-countries=IT, remote-addr=10.2.57.2, domain=ejemplo.com, service=Wordpress Login, ...
Ante un geo-bloqueo, el agente:
-
Registra el intento de forma duradera (no se rota la contraseña: un país equivocado no implica
que las credenciales estén comprometidas). -
Genera un aviso
wordpress_login_geo_blocked(nivel warning) con el usuario, dominio, motivo
e IP.
Accesos aceptados con la política “saltada” (skip / fail-open)
Para que quede claro que la política está activa pero se ignoró por diseño, los casos fail-open
también se registran:
Login country-policy skipped (reason=local-lan-allowed) for username=usuario, remote-addr=10.2.57.2, ...
Login country-policy skipped (reason=extra-allowed-ip) for username=usuario, remote-addr=203.0.113.10, ...
Login country-policy skipped (reason=geoip-unresolved) for username=usuario, geoip-ip=203.0.113.10, ...
Significado de cada reason:
| reason | Por qué se aceptó |
|---|---|
local-lan-allowed |
IP local/privada y la política tiene activado LAN/LocalLan/PrivateIp
|
extra-allowed-ip |
IP incluida en “Country policy extra ips” |
geoip-unresolved |
IP pública que no se pudo resolver (o exec no disponible): se acepta para no bloquear a nadie |
Cambios de configuración (auditoría)
Al guardar la política se emite un aviso de auditoría con quién, qué dominio y la configuración
aplicada:
wordpress_country_policy_updated: User [...] set country login policy for hosting [ejemplo.com] to enabled=yes, countries=[IT], local_lan_allowed=no, extra_ips=[]
Relación con el bloqueo por fuerza bruta
Conviene conocer esta interacción: cuando la política rechaza un login, WordPress lo convierte en un
fallo de autenticación, por lo que además de la línea Login geo-blocked detected se genera
también una línea Login failure detected. Esto significa que un geo-bloqueo cuenta como intento
fallido y, repetido desde una IP nueva, puede acabar provocando el bloqueo de esa IP a nivel de
firewall (kandadu). Es un efecto deseado (defensa extra contra atacantes persistentes).
El sistema es indulgente con las IPs que han autenticado correctamente hace poco, evitando bloquear
una IP legítima conocida:
Found IP 10.2.57.2 with login failures, but with proper auth previously (as much 12 hours ago)
Diagnóstico de resolución GeoIP
Para comprobar qué país resuelve Core-Admin para una IP (lo mismo que consulta el plugin), usa:
crad-geoip-lookup.pyc 203.0.113.10 --json
Salida (JSON, fácil de parsear):
{"ip": "203.0.113.10", "status": true, "country": "IT", "country_long": "Italy", "asn": "AS137", "organization": "..."}
Para una IP privada (10.x, 192.168.x…) o localhost, no se resuelve país: esa es la razón por la
que esos accesos caen en la regla LAN y no en la de país.
Relación con otras protecciones de acceso
Core-Admin ofrece tres mecanismos complementarios. Es importante entender en qué capa actúa cada
uno y qué protege, porque combinados forman una defensa en profundidad.
1. Limitación de login/administración por IP (limit_admin_to_ips)
En la propia ficha del WordPress (pestaña Security) puedes restringir el acceso a /wp-admin a
una lista de IPs concretas. Se implementa con un wp-admin/.htaccess (order deny,allow) a nivel
de Apache.
- Protege: el área de administración.
- Granularidad: IP / red.
- Capa: servidor web (antes de PHP).
- Cuándo: cuando administras siempre desde IPs fijas conocidas.
2. Limitación de peticiones POST por IP (limit_post_to_ips)
En Webhosting Management (opciones del hosting) puedes restringir todas las peticiones POST
del hosting a una lista de IPs/redes/IPv6. Se implementa con mod_rewrite sobre REMOTE_ADDR
(compatible con mod_remoteip), devolviendo 403 a cualquier POST que no provenga de una IP
permitida:
RewriteEngine On
RewriteCond %{REQUEST_METHOD} =POST
RewriteCond %{REMOTE_ADDR} !^203\.0\.113\.10$
RewriteRule .* - [F]
Como bloquear todo POST puede ser demasiado, existe skip_limit_post_to_ips_urls, que permite
excluir rutas concretas (una por línea, sin host ni protocolo, p.ej. /wp-json/.../callback,
/checkout-webhook.php) para que esas URLs sí acepten POST desde cualquier IP — útil para
callbacks de pasarelas de pago, webhooks, etc.
- Protege: cualquier envío de datos (logins, comentarios, XML-RPC, formularios, subidas…).
- Granularidad: IP / red / IPv6.
- Capa: servidor web (antes de PHP) → es la barrera más “dura”.
- Cuándo: cuando los envíos legítimos del sitio provienen de IPs conocidas.
3. Política de login por país (esta característica)
- Protege: el inicio de sesión (wp-login, XML-RPC, REST app-passwords).
- Granularidad: país (ISO-3166) + tokens LAN + IPs extra.
-
Capa: dentro de PHP, en el filtro
authenticate, solo en login correcto. - Cuándo: cuando no puedes enumerar las IPs de quienes acceden, pero sí los países.
Tabla comparativa
| Característica | Qué limita | Capa | Granularidad | Permite navegar el sitio |
|---|---|---|---|---|
limit_admin_to_ips |
Acceso a /wp-admin
|
Apache (.htaccess) | IP / red | Sí |
limit_post_to_ips |
Todas las peticiones POST del hosting | Apache (mod_rewrite) | IP / red / IPv6 | Sí (GET) |
| Política por país | El login (todas las vías) | PHP (authenticate) |
País + LAN + IPs | Sí |
Diferencias de protección y cómo combinarlas
-
limit_admin_to_ipsylimit_post_to_ipsactúan en Apache, antes de PHP: protegen incluso si
WordPress o un plugin tienen una vulnerabilidad en el flujo de login, porque la petición ni
siquiera llega a ejecutar código PHP. Son barreras de perímetro. - La política por país actúa dentro de PHP: es más “inteligente” (entiende de países y cubre
XML-RPC/REST), pero requiere que la petición llegue a WordPress. Es una puerta de login. - Para frenar fuerza bruta / DoS de POST en el perímetro,
limit_post_to_ipses lo más fuerte
(los POST de IPs no permitidas ni llegan a PHP). La política por país no frena que los
intentos lleguen a WordPress; de eso se encarga el seguimiento de fallos del WLT + kandadu.
Combinaciones recomendadas:
-
Sitio gestionado desde oficinas con IP fija:
limit_post_to_ipscon las IPs de oficina
(perímetro duro) +limit_admin_to_ipspara el panel. Máxima protección. -
Sitio gestionado desde ubicaciones variables pero dentro de unos países: política por país
(ES, AR, IT), opcionalmente conextra_allowed_ipspara alguna VPN concreta. Flexible sin tener
que mantener listas de IPs. -
Defensa en profundidad: combina ambas —
limit_post_to_ipsexcluyendo (skip_…_urls) las
rutas públicas necesarias, y la política por país como puerta adicional sobre el login.
Recomendaciones y buenas prácticas
- Mantén WLT activado; sin él la política no se aplica.
- Si administras desde la propia LAN del servidor, añade
LANexplícitamente; de lo contrario el
acceso interno quedará bloqueado por defecto. - Usa
extra_allowed_ipspara casos puntuales (una VPN en el extranjero) en lugar de añadir un país
entero. - Revisa periódicamente los avisos
wordpress_login_geo_blocked: te indican intentos de acceso con
credenciales correctas desde países no esperados (señal de credenciales filtradas). - Recuerda el factor proxy/CDN: si el sitio está detrás de un proxy inverso sin
mod_remoteip/
X-Forwarded-Forbien configurado,REMOTE_ADDRserá la IP del proxy y la geolocalización (y
limit_post_to_ips) no verán la IP real del cliente. Verifica que la IP real llega al servidor. - Activa 2FA en las cuentas con privilegios como capa adicional.
Resumen
| Acción | Dónde |
|---|---|
| Activar/configurar política por país | Ficha del WordPress → pestaña Security |
| Permitir red local | Añadir LAN (o LocalLan / PrivateIp) a los países |
| Permitir IPs concretas | Campo Country policy extra ips |
| Limitar acceso a wp-admin por IP |
limit_admin_to_ips (pestaña Security) |
| Limitar POST por IP |
limit_post_to_ips (Webhosting Management) |
| Diagnosticar país de una IP | crad-geoip-lookup.pyc <ip> --json |
| Ver bloqueos | syslog (Login geo-blocked detected) + aviso wordpress_login_geo_blocked
|




