2

I know that untrusted data retrieved from a user should not be serialised/deserialised by an application because it can lead to a remote code execution.

What if that input is encoded upon retrieval from the user and is only being decoded after it is being deserialised? Would it still be vulnerable?

In this case I'm talking about base64 encoding.

1

If you take untrusted data, re-encode it another format (doesn't matter which), it's still untrusted data. So yes, there's still a possibility of a vulnerability being exploited.

1

The encoding of the content is irrelevant; an attacker can simply use the same encoding. In fact, base64 is a common encoding format for binary serialised data.

Here's an example of some vulnerable code that uses base64:

class SearchQuery(object):
    def __init__(self, term, archived, page):
        self.searchTerm = term
        self.includeArchived = archived
        self.currentPage = page

@app.route('/search', methods=['POST'])
def index():
    searchData = request.form['data']
    searchQuery = cPickle.loads(base64.decode(searchData))
    searchResults = db.search(searchQuery.term, searchQuery.includeArchived)
    return formatResults(searchQuery, searchResults[searchQuery.page])

This is an example of how you could implement a paginated search page using serialisation to store the search state, i.e. the search term, whether or not archived results are included, and the current page of the search results that are being displayed.

The /search URL takes a POST request which takes a single parameter, "data". The normal search code would serialise a SearchQuery object and encode that serialised data with base64. When the user submits the form (e.g. via clicking the next page of search results) the data is sent to the /search URL, which decodes and deserialises it, and shows the next set of search results.

However, we can abuse this:

class EvilSearchQuery(object):
    def __reduce__(self):
        return (self.__class__, (subprocess.check_output(['id', '']), ))
    def __init__(self, term, archived, page):
        self.searchTerm = term
        self.includeArchived = archived
        self.currentPage = page
evilQuery = EvilSearchQuery("pwned", False, 1)
print(base64.encode(cPickle.dumps(evilQuery))

This script defines a new class, called EvilSearchQuery, which contains malicious code. Specifically, it overrides the __reduce__ function, which is used to provide custom deserialisation logic to Pickle. It returns a tuple where the first value is the class name (self.__class__ here) and the second value is a callable object (in this case a function) that should be called in order to perform the deserialisation. Instead of putting real deserialisation code here, we instead put in a call to subprocess.check_output, which runs an arbitrary command on the system. So instead of actually deserialising the object, it just runs id and puts the result in every field.

All we need to do to use this is take the base64 string it prints out and send it in a POST request to /search. The code is executed and, when the page returns, it'll show the results of the command in its contents.

There are two main ways around this:

  1. Don't use serialisation/deserialisation across trust boundaries. This means you can't deserialise anything that comes from the user's browser, and that includes GET/POST arguments, cookies, headers, file uploads, etc.
  2. Cryptographically sign serialised data and verify the signature before deserialising. In this case you could use PyCrypto or a similar library to perform the signature generation and verification. You must ensure that all signatures are verified - if you miss even one case, you'll be vulnerable to RCE. You must also ensure that error cases (e.g. invalid signatures) throw an exception so that invalid data cannot be accidentally used. Furthermore, you cannot wrap the signed data in a class and serialise that, otherwise the outer class will be vulnerable too. It's also important to note that signing is not the same as encryption! Signing data does not hide its contents, and encrypting data does not necessarily prevent the data from being modified (e.g. with CBC malleability attacks).

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.