sábado, 27 de julio de 2019

Criptografía (CLVI): Solución Reto picoCTF 2018 "Secure Logon"

Solución a otro reto de la plataforma picoCTF 2018.

Aunque en dicha plataforma este desafío está catalogado como de 'Web Exploitation', en mi opinión, encaja mejor en la categoría de 'Cryptography', por lo que en este humilde blog lo catalogo de forma preferente como de criptografía.

En este reto se ve involucrado nuevamente el modo de operación CBC ('Cipher Block Chaining'de los algoritmos de cifrado por bloques, y creo que tiene un nivel de dificultad alto ().

- Secure Logon - Points: 500:

Su enunciado dice lo siguiente: '
Uh oh, the login page is more secure... I think.
(link). Source'.
Solución: La pista que se da en la pestaña 'Hints' ('There are versions of AES that really aren't secure') indica que en este desafío se ve involucrado el citptosistema AES, y a la vista del archivo fuente que se proporciona (server_noflag.py) puedo ver que se emplea dicho algoritmo de cifrado por bloques en modo de operación CBC.

A través del link que se proporciona voy a la página de login del sitio web del reto:
En primer lugar pruebo a autenticarme con usuario "admin" y password "admin", y se me muestra un mensaje de error:
Seguidamente pruebo con un usuario y una contraseña cualesquiera: "username" y "password", respectivamente:
El proceso de login finaliza con éxito, pero se me informa de que no se me muestra la flag y de la existencia de una 'cookie' que, junto con el usuario y contraseña que he introducido, tiene valor 0 en 'admin', de lo que deduzco que para ver la flag 'admin' debería tener valor 1.

Acudo a ver la 'cookie' en cuestión:
Como se puede ver en el archivo fuente que se proporciona en el reto, la 'cookie' está cifrada utilizando AES en modo CBC y el resultado se codifica en base64.

Además, en el archivo fuente confirmo que para ver la flag el valor de 'admin' debe ser 1 y también veo que antes del cifrado se pasa el diccionario al método json.dumps() con el argumento sort_keys=True, por lo que el codificador devuelve las claves del objeto JSON ordenadas:
Por lo que sé dónde están almacenados los pares ('nombre':'valor') en la 'cookie': {'admin': 0, 'password': 'password', 'username': 'username'}.

Dicho todo lo anterior, ahora sólo me queda modificar el valor de 'admin' para ponerlo a 1. Para ello utilizo el ataque 'bit-flipping' con objeto de cambiar el Byte undécimo del vector de inicialización (IV) para que se produzca la modificación deseada en el Byte undécimo del primer bloque de texto plano (pase de 0 a 1).

En este blog he puesto hace poco un reto en el que puse un ejemplo de este tipo de ataque y de él se deduce que la modificación que tengo que realizar es la siguiente:

C'0[11]= C0[11] XOR P1[11] XOR P'1[11] = C0[11] XOR 0 XOR 1 = C0[11] XOR 1

Donde: C'0[11] = Valor al que hay que modificar el Byte 11 de C0 (IV); 
C0[11] = Byte 11 del bloque 0 del criptograma original (Byte 11 del IV original);
 P1[11] = Byte 11 del bloque 1 del texto plano original;
 P'1[11] = Valor deseado para el Byte 11 del bloque 1 del texto plano.

Es decir, basta con modificar el Byte 11 del vector de inicialización (IV) con el resultado de la operación XOR de ese mismo Byte con 1.

Creo un pequeño script en python para obtener la nueva 'cookie':

import base64

cookie='dAnfqmg2vDeyExZPi+AAklJ3UZoyKX3HSNfuisg7vIGnXgmnYUj8qAbcp4wvgJEoz35G/i48YK6B7fSenit4xzx+g9SMXydp23BwY/v5vKA='
print('[+] Cookie original:',cookie)
cookie=bytearray(base64.b64decode(cookie))

cookie[10]^=1
cookie=base64.b64encode(cookie).decode('utf-8')
print('[+] Cookie nueva   :',cookie)

Lo ejecuto:
Modifico la 'cookie' con el nuevo valor:
Guardo el cambio realizado, después: botón derecho ratón y clic en la opción "Volver a cargar Ctr + R", y ya puedo ver la solución a este reto:

martes, 23 de julio de 2019

Criptografía (CLV): ataque a cifrados por bloques en modo CBC mediante "Padding Oracle" (I)

En este y siguientes posts comparto lo que voy aprendiendo sobre los ataques a los algoritmos de cifrado simétrico por bloques. En este caso me referiré al ataque 'Padding Oracle' a cifradores de este tipo en modo de operación CBC ('Cipher Block Chaining').

Para ello, explico muy brevemente lo que he entendido sobre este ataque (por favor, como siempre, agradecería que si no lo he comprendido bien y/o no lo explico correctamente, se realicen las correcciones y/o ampliaciones de información oportunas en forma de comentarios a esta entrada).

Ante de empezar, comentar que este tipo de ataque sirve tanto para descifrar criptogramas como para "cifrar" mensajes en claro (lo he entrecomillado porque creo que es más correcto decir: obtener un texto cifrado para forzar que éste se descifre como el texto plano deseado, ya que hay que tener en cuenta que no se conoce la clave de cifrado/descifrado y mediante este ataque no se consigue obtenerla). No obstante, en este post pondré el foco única y exclusivamente en el primero de los aspectos comentados, el descifrado, mientras que el segundo, el cifrado de mensajes, queda para ser tratado en una entrada posterior. 

Este tipo de ataque es posible cuando un servidor, ante una petición de descifrado, informa sobre si se produce un error o no conforme a si el relleno ('padding') del criptograma que se le envía es incorrecto o válido, respectivamente. Como más adelante diré, en estos casos, el atacante adquiere una ventaja fundamental para descifrar posibles criptogramas interceptados e, incluso, para modificarlos con objeto de que sean descifrados conforme a su conveniencia.

Pero, como en todo en esta vida, las cosas se comprenden mejor si se pone algún ejemplo y, para ello, utilizo un reto tipo CTF sobre criptografía de la plataforma picoCTF 2018. En dicho desafío se plantea lo siguiente: '
Can you help us retreive the flag from this crypto service? Connect with nc 2018shell.picoctf. com 27533. We were able to recover some Source Code'.

Me conecto al servicio, se me muestra un ejemplo de 'cookie' y se me pide que introduzca la mía:
Después de realizar sucesivas pruebas, introduzco la siguiente cadena (64 caracteres "a"):

 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

 y se me informa de que el relleno es incorrecto:
Lo que me indica, tal y como he dicho anteriormente, que este servicio es vulnerable al ataque 'Padding Oracle'.

A la vista del archivo fuente que también se proporciona en este reto (pkcs7.py), veo que el servicio descifra la 'cookie' que se introduce utilizando el criptosistema AES en modo CBC y sólo se proporcionará la flag (se logrará el acceso deseado y, por ende, se conseguirá la información secreta) si el relleno PKCS#7 es válido, el JSON obtenido tras el descifrado también es válido, y en él el valor de "is_admin" es "true" y la fecha de caducidad de la 'cookie', valor de "expires", es posterior a la del sistema, es decir, no ha caducado. Bonito reto y, aunque se puede resolver sin descifrar la 'cookie' que se muestra, voy a utilizar esta última como ejemplo en este post para ver cómo es posible su descifrado sin conocer la clave mediante el ataque 'Padding Oracle'.

Antes de proceder con el ataque, y aunque en este post, tal y como he comentado, únicamente me voy a centrar en el descifrado de la 'cookie' que se proporciona como ejemplo, me conecto de nuevo al servicio y copio e introduzco la misma 'cookie' que se muestra:
Y veo que, efectivamente, no se me muestra la flag porque la 'cookie' introducida ha caducado y, además y aunque estuviera todavía vigente, no soy administrador.

Adicionalmente, comentar muy brevemente que el relleno PKCS#7 consiste en agregar al último bloque de texto plano tantos Bytes como falten para completar su longitud, caso de que éste tenga un tamaño inferior, con la que le correspondería conforme al tamaño de la clave empleada (por ejemplo: en AES-128 serían 16 Bytes).

Cada uno de los Bytes agregados contiene precisamente como valor el número de Bytes de relleno. Por ejemplo: supongamos que el último bloque de texto plano tiene 3 Bytes, es decir, faltan 13 Bytes para llegar a completar el tamaño de 16 Bytes. Pues bien, en este caso se añadirían 13 Bytes con valor 13 ('0d' en hexadecimal). Es importante hacer notar que en caso de que el último bloque tenga una longitud de 16 Bytes se añadiría también un último bloque de texto plano con valor 16 ('10' en hexadecimal) en todos sus Bytes.

El relleno se elimina después del descifrado, por lo que no afecta al contenido del texto plano original.

Y dicho esto vuelvo al ataque 'Padding Oracle', pero antes también hay que recordar el funcionamiento del descifrado en modo de operación CBC:
Es decir:
Lo primero que hace el servidor es descifrar todos los bloques de texto cifrado de la manera que se muestra en la figura anterior, después valida el relleno PKCS#7, elimina éste y obtiene el texto plano del mensaje.

Considerando como ejemplo la 'cookie' que se nos proporciona al conectarnos al servicio, el ataque se inicia utilizando la fuerza bruta para obtener el último Byte del texto plano (P6[16]) correspondiente al último Byte (C6[16]) del último bloque de texto cifrado (C6).

Para ello, genero un nuevo bloque cifrado n-1 con todos sus Bytes con valor \x00 y concateno este nuevo bloque (C'5) con el último bloque de texto cifrado (C6):

C’5 || C6 = 00000000000000000000000000000000fd8703dce82b480d21dfb56717e3a1ec

envío al servidor esa cadena cifrada de dos bloques. Tal y como he dicho, el servicio:

a) Descifra el segundo bloque (C6).

b) Realiza XOR entre Dk(C6y C’5.

c) Verifica el relleno del bloque resultante y si no es correcto informa de que éste no es válido, lo que es más que probable, puesto que hay 1 caso favorable entre 256 casos posibles (en hexadecimal de \x00 a \xff, o en decimal de 0 a 255).

d) Si el relleno no es válido aplico fuerza bruta en el Byte 16 del bloque cifrado n-1 (C’5), es decir, voy probando todos los posibles valores en C’5[16] (de \x00 a \xff), hasta obtener en el Byte 16 del texto plano en claro (P’6[16]) un relleno válido (\x01).
Posteriormente a que el proceso de fuerza bruta en “XXproduzca un relleno válido (P’6[16] = \x01) puedo averiguar el valor del último Byte ("??") de Dk(C6) de la siguiente manera: Dk(C6)[16] = C'5[16] \x01.

