Mostrando entradas con la etiqueta MD5. Mostrar todas las entradas
Mostrando entradas con la etiqueta MD5. Mostrar todas las entradas

domingo, 21 de noviembre de 2010

PoC de login seguro sobre HTTP

A raíz de este hilo en HackXCrack decidí programar un sistema de login que permitiese cierta seguridad ante atacantes pasivos aún funcionando sobre HTTP en plano, todo de forma transparente al usuario.

La idea es simple, cuando el usuario se intenta loguear, se le asigna un token que se utilizará como salt de una función hash que se aplicará a la contraseña (yo he utilizado MD5 porque tenía el código en python a mano), el resultado de la función será el que se envíe. El utilizar un token es importante porque evita ataques de replay, es decir, que el atacante pase a través de la función hash, enviando el resultado directamente, no tendría que conocer la contraseña original si el resultado es siempre el mismo. Además es importante que el token lo establezca el servidor y nunca quién se loguee, ya que lo podría modificar para que fuera el mismo que el del usuario original, con lo que se vuelve al ataque de replay.

Pero tiene bastantes problemas:
  • Si el atacante es activo (puede interceptar la conexión y modificar datos al vuelo), puede cambiar el código fuente de la página, invalidando esto.
  • Las contraseñas han de guardarse en texto plano, para poder realizar el hash a posteriori.
  • Como consecuencia de lo anterior se produce un aumento muy considerable de la carga del servidor, ya que en vez de hacer el hash una sola vez (ya que se supone que las contraseñas se guardan ya hasheadas), en el peor caso, ha de comprobarse el hash de todas las contraseñas cada intento de login.
La prueba (todo muy mínimo) está alojada en md5poc.webcindario.com, el código fuente se puede descargar aquí [md5poc.zip]

Btw... 1 año!!

miércoles, 28 de julio de 2010

Una ronda de bash ( I )

Hoy... scripts varios en bash.

Empezando por lo más simple, un cracker de MD5, simplemente lee el diccionario y va comparando las lineas con el hash objetivo [md5crack.sh]


./md5crack.sh <diccionario> <objetivo>




#!/usr/bin/env bash
# Escrito por kenkeiras
function showuse {
    echo "$0 <diccionario> <hash>"
    exit 1
}

if [ "$1" = "" ]
then
    showuse
fi

if [ "$2" = "" ]
then
    showuse
fi

i=0
for word in $(cat $1);do
    test=`echo -en "$word" |md5sum|cut -c1-32`
    if [ "$test" = "$2" ]
        then
            echo "-> $word"
            i=$(($i + 1))
        fi
done
echo "$i resultados"


Siguiendo con pruebas de /dev/tcp, buscar las contraseñas por defecto usando la web del cirt [get_pass_cirt.sh]:


./get_pass_cirt.sh <Criterio de búsqueda>



#!/usr/bin/env bash
# Escrito por kenkeiras

if [ "$1" = "" ]; then
    echo "No has especificado un criterio de búsqueda"
    exit 1
else
    key=$1
fi

# Se pide la web
exec 3<>/dev/tcp/cirt.net/80
echo -en "GET /passwords?criteria=$key\ HTTP/1.1\r\n" >&3
echo -en "HOST: cirt.net\r\n" >&3
echo -en "Connection: close\r\n\r\n" >&3
res=$(cat <&3) # Se leen los datos
exec 3>&- # Se cierra la conexion

n=0
for i in $res
    do
        if [ $n -eq 1 ] # Se comprueba si ya muestra el usuario/contraseña
            then
                echo $i|cut -d\> -f2|cut -d\< -f1
                n=0
        else if [ $n -ne 0 ] # Sino puede que venga pronto
            then
                n=$(($n - 1))
            fi
        fi
        out=$(echo $i|grep -e User -e \>Pass)
        if [ $? -eq 0 ] # Si es la parte del usuario/contraseña
            then
                out=$(echo $out|cut -d\> -f3|cut -d\& -f1|cut -d\< -f1)
                if [ ! $out = "" ]
                    then
                        echo $out|grep Pass >> /dev/null
                        if [ $? -eq 0 ]
                            then
                                n=2
                            else
                                n=3
                            fi
                        echo -n "$out:"
                    fi
            else
                echo $i|grep -e "</table>" >> /dev/null # Si se acabo el user/pass
                if [ $? -eq 0 ]
                    then
                        echo "----------------------------"
                    fi
            fi
    done


