web.py:如何选择性地用404隐藏任何HTTP方法的资源?

5 投票
3 回答
817 浏览
提问于 2025-04-16 02:09

我想在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']
            ...

撰写回答