17 años en Internet

11 mayo 2011

Modificando el ttysnoops

Ayer os enseñé a hacer uso del ttysnoop y ttysnoops para poder monitorizar sesiones de security shell (SSH). Uno de los puntos más molestos es que cada vez que hacemos uso del ttysnoop la aplicación nos consulta por el password de root. Y encima no podemos hacer uso de un script de "spawn/expect/send" para saltarnos este paso (ya lo he probado).

Parece ilógico, ¿verdad? Ya me he logueado como root para poder hacer uso de esta aplicación, ¿para qué voy a querer volverme a validar? Realmente lo que sucede es que a nivel de código fuente, dentro del config.h, se define a root como el usuario snoop predeterminado. Tras analizar el código del ttysnoops, entendí que esta solicitud de contraseña está puesta por mero capricho del programador.

En el bucle principal, a través del método authenticate, el programa crea un proceso hijo encargado de pedir al usuario la contraseña del usuario "snoop". A continuación he remarcado en rojo los valores de retorno y en verde el texto printado para solicitar el password del usuario. A primera vista el código asusta, pero veréis que es bastante inteligible.


    311 void authenticate (int fd)
    312 {
    313         struct passwd *pw;
    314
    315 #ifdef SHADOW_PWD
    316         struct spwd *spw;
    317 #endif
    318
    319         int ret = 0;
    320         char buff[16], *pwbuff;
    321
    322         if ((authpid = fork()) == 0)    /* authentication child */
    323         {
    324                 dup2 (fd, STDIN_FILENO);
    325                 dup2 (fd, STDOUT_FILENO);
    326                 dup2 (fd, STDERR_FILENO);
    327                 if (fd > STDERR_FILENO)
    328                         close (fd);
    329
    330                 if ((pw = getpwnam(SNOOPUSER)) == NULL)
    331                         exit (1);
    332
    333 #ifdef SHADOW_PWD
    334                 if ((spw = getspnam(SNOOPUSER)) == NULL)
    335                     exit(1);
    336 #endif
    337
    338                 printf ("Connected to %s snoop server...\r\n", ptynam);
    339                 printf ("%s (ASCII %d) to suspend, %s (ASCII %d) to terminate.\r\n",
    340                         SC_STRING, SUSP_CHAR, TC_STRING, TERM_CHAR);
    341                 printf ("Snoop password:"); fflush (stdout);
    342                 if (inputs(buff, 16, stdin) == 0)
    343                 {
    344 #ifndef SHADOW_PWD
    345                         if (strcmp(pw->pw_passwd, crypt(buff, pw->pw_passwd)) == 0)
    346 #else
    347                         if (strcmp(spw->sp_pwdp, crypt(buff, spw->sp_pwdp)) == 0)
    348 #endif
    349                         {
    350                                 printf ("\r\nVerified OK... Snoop started.\r\n");
    351                                 ret = 1;
    352                         }
    353                         else
    354                                 printf ("\r\nPassword incorrect.\r\n");
    355                 }
    356
    357                 fflush (stdout);
    358                 exit (ret);
    359         }
    360 }

En pocas palabras, el proceso ttyspoons crea un hijo para solicitar al usuario la contraseña de root. Este proceso verifica que el usuario root exista y que la contraseña sea válida para dicho usuario.

Ahora veamos el manejador del proceso padre, encargado de gestionar si la autenticación (realizada en el proceso hijo) ha sido correcta:


    404 void sigchld (int sig)
    405 {
    406         int status, pid;
    407         sig = sig;
    408
    409         if ((pid = wait(&status)) == authpid)
    410         {
    411                 if (((status >> 8) & 0xff) == 1)
    412                 {
    413                         snoopfd = authfd;
    414                         fdmax = max(fdmax, snoopfd);
    415                         syslog (LOG_INFO, "snoop on %s (%s)", ttyname(STDIN_FILENO),
    416                                                 leafname(sockname));
    417                 }
    418                 else
    419                         close (authfd);
    420
    421                 authpid = authfd = -1;
    422         }
    423         else if (pid == pgmpid)
    424                 raise (SIGHUP);
    425
    426         signal (SIGCHLD, sigchld);
    427 }


Según el código de este manejador, para cumplir la verificación deberemos de hacer que el proceso hijo finalice con valor 1. Cualquier otro valor devolverá error de autenticación.

Visto esto, he modificado el método authenticate para que JAMAS DE LOS JAMASES se nos solicite la contraseña y que además la verificación del padre se pase de forma correcta:

    357 void authenticate (int fd)
    358 {
    359          if ((authpid = fork()) == 0)    /* authentication child */
    360          {
    361                  dup2 (fd, STDIN_FILENO);
    362                  dup2 (fd, STDOUT_FILENO);
    363                  dup2 (fd, STDERR_FILENO);
    364                  if (fd > STDERR_FILENO)
    365                          close (fd);
    366                  exit(1);
    367          }
    368 }

Como veis el método ha adelgazado bastante... y encima funciona: Si eres root no te solicitará ninguna contraseña de usuario snoop, y si no eres root no podrás clonar ninguna terminal. Por cierto, si queréis recompilar las fuentes para Ubuntu o Debian, vais a tener que realizar las siguientes modificaciones.

No hay comentarios:

Publicar un comentario

Si te ha gustado la entrada o consideras que algún dato es erróneo o símplemente deseas dar algún consejo, no dudes en dejar un comentario. Todo feedback es bienvenido siempre que sea respetuoso. También puedes contactarme vía Twitter @Hamster_ruso si lo consideras necesario.