O usando la web de phenoelit [get_pass_phenoelit-us.sh] este tarda un rato, tiene que parsear 256 Kb's:

./get_pass_phenoelit-us.sh  <Empresa>



#!/usr/bin/env bash
# Escrito por kenkeiras

if [ "$1" = "" ]; then
    echo "No has especificado un criterio de búsqueda"
    exit 1
else
    key=$1
fi

# Se pide la web
exec 3<>/dev/tcp/www.phenoelit-us.org/80
echo -en "GET /dpl/dpl.html HTTP/1.1\r\n" >&3
echo -en "HOST: www.phenoelit-us.org\r\n" >&3
echo -en "Connection: close\r\n\r\n" >&3
res=$(cat <&3) # Se leen los datosexec 3>&- # Se cierra la conexion
res=$(echo $res|sed -e "s/\n//g" |sed -e "s/ /$/g" |sed -e "s/[<]/\n/g")

t=0
d=0
for i in $res
    do  
        echo $i|grep "/tr" >> /dev/null
        if [ $? -eq 0 ]
            then
                if [ $t -ne 0 ]
                    then
                        echo "------------------"
                    fi
                d=1
                t=0
            else
                echo $i|grep "</table>" >> /dev/null
                if [ $? -eq 0 ]
                    then
                        d=0
                    fi
                if [ $d -ne 0 ]
                    then
                        d=$(($d + 1))
                        if [ $d = 3 ]
                            then
                                out=$(echo $i|cut -d\> -f2|grep -i "$key")
                                if [ $? -eq 0 ]
                                    then
                                        echo $out|sed -e "s/$/ /g"
                                        t=1
                                    fi
                            fi
                        if [ $t -ne 0 ]
                            then
                                if [ $d = 11 ]
                                    then
                                        echo -n "User: "
                                        echo $i|cut -d\> -f2|sed -e "s/$/ /g"
                                    else
                                    if [ $d = 13 ]
                                        then
                                            echo -n "Password: "
                                            echo $i|cut -d\> -f2|sed -e "s/$º/ /g"
                                        fi
                                    fi
                            fi
                    fi
            fi
    done


Hasta ahora, la segunda parte por la tarde

ps: Todo bajo la WTFPL, claro

jueves, 22 de abril de 2010

Introduccion a la criptografia, con Python: MD5 (III)

Siguendo con el tema, en esta parte veremos como funciona la funcion de hash MD5:

¿Que es una funcion HASH?

En informática, Hash se refiere a una función o método para generar claves o llaves que representen de manera casi unívoca a un documento, registro, archivo, etc., resumir o identificar un dato a través de la probabilidad, utilizando una función hash o algoritmo hash. Un hash es el resultado de dicha función o algoritmo. [Wikipedia]
Es decir, es una funcion de un solo sentido (a partir del resultado no se puede recuperar el original) que identifica un "objeto" con precision, si cambia un byte, cambia todo el resultado.

Esto se suele utilizar (por ejemplo) para comprobar que las descargas se realizaron correctamente, comparando el hash del archivo original y el del archivo descargado, si son iguales, todo fue bien :)

MD5 es una funcion de hash, pero presenta debilidades, como colisiones, por eso se deberia evitar usar en el futuro, de todas formas, aun se utiliza y servira de introduccion para este tipo de funciones.
Vamos alla...



class md5:
    def __init__(self,s):

        ln=len(s)*8
        self.padd(s)

        self.attach_len(ln)
        self.buff_init()
        self.update(ln)

        self.final()


El primer paso es añadir un bit '1' a lo que se quere hashear, y despues añadir '0's hasta que el tamaño en bits sea igual a 448, siendo modulo 512 (o 56, modulo 64, en bytes):


    def padd(self,s):

        self.stream=s+"\x80"
        while ((len(self.stream)%64)!=56):

            self.stream+="\x00"


