/
middleware.py
129 lines (103 loc) · 5.07 KB
/
middleware.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
''' XFF Middleware '''
import logging
from re import compile
from django.conf import settings
from django.http import HttpResponseBadRequest, HttpResponseNotFound
XFF_EXEMPT_URLS = []
if hasattr(settings, 'XFF_EXEMPT_URLS'):
XFF_EXEMPT_URLS = [compile(expr) for expr in settings.XFF_EXEMPT_URLS]
logger = logging.getLogger(__name__)
class XForwardedForMiddleware:
'''
Fix HTTP_REMOTE_ADDR header to show client IP in a proxied environment.
WARNING: If you are going to trust the address, you need to know how
many proxies you have in chain. Nothing stops a malicious client from
sending one and your reverse proxies adding one.
You can set the exact number of reverse proxies by adding the
XFF_TRUSTED_PROXY_DEPTH setting. Without it, the header will remain
insecure even with this middleware. Setting XFF_STRICT = True will
cause a bad request to be sent on spoofed or wrongly routed requests.
XFF_ALWAYS_PROXY will drop all requests with too little depth.
XFF_NO_SPOOFING will drop connections with too many headers.
This middleware will automatically clean the X-Forwarded-For header
unless XFF_CLEAN = False is set.
By default, this middleware rewrites HTTP_REMOTE_ADDR. To leave it
untouched, set XFF_REWRITE_REMOTE_ADDR = False.
XFF_LOOSE_UNSAFE = True will simply shut up and set the last in the
stack.
XFF_EXEMPT_URLS can be an iterable (eg. list) that defines URLs as
regexps that will not be checked. XFF_EXEMPT_STEALTH = True will
return a 404 when all proxies are present. This is nice for a
healtcheck URL that is not for the public eye.
XFF_HEADER_REQUIRED = True will return a bad request when the header
is not set. By default it takes the same value as XFF_ALWAYS_PROXY.
'''
def __init__(self, get_response=None):
self.get_response = get_response
self.depth = getattr(settings, 'XFF_TRUSTED_PROXY_DEPTH', 0)
self.stealth = getattr(settings, 'XFF_EXEMPT_STEALTH', False)
self.loose = getattr(settings, 'XFF_LOOSE_UNSAFE', False)
self.strict = getattr(settings, 'XFF_STRICT', False)
self.always_proxy = getattr(settings, 'XFF_ALWAYS_PROXY', False)
self.no_spoofing = getattr(settings, 'XFF_NO_SPOOFING', False)
self.header_required = getattr(settings, 'XFF_HEADER_REQUIRED',
(self.always_proxy or self.strict))
self.clean = getattr(settings, 'XFF_CLEAN', True)
self.rewrite_remote = getattr(settings, 'XFF_REWRITE_REMOTE_ADDR',
True)
def __call__(self, request):
response = self.process_request(request)
if not response:
response = self.get_response(request)
return response
def process_request(self, request):
'''
The beef.
'''
path = request.path_info.lstrip('/')
depth = self.depth
exempt = any(m.match(path) for m in XFF_EXEMPT_URLS)
if 'HTTP_X_FORWARDED_FOR' in request.META:
header = request.META['HTTP_X_FORWARDED_FOR']
levels = [x.strip() for x in header.split(',')]
if len(levels) >= depth and exempt and self.stealth:
return HttpResponseNotFound()
if self.loose or exempt:
if self.rewrite_remote:
request.META['REMOTE_ADDR'] = levels[0]
return None
if len(levels) != depth and self.strict:
logger.warning((
"Incorrect proxy depth in incoming request.\n" +
'Expected {} and got {} remote addresses in ' +
'X-Forwarded-For header.')
.format(
depth, len(levels)))
return HttpResponseBadRequest()
if len(levels) < depth or depth == 0:
logger.warning(
'Not running behind as many reverse proxies as expected.' +
"\nThe right value for XFF_TRUSTED_PROXY_DEPTH for this " +
'request is {} and {} is configured.'.format(
len(levels), depth)
)
if self.always_proxy:
return HttpResponseBadRequest()
depth = len(levels)
elif len(levels) > depth:
logger.info(
('X-Forwarded-For spoof attempt with {} addresses when ' +
'{} expected. Full header: {}').format(
len(levels), depth, header))
if self.no_spoofing:
return HttpResponseBadRequest()
if self.rewrite_remote:
request.META['REMOTE_ADDR'] = levels[-1 * depth]
if self.clean:
cleaned = ','.join(levels[-1 * depth:])
request.META['HTTP_X_FORWARDED_FOR'] = cleaned
elif self.header_required and not (exempt or self.loose):
logger.error(
'No X-Forwarded-For header set, not behind a reverse proxy.')
return HttpResponseBadRequest()
return None