Y, por consiguiente, puedo obtener el valor del último Byte del texto plano sin más que realizar la siguiente operaciónP6[16] = Dk(C6)[16]  C5[16].

Para verlo más claro, creo un pequeño script en python, envío al servidor la cadena indicada anteriormente (C’5 || C6) y el servicio me informa de que el valor de C'5[16] que produce un relleno válido es \x1d. Por tanto:

Dk(C6)[16] = \x1d  \x01 = \x1c
P6[16] = \x1c  \x11 = \x0d

Con lo que el valor del último Byte del texto plano del último bloque es \x0d, de momento sólo un valor de relleno.

El valor del Byte anterior del texto plano del último bloque se puede obtener de la siguiente manera:

1.- Pongo el valor de C’5[16] = Dk(C6)[16]  \x02 \x1c  \x02 = \x1e, con objeto de forzar a que, tras aplicar el proceso de fuerza bruta en el Byte 15 del bloque cifrado n-1 (C’5), el relleno válido que se produzca sea P’6[15] || P’6[16] = \x02 || \x02.
2.- Es decir, envío la siguiente cadena al servidor:

C’5 || C6 = 0000000000000000000000000000001efd8703dce82b480d21dfb56717e3a1ec

3.- Posteriormente a que el proceso de fuerza bruta en en “XX” (C'5[15]) produzca un relleno válido (P’6[15] || P’6[16] = \x02 || \x02) el servicio me informa de que el valor de C'5[15] que lo produce es \x82). Entonces:

Dk(C6)[15] = \x82  \x02 = \x80
P6[15] = \x80  \x8d = \x0d

Con lo que, como no podría ser de otra manera, el valor del anteúltimo Byte del texto plano del último bloque es también \x0d, ya que, como anteriormente he dicho, de momento se trata sólo de un valor de relleno. Es decir, el texto plano tiene 13 Bytes de relleno con el valor \x0d (13 en decimal).

Y repitiendo este mismo proceso podemos obtener el resto de Bytes del texto plano del último bloque. Pongo el resultado obtenido con el script de python que he creado para ello:

P6 (hex.) = 65227d0d0d0d0d0d0d0d0d0d0d0d0d0d

Quitando el relleno:
P6 (ASCII) = e"}

Siguiendo este mismo proceso, excepto para el primer bloque del criptograma (que se corresponde con el vector de inicialización, IV, y que no se descifra), se pueden obtener el resto de bloque de texto plano. Pongo el resultado de descifrar el resto de los bloques de 16 Bytes de la 'cookie' que se nos muestra como ejemplo en el reto y el texto plano completo que se obtiene al final del proceso:

P5 (hex.) = 69735f61646d696e223a202266616c73
P5 (ASCII) = is_admin": "fals

P4 (hex.) = 2022323030302d30312d3037222c2022
P4 (ASCII) =  "2000-01-07", "