Despues hay que añadir a continuacion el tamaño en bits (al principio, antes del paso anterior) del archivo/mensaje/etc..., con un numero de 64 bits(8 bytes):


    def attach_len(self,ln):
        ln%=1<<64
        i=7

        l=[]
        while i>=0:
            k=256**i

            aux=ln/k
            l.append(aux)
            ln-=aux*(k)

            i-=1
        while len(l)>0:
            self.stream+=chr(l.pop(len(l)-1))


Se inicializan cuatro buffers (A,B,C y D) (de 32 bits) que se utilizan en el algoritmo:


    def buff_init(self):
        self.a = 0x67452301
        self.b = 0xefcdab89
        self.c = 0x98badcfe
        self.d = 0x10325476




Se procesa el  objeto/archivo/..., esto es la parte mas "opaca" del algoritmo...
Para comenzar se divide el objeto en partes de 64 bytes, concretamente en arrays de 16 elementos, con 4 bytes cada uno:


    def update(self,ln):
    
        i=0

        while i<(len(self.stream)/64):
            j=0

            x=[]
            while j<16:
                t=((ord(self.stream[(i*64)+(j*4)+0]))|

                 (ord(self.stream[(i*64)+(j*4)+1])<< 8)|

                 (ord(self.stream[(i*64)+(j*4)+2])<< 16)|

                 (ord(self.stream[(i*64)+(j*4)+3])<< 24))

                x.append(t)
                j+=1
            self.transform(x)

            i+=1



Despues, con cada division se obtienen los valores de los buffer's:


    def transform(self,x):

        AA=self.a
        BB=self.b
        CC=self.c

        DD=self.d


