mirror of
https://git.linux-kernel.at/oliver/ivatar.git
synced 2025-11-17 05:28:03 +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 URLs for default redirection
|
||||||
TRUSTED_DEFAULT_URLS = [
|
TRUSTED_DEFAULT_URLS = [
|
||||||
"https://ui-avatars.com/api/",
|
{
|
||||||
"http://gravatar.com/avatar/",
|
"schemes": [
|
||||||
"https://gravatar.com/avatar/",
|
"https"
|
||||||
"http://www.gravatar.org/avatar/",
|
],
|
||||||
"https://www.gravatar.org/avatar/",
|
"host_equals": "ui-avatars.com",
|
||||||
"https://secure.gravatar.com/avatar/",
|
"path_prefix": "/api/"
|
||||||
"http://0.gravatar.com/avatar/",
|
},
|
||||||
"https://0.gravatar.com/avatar/",
|
{
|
||||||
"http://www.gravatar.com/avatar/",
|
"schemes": [
|
||||||
"https://www.gravatar.com/avatar/",
|
"http",
|
||||||
"https://avatars.dicebear.com/api/",
|
"https"
|
||||||
"https://badges.fedoraproject.org/static/img/",
|
],
|
||||||
"http://www.planet-libre.org/themes/planetlibre/images/",
|
"host_equals": "gravatar.com",
|
||||||
"https://www.azuracast.com/img/",
|
"path_prefix": "/avatar/"
|
||||||
"https://reps.mozilla.org/static/base/img/remo/",
|
},
|
||||||
|
{
|
||||||
|
"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!
|
# This MUST BE THE LAST!
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Test our utils from ivatar.utils
|
|||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from ivatar.utils import openid_variations
|
from ivatar.utils import is_trusted_url, openid_variations
|
||||||
|
|
||||||
|
|
||||||
class Tester(TestCase):
|
class Tester(TestCase):
|
||||||
@@ -45,3 +45,60 @@ class Tester(TestCase):
|
|||||||
self.assertEqual(openid_variations(openid3)[1], openid1)
|
self.assertEqual(openid_variations(openid3)[1], openid1)
|
||||||
self.assertEqual(openid_variations(openid3)[2], openid2)
|
self.assertEqual(openid_variations(openid3)[2], openid2)
|
||||||
self.assertEqual(openid_variations(openid3)[3], openid3)
|
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 random
|
||||||
import string
|
import string
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
def random_string(length=10):
|
def random_string(length=10):
|
||||||
@@ -112,3 +113,42 @@ def mm_ng(
|
|||||||
)
|
)
|
||||||
|
|
||||||
return image
|
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 UnconfirmedEmail, UnconfirmedOpenId
|
||||||
from .ivataraccount.models import Photo
|
from .ivataraccount.models import Photo
|
||||||
from .ivataraccount.models import pil_format, file_format
|
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
|
URL_TIMEOUT = 5 # in seconds
|
||||||
|
|
||||||
@@ -146,7 +146,9 @@ class AvatarImageView(TemplateView):
|
|||||||
# Check for :// (schema)
|
# Check for :// (schema)
|
||||||
if default is not None and default.find("://") > 0:
|
if default is not None and default.find("://") > 0:
|
||||||
# Check if it's trusted, if not, reset to None
|
# 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(
|
print(
|
||||||
"Default URL is not in trusted URLs: '%s' ; Kicking it!" % default
|
"Default URL is not in trusted URLs: '%s' ; Kicking it!" % default
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user