P3 (hex.) = 657374222c202265787069726573223a
P3 (ASCII) =  est", "expires":

P2 (hex.) = 7b22757365726e616d65223a20226775
P3 (ASCII) = {"username": "gu

Es decir, el texto plano que se obtiene como resultado es:

P (ASCII) = {"username": "guest", "expires": "2000-01-07", "is_admin": "false"}

Si no me equivoco, con este ataque y suponiendo que el cifrado se ha realiza utilizando AES-128, el atacante conseguirá obtener el texto plano (P) en un número de intentos no mayor que el número de bloques del criptograma - 1 (es decir, excluido el IV), multiplicado por el número de Bytes de cada bloque (16) y multiplicado por 256 (número máximo de intentos por cada Byte). En el caso de la 'cookie' que ha servido de ejemplo en este post en no más de 20.480 intentos: (6 - 1) bloques x 16 Bytes/bloque x 256 intentos como máximo/Byte; lo que mejora sustancialmente el número de intentos máximos que serían necesarios para romper mediante fuerza bruta una clave de 128 bits.

Ahora sólo queda saber cómo podemos generar una 'cookie' para engañar al servicio (fecha de caducidad mayor que la del sistema y valor "true" en "is_admin") y que éste tras descifrarla nos muestre la solución al desafío, pero, como ya he dicho, el "cifrado" utilizando el ataque 'Padding Oracle' será objeto de un post posterior.

martes, 9 de julio de 2019

Criptografía (CLIV): Solución Reto 32

Solución al último reto de criptografía que he puesto recientemente en este blog en el que pretendo poner de manifiesto la importancia de la integridad de la información, es decir, de chequear ésta con objeto de asegurar que la información es exactamente la que el remitente nos envió y que ésta no ha sido interceptada y manipulada por un tercero.

Como en los más recientes de los posts de criptografía que he incluido en este blog, en este desafío también se ve involucrado el criptosistema de cifrado simétrico más utilizado,  AES ('Advanced Encryption Standard'), en el modo de operación CBC ('Cipher Block Chaining'de los algoritmos de cifrado por bloques.

El enunciado del reto decía lo siguiente:

"Supongamos que he interceptado un criptograma de una organización criminal que sé que se ha obtenido cifrando el texto en claro con AES en modo de operación CBC y, además, que también me las he ingeniado para hacerme con el texto en claro. Los dígitos que en él figuran se corresponden con un número de cuenta bancaria en la que el miembro de la organización al que va destinado este mensaje debe ingresar una importante cantidad del dinero obtenido como consecuencia de las actividades ilícitas llevadas a cabo (ver la información de partida de la que dispongo en los recursos asociados al reto). Si pudiera modificar el criptograma para que el miembro de la organización que lo reciba vea al descifrarlo un número de cuenta mío en lugar del que se le envía entonces podría no volver a trabajar nunca más :). El problema es que desconozco la clave de cifrado/descifrado, ¿me ayudas?. La solución a este reto es el nuevo vector de inicialización y el nuevo criptograma a enviar al receptor del mensaje para que ingrese el dinero en mi cuenta".

Entre los recursos asociados al reto se encuentran los siguientes:


- Criptograma: 9eaef1bbab38643957c7e9ca82d86075
- Vector de inicialización: fe5567e8d769550852182cdf69d74bb1
- Texto en claro (Número de cta.): 4800372564718908
- Mi número de cuenta: 3600538509837891

Como en la solución al reto anterior, antes de pasar a explicar cómo se puede resolver este desafío, comparto cómo lo preparé. Para ello, con objeto de cifrar el texto en claro del reto utilicé una de las muchas herramientas 'online' existentes:
En el gráfico anterior se puede ver toda la información correspondiente al cifrado de la que disponemos para resolver este reto e, incluso, la clave de cifrado/descifrado, pero esto último no lo sabemos :).

Pruebo a descifrarlo para ver si lo he hecho correctamente:
¡Todo listo! ;)