Y se le aplican una serie de transformaciones (veremos despues en detalle como funcionan esas transformaciones):


        #Ronda 1
        AA=FF (AA, BB, CC, DD, x[ 0], S11, 0xd76aa478) # 1

        DD=FF (DD, AA, BB, CC, x[ 1], S12, 0xe8c7b756) # 2

        CC=FF (CC, DD, AA, BB, x[ 2], S13, 0x242070db) # 3

        BB=FF (BB, CC, DD, AA, x[ 3], S14, 0xc1bdceee) # 4

        AA=FF (AA, BB, CC, DD, x[ 4], S11, 0xf57c0faf) # 5

        DD=FF (DD, AA, BB, CC, x[ 5], S12, 0x4787c62a) # 6

        CC=FF (CC, DD, AA, BB, x[ 6], S13, 0xa8304613) # 7

        BB=FF (BB, CC, DD, AA, x[ 7], S14, 0xfd469501) # 8

        AA=FF (AA, BB, CC, DD, x[ 8], S11, 0x698098d8) # 9

        DD=FF (DD, AA, BB, CC, x[ 9], S12, 0x8b44f7af) # 10

        CC=FF (CC, DD, AA, BB, x[10], S13, 0xffff5bb1) # 11

        BB=FF (BB, CC, DD, AA, x[11], S14, 0x895cd7be) # 12

        AA=FF (AA, BB, CC, DD, x[12], S11, 0x6b901122) # 13

        DD=FF (DD, AA, BB, CC, x[13], S12, 0xfd987193) # 14

        CC=FF (CC, DD, AA, BB, x[14], S13, 0xa679438e) # 15

        BB=FF (BB, CC, DD, AA, x[15], S14, 0x49b40821) # 16


        # Round 2
        AA=GG (AA, BB, CC, DD, x[ 1], S21, 0xf61e2562) # 17

        DD=GG (DD, AA, BB, CC, x[ 6], S22, 0xc040b340) # 18

        CC=GG (CC, DD, AA, BB, x[11], S23, 0x265e5a51) # 19

        BB=GG (BB, CC, DD, AA, x[ 0], S24, 0xe9b6c7aa) # 20

        AA=GG (AA, BB, CC, DD, x[ 5], S21, 0xd62f105d) # 21

        DD=GG (DD, AA, BB, CC, x[10], S22, 0x2441453) # 22

        CC=GG (CC, DD, AA, BB, x[15], S23, 0xd8a1e681) # 23

        BB=GG (BB, CC, DD, AA, x[ 4], S24, 0xe7d3fbc8) # 24

        AA=GG (AA, BB, CC, DD, x[ 9], S21, 0x21e1cde6) # 25

        DD=GG (DD, AA, BB, CC, x[14], S22, 0xc33707d6) # 26

        CC=GG (CC, DD, AA, BB, x[ 3], S23, 0xf4d50d87) # 27

        BB=GG (BB, CC, DD, AA, x[ 8], S24, 0x455a14ed) # 28

        AA=GG (AA, BB, CC, DD, x[13], S21, 0xa9e3e905) # 29

        DD=GG (DD, AA, BB, CC, x[ 2], S22, 0xfcefa3f8) # 30

        CC=GG (CC, DD, AA, BB, x[ 7], S23, 0x676f02d9) # 31

        BB=GG (BB, CC, DD, AA, x[12], S24, 0x8d2a4c8a) # 32


        # Round 3
        AA=HH (AA, BB, CC, DD, x[ 5], S31, 0xfffa3942) # 33

        DD=HH (DD, AA, BB, CC, x[ 8], S32, 0x8771f681) # 34

        CC=HH (CC, DD, AA, BB, x[11], S33, 0x6d9d6122) # 35

        BB=HH (BB, CC, DD, AA, x[14], S34, 0xfde5380c) # 36

        AA=HH (AA, BB, CC, DD, x[ 1], S31, 0xa4beea44) # 37

        DD=HH (DD, AA, BB, CC, x[ 4], S32, 0x4bdecfa9) # 38

        CC=HH (CC, DD, AA, BB, x[ 7], S33, 0xf6bb4b60) # 39

        BB=HH (BB, CC, DD, AA, x[10], S34, 0xbebfbc70) # 40

        AA=HH (AA, BB, CC, DD, x[13], S31, 0x289b7ec6) # 41

        DD=HH (DD, AA, BB, CC, x[ 0], S32, 0xeaa127fa) # 42

        CC=HH (CC, DD, AA, BB, x[ 3], S33, 0xd4ef3085) # 43

        BB=HH (BB, CC, DD, AA, x[ 6], S34, 0x4881d05) # 44

        AA=HH (AA, BB, CC, DD, x[ 9], S31, 0xd9d4d039) # 45

        DD=HH (DD, AA, BB, CC, x[12], S32, 0xe6db99e5) # 46

        CC=HH (CC, DD, AA, BB, x[15], S33, 0x1fa27cf8) # 47

        BB=HH (BB, CC, DD, AA, x[ 2], S34, 0xc4ac5665) # 48


        # Round 4
        AA=II (AA, BB, CC, DD, x[ 0], S41, 0xf4292244) # 49

        DD=II (DD, AA, BB, CC, x[ 7], S42, 0x432aff97) # 50

        CC=II (CC, DD, AA, BB, x[14], S43, 0xab9423a7) # 51

        BB=II (BB, CC, DD, AA, x[ 5], S44, 0xfc93a039) # 52

        AA=II (AA, BB, CC, DD, x[12], S41, 0x655b59c3) # 53

        DD=II (DD, AA, BB, CC, x[ 3], S42, 0x8f0ccc92) # 54

        CC=II (CC, DD, AA, BB, x[10], S43, 0xffeff47d) # 55

        BB=II (BB, CC, DD, AA, x[ 1], S44, 0x85845dd1) # 56

        AA=II (AA, BB, CC, DD, x[ 8], S41, 0x6fa87e4f) # 57

        DD=II (DD, AA, BB, CC, x[15], S42, 0xfe2ce6e0) # 58

        CC=II (CC, DD, AA, BB, x[ 6], S43, 0xa3014314) # 59

        BB=II (BB, CC, DD, AA, x[13], S44, 0x4e0811a1) # 60

        AA=II (AA, BB, CC, DD, x[ 4], S41, 0xf7537e82) # 61

        DD=II (DD, AA, BB, CC, x[11], S42, 0xbd3af235) # 62

        CC=II (CC, DD, AA, BB, x[ 2], S43, 0x2ad7d2bb) # 63

        BB=II (BB, CC, DD, AA, x[ 9], S44, 0xeb86d391) # 64



