Closes #4717: Introduce ALLOWED_URL_SCHEMES configuration parameter to mitigate dangerous hyperlinks

This commit is contained in:
Jeremy Stretch
2020-06-15 11:53:47 -04:00
parent 2e5058c4c9
commit 5af2b3c2f5
7 changed files with 29 additions and 16 deletions

View File

@@ -647,9 +647,8 @@ class DynamicModelMultipleChoiceField(DynamicModelChoiceMixin, forms.ModelMultip
class LaxURLField(forms.URLField):
"""
Modifies Django's built-in URLField in two ways:
1) Allow any valid scheme per RFC 3986 section 3.1
2) Remove the requirement for fully-qualified domain names (e.g. http://myserver/ is valid)
Modifies Django's built-in URLField to remove the requirement for fully-qualified domain names
(e.g. http://myserver/ is valid)
"""
default_validators = [EnhancedURLValidator()]

View File

@@ -10,7 +10,6 @@ from django.utils.html import strip_tags
from django.utils.safestring import mark_safe
from markdown import markdown
from utilities.choices import unpack_grouped_choices
from utilities.utils import foreground_color
register = template.Library()
@@ -39,6 +38,11 @@ def render_markdown(value):
# Strip HTML tags
value = strip_tags(value)
# Sanitize Markdown links
schemes = '|'.join(settings.ALLOWED_URL_SCHEMES)
pattern = fr'\[(.+)\]\((?!({schemes})).*:(.+)\)'
value = re.sub(pattern, '[\\1](\\3)', value, flags=re.IGNORECASE)
# Render Markdown
html = markdown(value, extensions=['fenced_code', 'tables'])

View File

@@ -1,31 +1,24 @@
import re
from django.conf import settings
from django.core.validators import _lazy_re_compile, BaseValidator, URLValidator
class EnhancedURLValidator(URLValidator):
"""
Extends Django's built-in URLValidator to permit the use of hostnames with no domain extension.
Extends Django's built-in URLValidator to permit the use of hostnames with no domain extension and enforce allowed
schemes specified in the configuration.
"""
class AnyURLScheme(object):
"""
A fake URL list which "contains" all scheme names abiding by the syntax defined in RFC 3986 section 3.1
"""
def __contains__(self, item):
if not item or not re.match(r'^[a-z][0-9a-z+\-.]*$', item.lower()):
return False
return True
fqdn_re = URLValidator.hostname_re + URLValidator.domain_re + URLValidator.tld_re
host_res = [URLValidator.ipv4_re, URLValidator.ipv6_re, fqdn_re, URLValidator.hostname_re]
regex = _lazy_re_compile(
r'^(?:[a-z0-9\.\-\+]*)://' # Scheme (previously enforced by AnyURLScheme or schemes kwarg)
r'^(?:[a-z0-9\.\-\+]*)://' # Scheme (enforced separately)
r'(?:\S+(?::\S*)?@)?' # HTTP basic authentication
r'(?:' + '|'.join(host_res) + ')' # IPv4, IPv6, FQDN, or hostname
r'(?::\d{2,5})?' # Port number
r'(?:[/?#][^\s]*)?' # Path
r'\Z', re.IGNORECASE)
schemes = AnyURLScheme()
schemes = settings.ALLOWED_URL_SCHEMES
class ExclusionValidator(BaseValidator):