web.py:如何选择性地用404隐藏任何HTTP方法的资源?
我想在web.py中根据某种认证方式选择性地隐藏一些资源,但如果我没有实现某个HTTP方法,就会返回405响应,这样就暴露了这些资源的存在。
这里有个例子:
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()
当发出一个未定义的方法请求时,资源就会被暴露出来:
$ 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
...
我可以对HTTP规范中的其他常见方法进行相同的检查,但有些人可能会创造出自己的方法:
$ 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
...
有没有办法在web.py类中实现一个“捕获所有”方法,以处理任何HTTP方法,这样我就可以确保安全检查会被执行?
或者有没有其他方法可以隐藏这些资源?
3 个回答
0
你可以在你的“秘密”类里面定义任何方法,比如 DELETE 或 SHENANIGANS,像这样:
class secret():
def DELETE(self):
...
def SHENANIGANS(self):
...
1
你可以这样实现一个处理所有方法的功能:
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__
这个方法被实现了两次,这是因为 web.py 检查方法是否存在的方式:
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's __getattribute__
raise web.nomethod(cls)
tocall = getattr(cls(), meth) # Calls instance's __getattribute__
3
受到Daniel Kluev回答的启发,我决定从web.application
这个地方出发,增加一个默认方法到_delegate
方法里:
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))
...
实例化:
app = application(urls, globals())
页面类:
class secret():
def _default(self):
raise web.notfound()
def GET(self):
...
我更喜欢这个解决方案,因为它让页面类保持整洁,并且可以在一个地方进一步定制委托过程。比如,我还想要一个功能,就是可以透明地处理重载的POST请求(比如,把一个带有method=DELETE
的POST请求重定向到页面类的DELETE方法),在这里添加这个功能也很简单:
...
meth = web.ctx.method
if meth == 'POST' and 'method' in web.input():
meth = web.input()['method']
...