Add strip_unsafe_input/INPUT_CLEANUP_BYPASS_PERMISSIONS helpers to strip unsafe input on a per-command level. Resolves #1738.
This commit is contained in:
parent
0556f527fe
commit
2a8cc57bbe
6 changed files with 74 additions and 2 deletions
|
|
@ -186,6 +186,9 @@ without arguments starts a full interactive Python console.
|
||||||
- Fixes in multi-match situations - don't allow finding/listing multimatches for 3-box when
|
- Fixes in multi-match situations - don't allow finding/listing multimatches for 3-box when
|
||||||
only two boxes in location.
|
only two boxes in location.
|
||||||
- Fix for TaskHandler with proper deferred returns/ability to cancel etc (PR by davewiththenicehat)
|
- Fix for TaskHandler with proper deferred returns/ability to cancel etc (PR by davewiththenicehat)
|
||||||
|
- Add `PermissionHandler.check` method for straight string perm-checks without needing lockstrings.
|
||||||
|
- Add `evennia.utils.utils.strip_unsafe_input` for removing html/newlines/tags from user input. The
|
||||||
|
`INPUT_CLEANUP_BYPASS_PERMISSIONS` is a list of perms that bypass this safety stripping.
|
||||||
|
|
||||||
|
|
||||||
## Evennia 0.9 (2018-2019)
|
## Evennia 0.9 (2018-2019)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from evennia.locks.lockhandler import LockException
|
||||||
from evennia.comms.comms import DefaultChannel
|
from evennia.comms.comms import DefaultChannel
|
||||||
from evennia.utils import create, logger, utils
|
from evennia.utils import create, logger, utils
|
||||||
from evennia.utils.logger import tail_log_file
|
from evennia.utils.logger import tail_log_file
|
||||||
from evennia.utils.utils import class_from_module
|
from evennia.utils.utils import class_from_module, strip_unsafe_input
|
||||||
from evennia.utils.evmenu import ask_yes_no
|
from evennia.utils.evmenu import ask_yes_no
|
||||||
|
|
||||||
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)
|
||||||
|
|
@ -298,6 +298,9 @@ class CmdChannel(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg(f"You are not allowed to send messages to channel {channel}")
|
caller.msg(f"You are not allowed to send messages to channel {channel}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# avoid unsafe tokens in message
|
||||||
|
message = strip_unsafe_input(message, self.session)
|
||||||
|
|
||||||
channel.msg(message, senders=self.caller, **kwargs)
|
channel.msg(message, senders=self.caller, **kwargs)
|
||||||
|
|
||||||
def get_channel_history(self, channel, start_index=0):
|
def get_channel_history(self, channel, start_index=0):
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ def text(session, *args, **kwargs):
|
||||||
arguments are ignored.
|
arguments are ignored.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# from evennia.server.profiling.timetrace import timetrace
|
# from evennia.server.profiling.timetrace import timetrace
|
||||||
# text = timetrace(text, "ServerSession.data_in")
|
# text = timetrace(text, "ServerSession.data_in")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -722,6 +722,12 @@ CREATION_THROTTLE_LIMIT = 2
|
||||||
CREATION_THROTTLE_TIMEOUT = 10 * 60
|
CREATION_THROTTLE_TIMEOUT = 10 * 60
|
||||||
LOGIN_THROTTLE_LIMIT = 5
|
LOGIN_THROTTLE_LIMIT = 5
|
||||||
LOGIN_THROTTLE_TIMEOUT = 5 * 60
|
LOGIN_THROTTLE_TIMEOUT = 5 * 60
|
||||||
|
# Certain characters, like html tags, line breaks and tabs are stripped
|
||||||
|
# from user input for commands using the `evennia.utils.strip_unsafe_input` helper
|
||||||
|
# since they can be exploitative. This list defines Account-level permissions
|
||||||
|
# (and higher) that bypass this stripping. It is used as a fallback if a
|
||||||
|
# specific list of perms are not given to the helper function.
|
||||||
|
INPUT_CLEANUP_BYPASS_PERMISSIONS = ['Builder']
|
||||||
|
|
||||||
|
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,9 @@ class ANSIParser(object):
|
||||||
# instance of each
|
# instance of each
|
||||||
ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
|
ansi_escapes = re.compile(r"(%s)" % "|".join(ANSI_ESCAPES), re.DOTALL)
|
||||||
|
|
||||||
|
# tabs/linebreaks |/ and |- should be able to be cleaned
|
||||||
|
unsafe_tokens = re.compile(r"\|\/|\|-", re.DOTALL)
|
||||||
|
|
||||||
def sub_ansi(self, ansimatch):
|
def sub_ansi(self, ansimatch):
|
||||||
"""
|
"""
|
||||||
Replacer used by `re.sub` to replace ANSI
|
Replacer used by `re.sub` to replace ANSI
|
||||||
|
|
@ -430,6 +433,13 @@ class ANSIParser(object):
|
||||||
string = self.mxp_url_sub.sub(r"\1", string) # replace with url verbatim
|
string = self.mxp_url_sub.sub(r"\1", string) # replace with url verbatim
|
||||||
return string
|
return string
|
||||||
|
|
||||||
|
def strip_unsafe_tokens(self, string):
|
||||||
|
"""
|
||||||
|
Strip explicitly ansi line breaks and tabs.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self.unsafe_tokens.sub('', string)
|
||||||
|
|
||||||
def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False):
|
def parse_ansi(self, string, strip_ansi=False, xterm256=False, mxp=False):
|
||||||
"""
|
"""
|
||||||
Parses a string, subbing color codes according to the stored
|
Parses a string, subbing color codes according to the stored
|
||||||
|
|
@ -564,6 +574,15 @@ def strip_raw_ansi(string, parser=ANSI_PARSER):
|
||||||
return parser.strip_raw_codes(string)
|
return parser.strip_raw_codes(string)
|
||||||
|
|
||||||
|
|
||||||
|
def strip_unsafe_tokens(string, parser=ANSI_PARSER):
|
||||||
|
"""
|
||||||
|
Strip markup that can be used to create visual exploits
|
||||||
|
(notably linebreaks and tags)
|
||||||
|
|
||||||
|
"""
|
||||||
|
return parser.strip_unsafe_tokens(string)
|
||||||
|
|
||||||
|
|
||||||
def raw(string):
|
def raw(string):
|
||||||
"""
|
"""
|
||||||
Escapes a string into a form which won't be colorized by the ansi
|
Escapes a string into a form which won't be colorized by the ansi
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,13 @@ from simpleeval import simple_eval
|
||||||
from unicodedata import east_asian_width
|
from unicodedata import east_asian_width
|
||||||
from twisted.internet.task import deferLater
|
from twisted.internet.task import deferLater
|
||||||
from twisted.internet.defer import returnValue # noqa - used as import target
|
from twisted.internet.defer import returnValue # noqa - used as import target
|
||||||
|
from twisted.internet import threads, reactor
|
||||||
from os.path import join as osjoin
|
from os.path import join as osjoin
|
||||||
from inspect import ismodule, trace, getmembers, getmodule, getmro
|
from inspect import ismodule, trace, getmembers, getmodule, getmro
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
from twisted.internet import threads, reactor
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.html import strip_tags
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.validators import validate_email as django_validate_email
|
from django.core.validators import validate_email as django_validate_email
|
||||||
|
|
@ -44,6 +45,7 @@ ENCODINGS = settings.ENCODINGS
|
||||||
|
|
||||||
_TASK_HANDLER = None
|
_TASK_HANDLER = None
|
||||||
_TICKER_HANDLER = None
|
_TICKER_HANDLER = None
|
||||||
|
_STRIP_UNSAFE_TOKENS = None
|
||||||
|
|
||||||
_GA = object.__getattribute__
|
_GA = object.__getattribute__
|
||||||
_SA = object.__setattr__
|
_SA = object.__setattr__
|
||||||
|
|
@ -2588,3 +2590,41 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs):
|
||||||
if raise_errors:
|
if raise_errors:
|
||||||
raise
|
raise
|
||||||
return args, kwargs
|
return args, kwargs
|
||||||
|
|
||||||
|
|
||||||
|
def strip_unsafe_input(txt, session=None, bypass_perms=None):
|
||||||
|
"""
|
||||||
|
Remove 'unsafe' text codes from text; these are used to elimitate
|
||||||
|
exploits in user-provided data, such as html-tags, line breaks etc.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
txt (str): The text to clean.
|
||||||
|
session (Session, optional): A Session in order to determine if
|
||||||
|
the check should be bypassed by permission (will be checked
|
||||||
|
with the 'perm' lock, taking permission hierarchies into account).
|
||||||
|
bypass_perms (list, optional): Iterable of permission strings
|
||||||
|
to check for bypassing the strip. If not given, use
|
||||||
|
`settings.INPUT_CLEANUP_BYPASS_PERMISSIONS`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The cleaned string.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
The `INPUT_CLEANUP_BYPASS_PERMISSIONS` list defines what account
|
||||||
|
permissions are required to bypass this strip.
|
||||||
|
|
||||||
|
"""
|
||||||
|
global _STRIP_UNSAFE_TOKENS
|
||||||
|
if not _STRIP_UNSAFE_TOKENS:
|
||||||
|
from evennia.utils.ansi import strip_unsafe_tokens as _STRIP_UNSAFE_TOKENS
|
||||||
|
|
||||||
|
if session:
|
||||||
|
obj = session.puppet if session.puppet else session.account
|
||||||
|
bypass_perms = bypass_perms or settings.INPUT_CLEANUP_BYPASS_PERMISSIONS
|
||||||
|
if obj.permissions.check(*bypass_perms):
|
||||||
|
return txt
|
||||||
|
|
||||||
|
# remove html codes
|
||||||
|
txt = strip_tags(txt)
|
||||||
|
txt = _STRIP_UNSAFE_TOKENS(txt)
|
||||||
|
return txt
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue