Securing Capability Web Services
I recently found myself implementing some capability web services, and got to thinking about how there is little online about how to do so securely. It is one thing to stay within an existing capability system such as waterken or E, and another to be implementing capabilities on top of a rest framework or websockets. Here I'll describe the technique that I like to use, and why.
What is a Capability Web Service?
The web is rife with authorisation schemes, from basic authentication through to OpenID. Capability Web Services are built around unguessable URLs. If the secret URL has been shared with you, perhaps sent to you via email, stashed in your bookmarks, or stored in a dashboard, then you have the authority to access the resource at that address. Capability URLs are often used for password resets, but they can be used for general authorisation and authentication, too; they can be much more secure than passwords when done right.
Making all web services Capability Web Services is not yet common practice, but capabilities themselves are a well-researched field of security with a wide range of usability benefits. Lets see how we can create one of these services in a popular framework.
Building a capability service requires that we identify the resource in the URL string and also authenticate it. We might reason that we can use the same large random number both to designate the resource and to authenticate it. Here is a statically named resource built in that fashion:
@app.route("/widgets/nLinRVtJOKB0ow2yqOXLiD1fSTk1twHXAC7XoGBVOHQ=") def my_resource(): "bad example 0" return "static secret details"
This resource has a 256 bit random number identifying it - too long to be guessable. Nevertheless, a simple timing attack on this service will discover the number easily. What gives?
Flask uses a regular expression to match the query string against the route - and regular expressions do not run in constant time. The service will take a little longer to 404 when more of the path matches. Lets look at a dynamic resource.
@app.route("/widgets/<uuid:token>") def my_resource(token): "bad example 2" for widget_id, secret in db.execute( select([widget]).where(widget.c.id == token)): return secret return make_response("No such widget", 404)
In this example, the uuid that identifies the resource is also the identifier in the database. I imagine that most people, when first tasked with developing a Capability Web Service, do something like the above. Unfortunately, it too is subject to a timing attack. This time the attack takes a little longer, however it can still determine which resources are stored in the database and then access them.
Lets see if we can build a page that is robust against timing attacks. This one will require that the part of the URL that must be kept secret is not used as the identifier for lookup. Here's how I do it in my web pages:
def is_equal(s, t): """Constant-time string equal function """ if len(s) == len(t): result = 1 else: result = 0 t = s for x, y in zip(s, t): result &= int(x == y) return result @app.route("/widgets/<string:token>") def my_resource(token): "correct example" # parse the token into uniform bytes try: raw_token = b64decode(token) except TypeError: return make_response("Invalid resource", 404) # grab the id and auth out of the token xid = raw_token[-8:] xauth = raw_token[:-8] # find the resource in the database for widget_id, digest, salt, secret in db.execute( select([widget]).where(widget.c.id == xid)): # validate the provided auth against the resource itself xdigest = sha(xauth + salt).hexdigest() if is_equal(digest, xdigest): return secret else: break return make_response("Invalid resource", 404)
Generating such a token requires concatenating N random bytes of auth with 8 bytes of id. I recommend using 32 bytes of auth, though you could get away with less. Make sure to save the hexdigest to the database when you create the resource.
While an attacker might be able to guess your 8-byte ids, they will not reasonably be able to guess an entire 40-byte token.
We saw how to build a Capability Web Service that is robust against timing attacks. The process should be the same when using something like CapTP or WAMP over websockets: even though the identifier and the auth are combined into one token, lets not use the same data both for lookup and for performing the authorisation.
CommentsComments powered by Disqus