Sending Ruby to the jail: an attemp on a Haml Safemode
Find the code here: http://svn.artweb-design.de/stuff/rails/haml_safemode.
So how does this work?
Basically I've switched to an approach where the "precompiled" Ruby code from a Haml template gets parsed (using ParseTree and Ruby2Ruby) and all method calls get "jailed" with only whitelisted methods allowed (think: Rails blankslate plus methods access control).
For example the code:
in a Haml template would be converted to this:
to_jail wraps the returned objects into Jail instances which are proxy objects with almost all methods removed. These proxies allow access only to whitelisted methods and raise an exception if someone tries to access something else. To instantiate this object it looks for a class Jail within the object's class' namespace (e.g.
So, to actually allow to do something useful in Haml templates these Jail subclasses have to be implemented for an application's models. (I.e. this is basically Liquid's concept of so called drops.) For most of Ruby's native classes like
Hash etc. the plugin will do that for us (see this file for a list of methods that will be allowed for certain native Ruby classes).
Also, the resulting Ruby code will be evaluated with a
ScopeObject's binding which shares the same blankslate-like base class with the jails. This object forbids access to almost everything but additionally passes calls to Rails helper methods back to the
ActionView instance. So we can use helpers, too.
Compared to Liquid jails play the role of drops (I chose a different name to avoid any name clashes). Liquid's filters actually become obsolete as they essentially play the role of calling methods on objects (e.g. formatting a date) which we can do in native Ruby here.
Because Haml uses Rails' "compiled templates" architecture all the ParseTree parsing and Ruby2Ruby re-assembling only takes place once when the template is "compiled". The result will be cached in memory and reused for subsequent requests.
Thus I wouldn't be surprised when this Haml Safemode actually proves faster compared to Liquid which (unless I'm totally mistaken) does it's RegExp work each and every time when the template gets rendered.
So, this approach should be rather restrictive than permissive. All attack vectors I could think of pass the tests (i.e. they raise an exception). But of course I'm not sure whether it's already waterproof.
I am by no means a security expert and definitely lack some "black creativity". So I need your help with this!
Like I said, this code is highly experimental. Do not rely on it or use it for any serious purpose beyond playing with it, yet!
That said, this code is experimental :)
Please do experiment with it!
Check it out, hammer it with all your evil creativity and try to find any working attack vectors. I totally lack this kind of skill, so I'm sure there's something to improve.
Many thanks to Peter Cooper who was so kind to already (briefly) review the code to catch any obvious bugs and check out the test cases.