Skip to content

Commit

Permalink
Closes #4717: Introduce ALLOWED_URL_SCHEMES configuration parameter t…
Browse files Browse the repository at this point in the history
…o mitigate dangerous hyperlinks
  • Loading branch information
jeremystretch committed Jun 15, 2020
1 parent 2e5058c commit 5af2b3c
Show file tree
Hide file tree
Showing 7 changed files with 29 additions and 16 deletions.
8 changes: 8 additions & 0 deletions docs/configuration/optional-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ ADMINS = [

---

## ALLOWED_URL_SCHEMES

Default: `('file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp')`

A list of permitted URL schemes referenced when rendering links within NetBox. Note that only the schemes specified in this list will be accepted: If adding your own, be sure to replicate the entire default list as well (excluding those schemes which are not desirable).

---

## BANNER_TOP

## BANNER_BOTTOM
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/version-2.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Enhancements

* [#4698](https://github.com/netbox-community/netbox/issues/4698) - Improve display of template code for object in admin UI
* [#4717](https://github.com/netbox-community/netbox/issues/4717) - Introduce `ALLOWED_URL_SCHEMES` configuration parameter to mitigate dangerous hyperlinks
* [#4755](https://github.com/netbox-community/netbox/issues/4755) - Enable creation of rack reservations directly from navigation menu

### Bug Fixes
Expand Down
5 changes: 5 additions & 0 deletions netbox/netbox/configuration.example.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
# ['John Doe', 'jdoe@example.com'],
]

# URL schemes that are allowed within links in NetBox
ALLOWED_URL_SCHEMES = (
'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
)

# Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same
# content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP.
BANNER_TOP = ''
Expand Down
3 changes: 3 additions & 0 deletions netbox/netbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@

# Set optional parameters
ADMINS = getattr(configuration, 'ADMINS', [])
ALLOWED_URL_SCHEMES = getattr(configuration, 'ALLOWED_URL_SCHEMES', (
'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp',
))
BANNER_BOTTOM = getattr(configuration, 'BANNER_BOTTOM', '')
BANNER_LOGIN = getattr(configuration, 'BANNER_LOGIN', '')
BANNER_TOP = getattr(configuration, 'BANNER_TOP', '')
Expand Down
5 changes: 2 additions & 3 deletions netbox/utilities/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,9 +647,8 @@ def _get_initial_value(self, initial_data, field_name):

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()]

Expand Down
6 changes: 5 additions & 1 deletion netbox/utilities/templatetags/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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()
Expand Down Expand Up @@ -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'])

Expand Down
17 changes: 5 additions & 12 deletions netbox/utilities/validators.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down

0 comments on commit 5af2b3c

Please sign in to comment.