Y por ultimo, se devuelven los valores que se tomaron de los buffer a ellos:


        self.a=(self.a+AA)&0xFFFFFFFF
        self.b=(self.b+BB)&0xFFFFFFFF

        self.c=(self.c+CC)&0xFFFFFFFF
        self.d=(self.d+DD)&0xFFFFFFFF



Una cosa... el hacer and con 0xFFFFFFFF a las variables es para evitar que ocupen mas de 32 bytes, otra forma seria hacerlo modulo 0x100000000, pero esta es mas rapida.
Bien, veamos en que consisten las transformaciones... para empezar, S11,S12,S.. son constantes predefinidas, sus valores son estos:


S11=7
S12=12

S13=17
S14=22
S21=5
S22=9
S23=14

S24=20
S31=4
S32=11
S33=16
S34=23

S41=6
S42=10
S43=15
S44=21

Ademas en las transformaciones se utilizan unas funciones basicas:
def F(x,y,z):

    return (x&y)|((~x&0xFFFFFFFF)&z)

def G(x,y,z):
    return (x&z)|(y&(~z&0xFFFFFFFF))

def H(x,y,z):
    return x^y^z

def I(x,y,z):
    return y^(x|(~z&0xFFFFFFFF))

# Rotacion
def rotate_left(x,y):
    return ((x<<y)|(x>>(32-y)))

Una vez definido eso, las transformaciones no son demasiado complejas:
def FF(a,b,c,d,x,s,ac):

    a=(a+(F(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a

def GG(a,b,c,d,x,s,ac):

    a=(a+(G(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a

def HH(a,b,c,d,x,s,ac):

    a=(a+(H(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a

def II(a,b,c,d,x,s,ac):

    a=(a+(I(b,c,d)+x+ac))&0xFFFFFFFF

    a= rotate_left(a,s)&0xFFFFFFFF
    a=(a+b)&0xFFFFFFFF

    return a



Como se puede ver, el patron es bastante obvio:


def XX(a,b,c,d,x,s,ac):

    a=(a+(X(b,c,d)+x+ac))

    a= rotate_left(a,s)
    a=(a+b)

    return a


El resto solo sirve para evitar que los valores se salgan de los 32 bits

Y por ultimo, se presenta como resultado lo que hay en los buffers, comenzando por el byte menos significativo de A y acabando por el mas significativo de D, normalmente se hace directamente en la representacion hexadecimal:


    def final(self):

        self.digest=[]
        for some in [self.a,self.b,self.c,self.d]:

            t=uint4tochar(some)
            while len(t)>0:

                thing=t.pop(len(t)-1)
                self.digest.append(myhex(thing)[2:4])

 
    def hexdigest(self):
        from string import join

        return join(self.digest,'')



Las funciones utilizadas son estas:


#Pasa un entero de 4 bytes a un array de 4 bytes
def uint4tochar(s):
    i=0
    t=[0,0,0,0]

    while i<4:
        aux=(s>>(8*i))&0xFF

        t[3-i]=int(aux)
        i+=1

    return t

#Devuelve un hexadecimal con 4 caracteres o mas
def myhex(a):
    r=hex(a)

    if (len(r)==3):
        r="0x0"+r[2]

    return r


El código completo aqui [md5.py]

Si se ejecuta directamente (no importandolo), hace unas pruebas para comprobar que funciona bien, las comprobaciones se hacen contra hashlib:

d41d8cd98f00b204e9800998ecf8427e = d41d8cd98f00b204e9800998ecf8427e True
81dc9bdb52d04dc20036dbd8313ed055 = 81dc9bdb52d04dc20036dbd8313ed055 True
9e107d9d372bb6826bd81d3542a419d6 = 9e107d9d372bb6826bd81d3542a419d6 True
e4d909c290d0fb1ca068ffaddf22cbd0 = e4d909c290d0fb1ca068ffaddf22cbd0 True
6f728eb0a9ef9387cb17dba7cc2116fb = 6f728eb0a9ef9387cb17dba7cc2116fb True


Hasta la proxima ;)

[Referencia]
RFC 1321 
MD5 - Wikipedia
Otra implementacion