SoluciónDicho lo anterior, me dispongo a intentar resolver el reto.

Para ello, recuerdo el funcionamiento del descifrado en modo de operación CBC ('Cipher Block Chaining') de los algoritmos de cifrado por bloques:
Decía en las pistas que puse para ayudar a resolver este reto que aunque me he permitido una cierta licencia, yo diría que incluso excesiva :), a la hora de poner el título de este reto, posiblemente no se le escape a nadie que me estoy refiriendo al 'bit-flipping attack', que, tal y como nos cuenta wikipedia, puede consistir en que el atacante convierta el mensaje original en otro similar en el que altera cierta información a su conveniencia, y también decía que en este desafío sólo tenemos control sobre los bloques del criptograma (Ci, donde C0 = IV - Vector de inicialización), por lo que la solución a este reto pasa necesariamente por modificar éstos hasta obtener el texto plano deseado.

Para ello, inicialmente, sólo hay que tener presente una cosa: la modificación de un Byte de un bloque del criptograma (Ci) supone una modificación en el mismo Byte del bloque siguiente del texto plano (Pi+1). Decir, además, que dicha modificación supone alterar también, esta vez sin control, el texto plano (Pi) que se obtiene al descifrar el bloque que se ha modificado (Cidel criptograma, pero como veremos más adelante en el reto no tengo que preocuparme de esto último:
En el reto sólo tenemos C0 = IV y C1, y, por tanto, sólo tengo que modificar C0 para obtener el texto en claro deseado en P1 (cambiar el valor de C0 sólo conlleva, única y exclusivamente, alterar P1). Pero, ¿con qué valor modifico C0 para que el miembro de la organización que reciba el mensaje vea al descifrarlo mi número de cuenta bancaria e ingrese el dinero en ella?. Considerando la siguiente figura:
Como:
 Dk(C1) = C0 XOR P1

Donde: Dk = Función de descifrado;
C1 = Bloque 1 del criptograma original;
C0 = Bloque 0 del criptograma original (IV original);
 P1 = Bloque 1 del texto plano original.

Entonces:
C'0 = C0 XOR PXOR P'1

Donde: C'0 = Valor al que hay que modificar  C0; 
C0 = Bloque 0 del criptograma original (IV original);
 P1 = Bloque 1 del texto plano original;
 P'1 = Valor deseado para el bloque 1 del texto plano.

Creo un pequeño script en python:

import binascii

criptograma_original_hex='9eaef1bbab38643957c7e9ca82d86075'
print '[+] Criptograma original  (hex)  :',criptograma_original_hex
IV_original_hex='fe5567e8d769550852182cdf69d74bb1'
print '[+] V. inicializ. original(hex)  :',IV_original_hex
texto_plano_original_ascii='4800372564718908'
print '[+] Texto plano original  (ascii):',texto_plano_original_ascii
texto_plano_original_hex=binascii.hexlify(texto_plano_original_ascii)
print '[+] Texto plano original  (hex)  :',texto_plano_original_hex
texto_plano_deseado_ascii='3600538509837891'
print '[+] Texto plano deseado   (ascii):',texto_plano_deseado_ascii
texto_plano_deseado_hex=binascii.hexlify(texto_plano_deseado_ascii)
print '[+] Texto plano deseado   (hex)  :',texto_plano_deseado_hex
IV_nuevo_hex="{:02x}".format(int(IV_original_hex,16)^int(texto_plano_original_hex,16)^int(texto_plano_deseado_hex,16))
print '[+] V. inicializ. nuevo   (hex)  :',IV_nuevo_hex
print '[+] Criptograma nuevo     (hex)  :',criptograma_original_hex

Lo ejecuto:

Por lo que la solución a este reto es:

Vector de inicialización a enviar (IV = C0) = f95b67e8d16d5f08541523dd66d642b8
Bloque 1 del criptograma a enviar (C1) = 9eaef1bbab38643957c7e9ca82d86075

Como se ve, tal y como he dicho antes, el bloque 1 del criptograma a enviar no varía respecto al original. 

Pero veamos cómo termina esta historia: tras enviar al destinatario el mensaje cifrado éste lo descifra utilizando la clave que conoce:
Y el dinero se ingresa en mi cuenta :).

