How do I safely get the user's real IP address in Flask (using mod_wsgi)?

前端 未结 2 1780
星月不相逢
星月不相逢 2020-12-08 06:19

I have a flask app setup on mod_wsgi/Apache and need to log the IP Address of the user. request.remote_addr returns \"127.0.0.1\" and this fix attempts to correct that but I

相关标签:
2条回答
  • 2020-12-08 06:30

    You can't safely do that, because the you can't trust all the parts in a http (or tcp) connection

    0 讨论(0)
  • 2020-12-08 06:55

    You can use the request.access_route attribute only if you define a list of trusted proxies.

    The access_route attribute uses the X-Forwarded-For header, falling back to the REMOTE_ADDR WSGI variable; the latter is fine as your server determines this; the X-Forwarded-For could have been set by just about anyone, but if you trust a proxy to set the value correctly, then use the first one (from the end) that is not trusted:

    trusted_proxies = {'127.0.0.1'}  # define your own set
    route = request.access_route + [request.remote_addr]
    
    remote_addr = next((addr for addr in reversed(route) 
                        if addr not in trusted_proxies), request.remote_addr)
    

    That way, even if someone spoofs the X-Forwarded-For header with fake_ip1,fake_ip2, the proxy server will add ,spoof_machine_ip to the end, and the above code will set the remote_addr to spoof_machine_ip, no matter how many trusted proxies there are in addition to your outermost proxy.

    This is the whitelist approach your linked article talks about (briefly, in that Rails uses it), and what Zope implemented over 11 years ago.

    Your ProxyFix approach works just fine, but you misunderstood what it does. It only sets request.remote_addr; the request.access_route attribute is unchanged (the X-Forwarded-For header is not adjusted by the middleware). However, I'd be very wary of blindly counting off proxies.

    Applying the same whitelist approach to the middleware would look like:

    class WhitelistRemoteAddrFix(object):
        """This middleware can be applied to add HTTP proxy support to an
        application that was not designed with HTTP proxies in mind.  It
        only sets `REMOTE_ADDR` from `X-Forwarded` headers.
    
        Tests proxies against a set of trusted proxies.
    
        The original value of `REMOTE_ADDR` is stored in the WSGI environment
        as `werkzeug.whitelist_remoteaddr_fix.orig_remote_addr`.
    
        :param app: the WSGI application
        :param trusted_proxies: a set or sequence of proxy ip addresses that can be trusted.
        """
    
        def __init__(self, app, trusted_proxies=()):
            self.app = app
            self.trusted_proxies = frozenset(trusted_proxies)
    
        def get_remote_addr(self, remote_addr, forwarded_for):
            """Selects the new remote addr from the given list of ips in
            X-Forwarded-For.  Picks first non-trusted ip address.
            """
    
            if remote_addr in self.trusted_proxies:
                return next((ip for ip in reversed(forwarded_for)
                             if ip not in self.trusted_proxies),
                            remote_addr)
    
        def __call__(self, environ, start_response):
            getter = environ.get
            remote_addr = getter('REMOTE_ADDR')
            forwarded_for = getter('HTTP_X_FORWARDED_FOR', '').split(',')
            environ.update({
                'werkzeug.whitelist_remoteaddr_fix.orig_remote_addr': remote_addr,
            })
            forwarded_for = [x for x in [x.strip() for x in forwarded_for] if x]
            remote_addr = self.get_remote_addr(remote_addr, forwarded_for)
            if remote_addr is not None:
                environ['REMOTE_ADDR'] = remote_addr
            return self.app(environ, start_response)
    

    To be explicit: this middleware too, only sets request.remote_addr; request.access_route remains unaffected.

    0 讨论(0)
提交回复
热议问题