From 44ecbee250a54597518766c3900522508cf7fc1f Mon Sep 17 00:00:00 2001 From: Jona Andersen Date: Sun, 1 May 2022 11:53:41 +0200 Subject: [PATCH] Strip port number from IP address in X-Forwarded-For --- defender/tests.py | 24 ++++++++++++++++++++++++ defender/utils.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/defender/tests.py b/defender/tests.py index dcf556f..20cf1fa 100644 --- a/defender/tests.py +++ b/defender/tests.py @@ -1075,3 +1075,27 @@ class TestUtils(DefenderTestCase): utils.add_login_attempt_to_db(request, True, username=username) self.assertEqual(AccessAttempt.objects.filter(username=username).count(), 1) + + def test_ip_address_strip_port_number(self): + """ Test the strip_port_number() method """ + # IPv4 with/without port + self.assertEqual(utils.strip_port_number("192.168.1.1"), "192.168.1.1") + self.assertEqual(utils.strip_port_number( + "192.168.1.1:8000"), "192.168.1.1") + + # IPv6 with/without port + self.assertEqual(utils.strip_port_number( + "2001:db8:85a3:0:0:8a2e:370:7334"), "2001:db8:85a3:0:0:8a2e:370:7334") + self.assertEqual(utils.strip_port_number( + "[2001:db8:85a3:0:0:8a2e:370:7334]:123456"), "2001:db8:85a3:0:0:8a2e:370:7334") + + @patch("defender.config.BEHIND_REVERSE_PROXY", True) + def test_get_ip_strips_port_number(self): + """ make sure the IP address is stripped of its port number """ + req = HttpRequest() + req.META["HTTP_X_FORWARDED_FOR"] = "1.2.3.4:123456" + self.assertEqual(utils.get_ip(req), "1.2.3.4") + + req = HttpRequest() + req.META["HTTP_X_FORWARDED_FOR"] = "[2001:db8::1]:123456" + self.assertEqual(utils.get_ip(req), "2001:db8::1") diff --git a/defender/utils.py b/defender/utils.py index e390584..658377c 100644 --- a/defender/utils.py +++ b/defender/utils.py @@ -1,4 +1,5 @@ import logging +import re from django.http import HttpResponse from django.http import HttpResponseRedirect @@ -43,15 +44,51 @@ def get_ip_address_from_request(request): return "127.0.0.1" +ipv4_with_port = re.compile(r"^(\d+\.\d+\.\d+\.\d+):\d+") +ipv6_with_port = re.compile(r"^\[([^\]]+)\]:\d+") + + +def strip_port_number(ip_address_string): + """ strips port number from IPv4 or IPv6 address """ + ip_address = None + + if ipv4_with_port.match(ip_address_string): + match = ipv4_with_port.match(ip_address_string) + ip_address = match[1] + elif ipv6_with_port.match(ip_address_string): + match = ipv6_with_port.match(ip_address_string) + ip_address = match[1] + + """ + If it's not a valid IP address, we prefer to return + the string as-is instead of returning a potentially + corrupted string: + """ + if is_valid_ip(ip_address): + return ip_address + + return ip_address_string + + def get_ip(request): """ get the ip address from the request """ if config.BEHIND_REVERSE_PROXY: ip_address = request.META.get(config.REVERSE_PROXY_HEADER, "") ip_address = ip_address.split(",", 1)[0].strip() + if ip_address == "": ip_address = get_ip_address_from_request(request) + else: + """ + Some reverse proxies will include a port number with the + IP address; as this port may change from request to request, + and thus make it appear to be different IP addresses, we'll + want to remove the port number, if present: + """ + ip_address = strip_port_number(ip_address) else: ip_address = get_ip_address_from_request(request) + return ip_address