******** PRÓXIMO RETO
Reto 33:   Por publicar.

domingo, 7 de julio de 2019

Criptografía (CLIII): Solución Reto 31

Solución al reto de dificultad media sobre criptografía que he puesto recientemente en este blog y en el que se ve involucrado el criptosistema de cifrado simétrico más utilizado, AES ('Advanced Encryption Standard'), en el modo de operación CBC ('Cipher Block Chaining'de los algoritmos de cifrado por bloques. Su enunciado decía lo siguiente:

"Para resolver este reto se necesita conocer cómo funciona el modo CBC de los cifradores por bloques. Demuestra que tú sí conoces dicho funcionamiento y obtén la solución de este reto con las siguientes pistas:

- Los primeros 14 de los 16 caracteres de la clave: '5rjIubT&Op_$3D'.
- Los tres últimos Bytes del cuarto bloque del criptograma: b1607e.
- El quinto bloque completo del criptograma: 925275279e9ca54278d6636884970397.
- Mensaje en claro: 'Veamos si eres capaz de encontrar la flag, que es el vector de inicializacion-IV'".

Antes de pasar a explicar la solución de este reto, comparto cómo lo preparé. Para ello, con objeto de cifrar el texto en claro del desafío utilicé una de las muchas herramientas 'online' existentes:
En el gráfico anterior se pueden ver las pistas que se dan para resolver este reto e, incluso, la solución, es decir, el vector de inicialización (IV): 336e63306e37723464305f336c5f4956 (en hexadecimal) o, lo que es lo mismo, 3nc0n7r4d0_3l_IV (en ASCII). Cualquiera de ambas respuestas sería válida como respuesta a este desafío.

Pruebo a descifrarlo para ver si lo he hecho correctamente:
¡Todo listo! ;)

Solución: Dicho lo anterior, me olvido de todo ello, me dispongo a intentar resolver el reto.

Decía en el enunciado que para resolver este desafío es necesario conocer cómo funciona el modo CBC ('Cipher Block Chaining') de los algoritmos de cifrado por bloques. El funcionamiento del descifrado en este modo de operación se muestra en la siguiente figura:
Doy los siguientes pasos:

1º) Obtención de la clave AES:

Conozco el quinto bloque completo y los tres últimos Bytes del cuarto bloque del criptograma o texto cifrado, y el mensaje en claro o texto plano completo, por lo que puedo utilizar la fuerza bruta para obtener los dos últimos caracteres de la clave y así tenerla entera:

a) Relleno con ceros los Bytes desconocidos del cuarto bloque del criptograma.

b) Realizo fuerza bruta sobre los dos últimos caracteres de la clave AES hasta que los tres últimos caracteres del quinto bloque del texto plano que obtenga sean iguales a los tres últimos caracteres del quinto bloque del texto plano conocido, es decir, "-IV".

Creo un pequeño script en python para ello:

from Crypto.Cipher import AES
import binascii

def descifrado(criptograma,clave,IV):
    aes=AES.new(clave,AES.MODE_CBC,IV)
    return aes.decrypt(criptograma)

caracteres='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '

clave_catorce_primeros_caracteres='5rjIubT&Op_$3D'
criptograma_cuarto_bloque='00000000000000000000000000b1607e'
criptograma_quinto_bloque='925275279e9ca54278d6636884970397'
texto_plano='Veamos si eres capaz de encontrar la flag, que es el vector de inicializacion-IV'

# Obtener clave AES
clave_AES_encontrada=''
for i in caracteres:
    for j in caracteres:
        clave_fuerza_bruta=clave_catorce_primeros_caracteres+i+j
        texto_plano_quinto_bloque_fuerza_bruta=descifrado(binascii.unhexlify( criptograma_quinto_bloque),clave_fuerza_bruta,binascii.unhexlify(criptograma_cuarto_bloque))
        if  str(texto_plano_quinto_bloque_fuerza_bruta).endswith(texto_plano[78:81]):
            clave_AES_encontrada=clave_fuerza_bruta
            break
    if clave_AES_encontrada!='':
       print''
       print'[+] Obtencion de la clave AES mediante descifrado por fuerza bruta del quinto bloque del criptograma:',texto_plano_quinto_bloque_fuerza_bruta,'; Clave AES:',clave_AES_encontrada
       break

