{"id":365,"date":"2026-10-24T12:41:29","date_gmt":"2026-10-24T16:41:29","guid":{"rendered":"https:\/\/juredev.com\/blog\/?p=365"},"modified":"2026-10-24T12:41:32","modified_gmt":"2026-10-24T16:41:32","slug":"como-detectar-y-limpiar-claves-ssh-olvidadas-en-authorized_keys","status":"publish","type":"post","link":"https:\/\/juredev.com\/blog\/2026\/10\/como-detectar-y-limpiar-claves-ssh-olvidadas-en-authorized_keys\/","title":{"rendered":"C\u00f3mo detectar y limpiar claves SSH olvidadas en authorized_keys"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Un inicio de sesi\u00f3n SSH exitoso sin intentos fallidos previos puede parecer inofensivo, pero es una se\u00f1al que merece revisi\u00f3n. En este art\u00edculo repasamos, paso a paso, c\u00f3mo investigar un acceso as\u00ed y c\u00f3mo evitar que se repita, bas\u00e1ndonos en un incidente real: una clave SSH de un servidor de compilaci\u00f3n retirado segu\u00eda autorizada en producci\u00f3n.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El escenario<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Un servidor Linux de producci\u00f3n registr\u00f3 dos inicios de sesi\u00f3n SSH exitosos desde una IP externa durante la noche. Lo llamativo: no hubo intentos fallidos de contrase\u00f1a antes. Eso descarta un ataque de fuerza bruta y apunta a que el acceso se hizo con credenciales v\u00e1lidas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 1: Identificar el m\u00e9todo de autenticaci\u00f3n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Revisa los registros de SSH y busca las l\u00edneas de inicio de sesi\u00f3n exitoso. Seg\u00fan tu sistema, estar\u00e1n en uno de estos lugares:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Journal de systemd<\/strong> (por defecto en distribuciones recientes como Debian 13):<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  journalctl -u ssh | grep Accepted<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">En algunos sistemas el servicio se llama sshd; si el comando no devuelve nada, prueba con -u sshd.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Archivos de log tradicionales:<\/strong> <code>\/var\/log\/auth.log<\/code> (Debian\/Ubuntu con <code>rsyslog<\/code>) o <code>\/var\/log\/secure<\/code> (RHEL\/CentOS).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Si ves esto:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Accepted publickey<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">significa que el acceso fue con clave p\u00fablica, no con contrase\u00f1a. Quien entr\u00f3 ten\u00eda la clave privada correspondiente a una clave autorizada en el servidor.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 2: Obtener la huella de la clave usada<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">La l\u00ednea <code>Accepted publickey<\/code> incluye la huella digital (fingerprint) de la clave con la que se autentic\u00f3 la sesi\u00f3n, con un formato similar a este:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Accepted publickey for deploy from &lt;IP> port &lt;PUERTO> ssh2: ED25519 SHA256:xxxxxxxxxxxx<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Anota la huella (<code>SHA256:...<\/code>), la IP de origen y la hora.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 3: Compararla con las claves autorizadas<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Revisa el archivo de claves autorizadas del usuario con el que se inici\u00f3 sesi\u00f3n:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/home\/deploy\/.ssh\/authorized_keys<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Para ver la huella de cada clave del archivo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keygen -lf \/home\/deploy\/.ssh\/authorized_keys<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Compara cada huella con la que sacaste del registro. En nuestro caso hab\u00eda una sola clave, y coincid\u00eda.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 4: Revisar el comentario de la clave<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">El comentario al final de cada clave suele indicar su origen. Aqu\u00ed era:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>deploy@build-prerelease<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">B\u00fascalo en tu inventario de servidores y registros internos. En nuestro caso, ese servidor de compilaci\u00f3n hab\u00eda sido retirado meses atr\u00e1s, pero su clave nunca se elimin\u00f3 del servidor de producci\u00f3n.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Ojo: el comentario es solo texto y puede editarse. \u00dasalo como pista, pero conf\u00eda en la huella para la verificaci\u00f3n.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 5: Determinar desde cu\u00e1ndo existe la clave<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Revisa la marca de tiempo del archivo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>stat \/home\/deploy\/.ssh\/authorized_keys<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">La clave llevaba ah\u00ed desde octubre de 2025. Esto indica que no era una persistencia reci\u00e9n plantada por el atacante, sino una relaci\u00f3n de confianza antigua que nadie revis\u00f3. Ten en cuenta que la fecha de modificaci\u00f3n refleja el \u00faltimo cambio del archivo completo, no necesariamente cu\u00e1ndo se a\u00f1adi\u00f3 una clave en particular.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 6: Confirmar el origen de las sesiones<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Revisa los accesos exitosos dentro de la ventana de tiempo sospechosa y verifica si ambas sesiones vinieron de la misma IP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>journalctl -u ssh --since \"AAAA-MM-DD HH:MM\" --until \"AAAA-MM-DD HH:MM\" | grep -E \"Accepted|session opened\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Esto te da la <strong>IP de origen, la hora y el m\u00e9todo de autenticaci\u00f3n<\/strong> de cada sesi\u00f3n, y las l\u00edneas <code>session opened<\/code> \/ <code>session closed<\/code> te permiten estimar su duraci\u00f3n.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Nota sobre <code>last<\/code>:<\/strong> en distribuciones recientes como Debian 13 el comando <code>last<\/code> ya no viene incluido (desde util-linux 2.40 se separ\u00f3 en el paquete <code>wtmpdb<\/code>). Puedes instalarlo con <code>apt install wtmpdb<\/code> y usar <code>wtmpdb last<\/code>, pero solo registrar\u00e1 sesiones a partir de su instalaci\u00f3n, as\u00ed que no sirve para investigar un incidente pasado. Para eso, usa el journal o <code>auth.log<\/code>.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 7: Buscar qu\u00e9 se toc\u00f3 durante las sesiones<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Busca archivos creados o modificados en la ventana de tiempo de las sesiones:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>find \/opt \/home -type f -newermt \"AAAA-MM-DD HH:MM\" ! -newermt \"AAAA-MM-DD HH:MM\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Ajusta las fechas y los directorios a tu caso.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">En el incidente apareci\u00f3 una copia de seguridad del archivo .env de la aplicaci\u00f3n en \/opt\/app\/. Los archivos de entorno suelen contener credenciales de base de datos, claves de API y secretos de la aplicaci\u00f3n, as\u00ed que hay que asumir que quedaron expuestos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Paso 8: Contener y remediar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Con la evidencia en mano, estas fueron las acciones:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Desactivar la cuenta afectada<\/strong> (<code>deploy<\/code>).<\/li>\n\n\n\n<li><strong>Eliminar la clave obsoleta<\/strong> de <code>authorized_keys<\/code>.<\/li>\n\n\n\n<li><strong>Rotar los secretos de la aplicaci\u00f3n:<\/strong> credenciales de base de datos, claves de API y cualquier otro valor del <code>.env<\/code>.<\/li>\n\n\n\n<li><strong>Auditar el resto de la infraestructura<\/strong> en busca de otros servidores que conf\u00eden en la misma clave.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Para el punto 4, una b\u00fasqueda por huella en cada servidor ayuda a encontrar rastros de la misma clave:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh-keygen -lf \/home\/USUARIO\/.ssh\/authorized_keys<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Rep\u00edtelo para cada usuario con acceso SSH y compara con la huella que identificaste.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3mo prevenirlo<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Inventar\u00eda tus claves SSH<\/strong>: documenta qu\u00e9 clave pertenece a qu\u00e9 persona, servidor o servicio.<\/li>\n\n\n\n<li><strong>Incluye la revisi\u00f3n de claves en el proceso de baja<\/strong>: cuando retires un servidor, un empleado o un servicio, elimina sus claves de todos los sistemas donde estaban autorizadas.<\/li>\n\n\n\n<li><strong>Audita authorized_keys peri\u00f3dicamente<\/strong>: revisa que cada clave tenga un due\u00f1o y un prop\u00f3sito vigentes.<\/li>\n\n\n\n<li><strong>Usa claves con alcance limitado<\/strong>: en authorized_keys puedes restringir una clave con opciones como from=\u00bbIP\u00bb o command=\u00bb&#8230;\u00bb, para que aunque se filtre tenga un alcance reducido.<\/li>\n\n\n\n<li><strong>Considera certificados SSH con caducidad<\/strong>: a diferencia de las claves est\u00e1ticas, expiran solos.<\/li>\n\n\n\n<li><strong>Monitorea inicios de sesi\u00f3n inusuales<\/strong>: configura alertas por accesos desde IPs nuevas, aunque la autenticaci\u00f3n sea v\u00e1lida.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Descuidos que se pueden aprovechar<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">No todo compromiso empieza con malware ni con una contrase\u00f1a d\u00e9bil. A veces el atacante solo necesita una credencial que tu entorno olvid\u00f3 que exist\u00eda. Las contrase\u00f1as expiran, los empleados se van y los servidores se retiran, pero una clave SSH puede permanecer en <code>authorized_keys<\/code> durante a\u00f1os si nadie la revisa.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Auditar y rotar claves no es glamuroso, pero es una de las defensas m\u00e1s baratas y efectivas que puedes tener.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Un inicio de sesi\u00f3n SSH exitoso sin intentos fallidos previos puede parecer inofensivo, pero es una se\u00f1al que merece revisi\u00f3n. En este art\u00edculo repasamos, paso a paso, c\u00f3mo investigar un acceso as\u00ed y c\u00f3mo evitar que se repita, bas\u00e1ndonos en un incidente real: una clave SSH de un servidor de compilaci\u00f3n retirado segu\u00eda autorizada en [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_piko_noindex":"","footnotes":""},"categories":[18],"tags":[24,28],"class_list":["post-365","post","type-post","status-publish","format-standard","hentry","category-guia","tag-linux","tag-seguridad"],"_links":{"self":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/365","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/comments?post=365"}],"version-history":[{"count":0,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/365\/revisions"}],"wp:attachment":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/media?parent=365"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/categories?post=365"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/tags?post=365"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}