mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 21:48:02 +00:00
fix: validation for trusted urls
This commit is contained in:
81
config.py
81
config.py
@@ -211,21 +211,72 @@ CACHE_RESPONSE = True
|
||||
|
||||
# Trusted URLs for default redirection
|
||||
TRUSTED_DEFAULT_URLS = [
|
||||
"https://ui-avatars.com/api/",
|
||||
"http://gravatar.com/avatar/",
|
||||
"https://gravatar.com/avatar/",
|
||||
"http://www.gravatar.org/avatar/",
|
||||
"https://www.gravatar.org/avatar/",
|
||||
"https://secure.gravatar.com/avatar/",
|
||||
"http://0.gravatar.com/avatar/",
|
||||
"https://0.gravatar.com/avatar/",
|
||||
"http://www.gravatar.com/avatar/",
|
||||
"https://www.gravatar.com/avatar/",
|
||||
"https://avatars.dicebear.com/api/",
|
||||
"https://badges.fedoraproject.org/static/img/",
|
||||
"http://www.planet-libre.org/themes/planetlibre/images/",
|
||||
"https://www.azuracast.com/img/",
|
||||
"https://reps.mozilla.org/static/base/img/remo/",
|
||||
{
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"host_equals": "ui-avatars.com",
|
||||
"path_prefix": "/api/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_equals": "gravatar.com",
|
||||
"path_prefix": "/avatar/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_suffix": ".gravatar.com",
|
||||
"path_prefix": "/avatar/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_equals": "www.gravatar.org",
|
||||
"path_prefix": "/avatar/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"host_equals": "avatars.dicebear.com",
|
||||
"path_prefix": "/api/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"host_equals": "badges.fedoraproject.org",
|
||||
"path_prefix": "/static/img/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
],
|
||||
"host_equals": "www.planet-libre.org",
|
||||
"path_prefix": "/themes/planetlibre/images/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"host_equals": "www.azuracast.com",
|
||||
"path_prefix": "/img/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"host_equals": "reps.mozilla.org",
|
||||
"path_prefix": "/static/base/img/remo/"
|
||||
}
|
||||
]
|
||||
|
||||
# This MUST BE THE LAST!
|
||||
|
||||
@@ -5,7 +5,7 @@ Test our utils from ivatar.utils
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ivatar.utils import openid_variations
|
||||
from ivatar.utils import is_trusted_url, openid_variations
|
||||
|
||||
|
||||
class Tester(TestCase):
|
||||
@@ -45,3 +45,60 @@ class Tester(TestCase):
|
||||
self.assertEqual(openid_variations(openid3)[1], openid1)
|
||||
self.assertEqual(openid_variations(openid3)[2], openid2)
|
||||
self.assertEqual(openid_variations(openid3)[3], openid3)
|
||||
|
||||
def test_is_trusted_url(self):
|
||||
test1 = is_trusted_url("https://gravatar.com/avatar/63a75a80e6b1f4adfdb04c1ca02e596c", [
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_equals": "gravatar.com",
|
||||
"path_prefix": "/avatar/"
|
||||
}
|
||||
])
|
||||
self.assertTrue(test1)
|
||||
|
||||
test2 = is_trusted_url("https://gravatar.com.example.org/avatar/63a75a80e6b1f4adfdb04c1ca02e596c", [
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_suffix": ".gravatar.com",
|
||||
"path_prefix": "/avatar/"
|
||||
}
|
||||
])
|
||||
self.assertFalse(test2)
|
||||
|
||||
# Test against open redirect with valid URL in query params
|
||||
test3 = is_trusted_url("https://github.com/SethFalco/?boop=https://secure.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50", [
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_suffix": ".gravatar.com",
|
||||
"path_prefix": "/avatar/"
|
||||
}
|
||||
])
|
||||
self.assertFalse(test3)
|
||||
|
||||
test4 = is_trusted_url("https://ui-avatars.com/api/blah", [
|
||||
{
|
||||
"schemes": [
|
||||
"https"
|
||||
],
|
||||
"host_equals": "ui-avatars.com",
|
||||
"path_prefix": "/api/"
|
||||
},
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"host_suffix": ".gravatar.com",
|
||||
"path_prefix": "/avatar/"
|
||||
}
|
||||
])
|
||||
self.assertTrue(test4)
|
||||
|
||||
@@ -5,6 +5,7 @@ Simple module providing reusable random_string function
|
||||
import random
|
||||
import string
|
||||
from PIL import Image, ImageDraw
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
def random_string(length=10):
|
||||
@@ -112,3 +113,42 @@ def mm_ng(
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def is_trusted_url(url, url_filters):
|
||||
"""
|
||||
Check if a URL is valid and considered a trusted URL.
|
||||
If the URL is malformed, returns False.
|
||||
|
||||
Based on: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/events/UrlFilter
|
||||
"""
|
||||
(scheme, netloc, path, params, query, fragment) = urlparse(url)
|
||||
|
||||
for filter in url_filters:
|
||||
if "schemes" in filter:
|
||||
schemes = filter["schemes"]
|
||||
|
||||
if scheme not in schemes:
|
||||
continue
|
||||
|
||||
if "host_equals" in filter:
|
||||
host_equals = filter["host_equals"]
|
||||
|
||||
if netloc != host_equals:
|
||||
continue
|
||||
|
||||
if "host_suffix" in filter:
|
||||
host_suffix = filter["host_suffix"]
|
||||
|
||||
if not netloc.endswith(host_suffix):
|
||||
continue
|
||||
|
||||
if "path_prefix" in filter:
|
||||
path_prefix = filter["path_prefix"]
|
||||
|
||||
if not path.startswith(path_prefix):
|
||||
continue
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@@ -34,7 +34,7 @@ from .ivataraccount.models import ConfirmedEmail, ConfirmedOpenId
|
||||
from .ivataraccount.models import UnconfirmedEmail, UnconfirmedOpenId
|
||||
from .ivataraccount.models import Photo
|
||||
from .ivataraccount.models import pil_format, file_format
|
||||
from .utils import mm_ng
|
||||
from .utils import is_trusted_url, mm_ng
|
||||
|
||||
URL_TIMEOUT = 5 # in seconds
|
||||
|
||||
@@ -146,7 +146,9 @@ class AvatarImageView(TemplateView):
|
||||
# Check for :// (schema)
|
||||
if default is not None and default.find("://") > 0:
|
||||
# Check if it's trusted, if not, reset to None
|
||||
if not any(x in default for x in TRUSTED_DEFAULT_URLS):
|
||||
trusted_url = is_trusted_url(default, TRUSTED_DEFAULT_URLS)
|
||||
|
||||
if not trusted_url:
|
||||
print(
|
||||
"Default URL is not in trusted URLs: '%s' ; Kicking it!" % default
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user