if clave_AES_encontrada=='':
   print'[-] No se ha encontrado la clave AES.'

Lo ejecuto:
Como resultado obtengo que la clave de cifrado es: "5rjIubT&Op_$3Dv%", es decir. los dos caracteres que faltaban son: "v%".

2º) Obtención del  resto de bloques del criptograma (del 1 al 4) y del vector de inicialización (IV):

Ahora conozco la clave y puedo "empezar por el final":

 si Pi Dk(Ci) XOR Ci-1
 entonces:
Ci-1 Dk(Ci) XOR Pi 

Donde: C0 = IV (vector de inicialización)

Y, tras esta breve explicación, el script en python completo para obtener la solución de este reto  es:

from Crypto.Cipher import AES
import binascii

def descifrado(criptograma,clave,IV):
    aes=AES.new(clave,AES.MODE_CBC,IV)
    return aes.decrypt(criptograma)

caracteres='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '

clave_catorce_primeros_caracteres='5rjIubT&Op_$3D'
criptograma_cuarto_bloque='00000000000000000000000000b1607e'
criptograma_quinto_bloque='925275279e9ca54278d6636884970397'
texto_plano='Veamos si eres capaz de encontrar la flag, que es el vector de inicializacion-IV'

# Obtener clave AES
clave_AES_encontrada=''
for i in caracteres:
    for j in caracteres:
        clave_fuerza_bruta=clave_catorce_primeros_caracteres+i+j
        texto_plano_quinto_bloque_fuerza_bruta=descifrado(binascii.unhexlify( criptograma_quinto_bloque),clave_fuerza_bruta,binascii.unhexlify(criptograma_cuarto_bloque))
        if  str(texto_plano_quinto_bloque_fuerza_bruta).endswith(texto_plano[78:81]):
            clave_AES_encontrada=clave_fuerza_bruta
            break
    if clave_AES_encontrada!='':
       print''
       print'[+] Obtencion de la clave AES mediante descifrado por fuerza bruta del quinto bloque del criptograma:',texto_plano_quinto_bloque_fuerza_bruta,'; Clave AES:',clave_AES_encontrada
       break

# Obtener resto de bloques del criptograma
if clave_AES_encontrada!='':
   criptograma_cuarto_bloque=binascii.hexlify(descifrado(binascii.unhexlify( criptograma_quinto_bloque),clave_AES_encontrada,texto_plano[64:81]))
   print'[+] Cuarto bloque del criptograma   :',criptograma_cuarto_bloque
   criptograma_tercer_bloque=binascii.hexlify(descifrado(binascii.unhexlify( criptograma_cuarto_bloque),clave_AES_encontrada,texto_plano[48:64]))
   print'[+] Tercer bloque del criptograma   :',criptograma_tercer_bloque
   criptograma_segundo_bloque=binascii.hexlify(descifrado(binascii.unhexlify( criptograma_tercer_bloque),clave_AES_encontrada,texto_plano[32:48]))
   print'[+] Segundo bloque del criptograma  :',criptograma_segundo_bloque
   criptograma_primer_bloque=binascii.hexlify(descifrado(binascii.unhexlify( criptograma_segundo_bloque),clave_AES_encontrada,texto_plano[16:32]))
   print'[+] Primer bloque del criptograma   :',criptograma_primer_bloque

# Obtener vector de inicializacion (IV)
   vector_inicializacion=binascii.hexlify(descifrado(binascii.unhexlify( criptograma_primer_bloque),clave_AES_encontrada,texto_plano[0:16]))
   print'[+] Vector de inicializacion (hex)  :',vector_inicializacion
   print'[+] Vector de inicializacion (ascii):',vector_inicializacion.decode('hex')
else:
   print'[-] No se ha encontrado la clave AES.'

Lo ejecuto:
Por lo que la solución a este reto es: 3nc0n7r4d0_3l_IV.

******** PRÓXIMO RETO
Reto 32:   "Flipando un poco".