Web.py: How to selectively hide resources from 404s for any HTTP method?

I want to selectively hide some resources based on some form of authentication in web.py, but their existence was revealed by 405 responses to any HTTP method that I did not implement.

Here is an example:

import web urls = ( '/secret', 'secret', ) app = web.application(urls, globals()) class secret(): def GET(self): if web.cookies().get('password') == 'secretpassword': return "Dastardly secret plans..." raise web.notfound() if __name__ == "__main__": app.run() 

When an undefined method request is issued, the resource is detected:

 $ curl -v -X DELETE http://localhost:8080/secret ... > DELETE /secret HTTP/1.1 ... < HTTP/1.1 405 Method Not Allowed < Content-Type: text/html < Allow: GET ... 

I could do the same check for other common methods in the HTTP specification, but a creative attacker could come up with their own:

 $ curl -v -X SHENANIGANS http://localhost:8080/secret ... > SHENANIGANS /secret HTTP/1.1 ... < HTTP/1.1 405 Method Not Allowed < Content-Type: text/html < Allow: GET ... 

Is there a way to implement the catch all method in the web.py class for any HTTP method, so I can guarantee that a security check will be run?

Or is there an alternative way to hide these resources?

+6
python web.py
source share
3 answers

Enlightened by Daniel Klyuyev's answer, I ended up with web.application to add support for the default method in the _delegate method:

 import types class application(web.application): def _delegate(self, f, fvars, args=[]): def handle_class(cls): meth = web.ctx.method if meth == 'HEAD' and not hasattr(cls, meth): meth = 'GET' if not hasattr(cls, meth): if hasattr(cls, '_default'): tocall = getattr(cls(), '_default') return tocall(*args) raise web.nomethod(cls) tocall = getattr(cls(), meth) return tocall(*args) def is_class(o): return isinstance(o, (types.ClassType, type)) ... 

Instantiation:

 app = application(urls, globals()) 

Page Class:

 class secret(): def _default(self): raise web.notfound() def GET(self): ... 

I prefer this solution because it keeps the page classes clean and further configures the delegation process in one place. For example, another function I wanted was transparent overloaded POST (for example, redirecting a POST request using method=DELETE to the DELETE method of the page class), and this is just to add this too:

  ... meth = web.ctx.method if meth == 'POST' and 'method' in web.input(): meth = web.input()['method'] ... 
+4
source

You can implement the handle-all-methods method as follows:

 class HelloType(type): """Metaclass is needed to fool hasattr(cls, method) check""" def __getattribute__(obj, name): try: return object.__getattribute__(obj, name) except AttributeError: return object.__getattribute__(obj, '_handle_unknown') class hello(object): __metaclass__ = HelloType def GET(self, *args, **kw): if web.cookies().get('password') == 'secretpassword': return "Dastardly secret plans..." raise web.notfound() def _handle_unknown(self, *args, **kw): """This method will be called for all requests, which have no defined method""" raise web.notfound() def __getattribute__(obj, name): try: return object.__getattribute__(obj, name) except AttributeError: return object.__getattribute__(obj, '_handle_unknown') 

__getattribute__ is executed twice due to the way web.py checks for the existence of the method:

 def _delegate(self, f, fvars, args=[]): def handle_class(cls): meth = web.ctx.method if meth == 'HEAD' and not hasattr(cls, meth): meth = 'GET' if not hasattr(cls, meth): # Calls type __getattribute__ raise web.nomethod(cls) tocall = getattr(cls(), meth) # Calls instance __getattribute__ 
+1
source

you can define any method in your "secret" class, for example DELETE or SHENANIGANS, for example:

 class secret(): def DELETE(self): ... def SHENANIGANS(self): ... 
0
source

All Articles