Monday, December 21, 2009

hashing urls for authorisation, and pywebsite.signed_url

Using a hash with a salt is one way of validating urls. With it you can tell that it is quite likely the url was generated by someone with a secret. That is, generated by someone authorised to make that url. This post describes using this technique with the hmac python module applied to urls(and object method calls).

Generating thumbnail urls is a good example. If you have a url which can generate a thumbnail of an image at a specified size that is pretty cool. Except then you can have someone generating images at all sorts of other weird sizes by exploiting the width, and height parameters (eg making 100,000 pixel wide thumbnails). Or perhaps you want to limit it to a certain set of images.

You could always code in your thumb nail generating routine which variables are valid... but this has problems. First it makes your routine less flexible. Separating authorisation is always a nice idea.

Another way (with HMAC like using pywebsite.signed_url) is to generate the urls and add a hash to them with a secret salt. This way you can be fairly sure that it was generated by someone with access to the secret salt.

This system is not as secure as using PKI, but it is a lot quicker to implement and is faster running.

One problem with using hashes is when you have to change the salt or hash scheme... then all your old urls are invalidated. Very annoying (sad panda).

Below is a little example of how you would hash protect some object methods, so only methods which are generated by authorised people are allowed. Note that it only uses a hash with a length of 6 characters, this is to keep urls short.

from pywebsite import signed_url, imageops

class SignedImages(object):
salt = 'somesecret'
root_dir = '/tmp/'

def thumb(self, hash, width, height, rotate, gallery, image):
""" serves a thumb nail
http://localhost:9942/thumb/XXXahashXXX/1000/1000/0/test/colorpicker.jpg
"""
salt = self.salt
keys = None
length_used = 6
values = [width, height, rotate, gallery, image]
if not signed_url.verify(values, salt, hash, keys, length_used = length_used):
raise ValueError('not a valid url')
cache_dir = os.path.join(self.root_dir, "cache/")
path_to_galleries = os.path.join(self.root_dir, "galleries/")
iops = imageops.ImageOps(cache_dir, path_to_galleries)
path = iops.resize(gallery, image, width, height, rotate)
return cherrypy.lib.static.serve_file(path)


def gen_thumb_url(self, width, height, gallery, image):
values = [ width, height, '0', gallery, image]
salt = self.salt
keys = None
length_used = 6
hash = signed_url.sign(values, salt, keys = keys, length_used = length_used)
url = "/".join(["thumb", hash] + values)
return url

thumb.exposed = True
gen_thumb_url.exposed = True


See the signed_url.py on launchpad file browser, or bzr co lp:pywebsite. Also see pywebsite.signed_url.signed_url_test for the unit tests. It is quite a simple module, and you could roll your own quite easily (with the python hmac module). Well, maybe not well easily... eg, consider the flickr exploits This code is also probably vulnerable( eg, SHA-1 is now vulnerable to collisions), but at least it will stop basic fiddling with unauthorised urls and shouldn't have the same vulnerabilities as the flickr API used to(and the facebook API might still have).


updates: been through a number of changes... the name has changed from hash_url to signed_url.sign, signed_url.verify. It has also moved into its own sub-package (like all pywebsite modules now live in their own self contained sub-package). Fixes for timing attack(unit tested). Uses hmac.

No comments: