检查字符串是否是任何 URL 模式的前缀
Checking if a string is a prefix of any URL pattern
无论是否不可取,我的 Django 站点在根目录下为每个用户分配一个页面,例如 /rgov
。
我使用字符集白名单,因此应该 阻止创建 index.html
或一些恶意的 。我的 URL 配置也是最后路由用户页面,所以应该不可能通过注册相应的名称来劫持 /admin
或其他任何东西。
但是,我想阻止用户注册 admin
,因为他们的页面会被破坏。
(Similar question,没有一个理想的解决方案,下面会介绍。)
这是我的尝试:
def is_reserved(username):
r = urls.resolvers.get_resolver('mysite.systemurls')
hit = False
for path in ('/{}', '/{}/'):
try:
r.resolve(path.format(username))
hit = True
break
except urls.exceptions.Resolver404:
continue
return hit
此处,mysite.systemurls
模块定义了除用户页面之外的每个 URL 模式。
这确实阻止了选择用户名 admin
,因为有为 /admin/
定义的路由。但它不阻止api
,因为虽然有/api/foo/bar
,但没有/api/
.
的路由
有没有办法测试是否存在后缀为/api/
的路由(例如)?由于 URL 模式是正则表达式,可能不是那么容易,但理论上应该是可以的。
这是我不雅的解决方案,抱歉,造成了不便。我使用 Django 系统检查框架实施了检查。该检查收集应用程序使用的所有 URL 模式,然后从每个模式中提取第一个路径组件。然后它确保 none 这些第一个路径组件
如果您的 URL 模式中有一些 re_path
打破了所做的假设,这将不起作用。
import re
from django import urls
from django.core.checks import register, Error, Tags, Warning
from . import usernames
@register(Tags.urls, Tags.security)
def check_scary_available_usernames(app_configs=None, **kwargs):
'''
Checks that there are no URL patterns /x/y where /x itself is not a pattern.
In this case, /x might be available for user registration, which would be bad.
'''
errors, prefixes = [], set()
r = urls.resolvers.get_resolver('mysite.systemurls')
descend_into_resolver(r, [], errors, prefixes)
# Check to make sure none of these usernames is taken or available
for prefix in prefixes:
if not prefix:
continue
if not usernames.is_reserved(prefix):
errors.append(Warning(
'There is no restriction on registering the forbidden username {}, '
'which would conflict with a URL in use by the system.'.format(prefix)
))
if usernames.user_exists(prefix):
errors.append(Warning(
'A user has the forbidden username {}, which conflicts with a URL in '
'use by the system.'.format(prefix)
))
return errors
def descend_into_resolver(resolver, chain, errors, prefixes):
for up in resolver.url_patterns:
regex = up.pattern.regex.pattern
if isinstance(up, urls.resolvers.URLResolver):
descend_into_resolver(up, chain + [regex], errors, prefixes)
elif isinstance(up, urls.resolvers.URLPattern):
collect_pattern_prefix(chain + [regex], errors, prefixes)
else:
errors.append(Warning(
'Resolver has unexpected URL pattern: {}'.format(repr(up))
))
def collect_pattern_prefix(patterns, errors, prefixes):
# Remember, we are matching against a regular expression pattern! We are not
# taking a robust approach; if it fails, this could report spurious warnings.
uberpattern = r''
for i, pattern in enumerate(patterns):
if i != 0:
pattern = pattern.lstrip('^')
if i != len(patterns) - 1:
pattern = pattern.rstrip('$')
uberpattern += pattern
uberpattern = uberpattern.replace('\/', '/')
m = re.match(r'^\^?([^/$]*)', uberpattern)
if m is None:
errors.append(Warning(
'Could not determine first component of URL pattern '
'{}'.format(uberpattern)
))
else:
prefixes.add(m.group(1))
无论是否不可取,我的 Django 站点在根目录下为每个用户分配一个页面,例如 /rgov
。
我使用字符集白名单,因此应该 阻止创建 index.html
或一些恶意的 。我的 URL 配置也是最后路由用户页面,所以应该不可能通过注册相应的名称来劫持 /admin
或其他任何东西。
但是,我想阻止用户注册 admin
,因为他们的页面会被破坏。
(Similar question,没有一个理想的解决方案,下面会介绍。)
这是我的尝试:
def is_reserved(username):
r = urls.resolvers.get_resolver('mysite.systemurls')
hit = False
for path in ('/{}', '/{}/'):
try:
r.resolve(path.format(username))
hit = True
break
except urls.exceptions.Resolver404:
continue
return hit
此处,mysite.systemurls
模块定义了除用户页面之外的每个 URL 模式。
这确实阻止了选择用户名 admin
,因为有为 /admin/
定义的路由。但它不阻止api
,因为虽然有/api/foo/bar
,但没有/api/
.
有没有办法测试是否存在后缀为/api/
的路由(例如)?由于 URL 模式是正则表达式,可能不是那么容易,但理论上应该是可以的。
这是我不雅的解决方案,抱歉,造成了不便。我使用 Django 系统检查框架实施了检查。该检查收集应用程序使用的所有 URL 模式,然后从每个模式中提取第一个路径组件。然后它确保 none 这些第一个路径组件
如果您的 URL 模式中有一些 re_path
打破了所做的假设,这将不起作用。
import re
from django import urls
from django.core.checks import register, Error, Tags, Warning
from . import usernames
@register(Tags.urls, Tags.security)
def check_scary_available_usernames(app_configs=None, **kwargs):
'''
Checks that there are no URL patterns /x/y where /x itself is not a pattern.
In this case, /x might be available for user registration, which would be bad.
'''
errors, prefixes = [], set()
r = urls.resolvers.get_resolver('mysite.systemurls')
descend_into_resolver(r, [], errors, prefixes)
# Check to make sure none of these usernames is taken or available
for prefix in prefixes:
if not prefix:
continue
if not usernames.is_reserved(prefix):
errors.append(Warning(
'There is no restriction on registering the forbidden username {}, '
'which would conflict with a URL in use by the system.'.format(prefix)
))
if usernames.user_exists(prefix):
errors.append(Warning(
'A user has the forbidden username {}, which conflicts with a URL in '
'use by the system.'.format(prefix)
))
return errors
def descend_into_resolver(resolver, chain, errors, prefixes):
for up in resolver.url_patterns:
regex = up.pattern.regex.pattern
if isinstance(up, urls.resolvers.URLResolver):
descend_into_resolver(up, chain + [regex], errors, prefixes)
elif isinstance(up, urls.resolvers.URLPattern):
collect_pattern_prefix(chain + [regex], errors, prefixes)
else:
errors.append(Warning(
'Resolver has unexpected URL pattern: {}'.format(repr(up))
))
def collect_pattern_prefix(patterns, errors, prefixes):
# Remember, we are matching against a regular expression pattern! We are not
# taking a robust approach; if it fails, this could report spurious warnings.
uberpattern = r''
for i, pattern in enumerate(patterns):
if i != 0:
pattern = pattern.lstrip('^')
if i != len(patterns) - 1:
pattern = pattern.rstrip('$')
uberpattern += pattern
uberpattern = uberpattern.replace('\/', '/')
m = re.match(r'^\^?([^/$]*)', uberpattern)
if m is None:
errors.append(Warning(
'Could not determine first component of URL pattern '
'{}'.format(uberpattern)
))
else:
prefixes.add(m.group(1))