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:
Griatch 2021-10-09 16:27:58 +02:00
parent 0556f527fe
commit 2a8cc57bbe
6 changed files with 74 additions and 2 deletions

View file

@ -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
only two boxes in location.
- 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)

View file

@ -15,7 +15,7 @@ from evennia.locks.lockhandler import LockException
from evennia.comms.comms import DefaultChannel
from evennia.utils import create, logger, utils
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
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}")
return
# avoid unsafe tokens in message
message = strip_unsafe_input(message, self.session)
channel.msg(message, senders=self.caller, **kwargs)
def get_channel_history(self, channel, start_index=0):

View file

@ -59,6 +59,7 @@ def text(session, *args, **kwargs):
arguments are ignored.
"""
# from evennia.server.profiling.timetrace import timetrace
# text = timetrace(text, "ServerSession.data_in")

View file

@ -722,6 +722,12 @@ CREATION_THROTTLE_LIMIT = 2
CREATION_THROTTLE_TIMEOUT = 10 * 60
LOGIN_THROTTLE_LIMIT = 5
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']
######################################################################

View file

@ -252,6 +252,9 @@ class ANSIParser(object):
# instance of each
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):
"""
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
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):
"""
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)
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):
"""
Escapes a string into a form which won't be colorized by the ansi

View file

@ -24,12 +24,13 @@ from simpleeval import simple_eval
from unicodedata import east_asian_width
from twisted.internet.task import deferLater
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 inspect import ismodule, trace, getmembers, getmodule, getmro
from collections import defaultdict, OrderedDict
from twisted.internet import threads, reactor
from django.conf import settings
from django.utils import timezone
from django.utils.html import strip_tags
from django.utils.translation import gettext as _
from django.apps import apps
from django.core.validators import validate_email as django_validate_email
@ -44,6 +45,7 @@ ENCODINGS = settings.ENCODINGS
_TASK_HANDLER = None
_TICKER_HANDLER = None
_STRIP_UNSAFE_TOKENS = None
_GA = object.__getattribute__
_SA = object.__setattr__
@ -2588,3 +2590,41 @@ def safe_convert_to_types(converters, *args, raise_errors=True, **kwargs):
if raise_errors:
raise
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