From 6fef01a3b143af4764ce46d323eec5ff68e49118 Mon Sep 17 00:00:00 2001 From: Griatch Date: Tue, 2 Nov 2021 22:52:13 +0100 Subject: [PATCH] Allow to disable MXP or make it one-directional. Resolve #2169. --- CHANGELOG.md | 2 ++ evennia/server/inputfuncs.py | 23 +++++++++++++++++++++++ evennia/server/portal/mxp.py | 11 ++++++++--- evennia/settings_default.py | 8 ++++++++ evennia/utils/ansi.py | 14 ++++++++++++-- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f645e6682..e7bf738552 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -108,6 +108,8 @@ Up requirements to Django 3.2+, Twisted 21+ - Add confirmation question to `ban`/`unban` commands. - Check new `teleport` and `teleport_here` lock-types in `teleport` command to optionally allow to limit teleportation of an object or to a specific destination. +- Add `settings.MXP_ENABLED=True` and `settings.MXP_OUTGOING_ONLY=True` as sane defaults, + to avoid known security issues with players entering MXP links. ### Evennia 0.9.5 (2019-2020) diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index 976c40c32e..0f9c791554 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -38,10 +38,24 @@ _GA = object.__getattribute__ _SA = object.__setattr__ +_STRIP_INCOMING_MXP = settings.MXP_ENABLED and settings.MXP_OUTGOING_ONLY +_STRIP_MXP = None + + + def _NA(o): return "N/A" +def _maybe_strip_incoming_mxp(txt): + global _STRIP_MXP + if _STRIP_INCOMING_MXP: + if not _STRIP_MXP: + from evennia.utils.ansi import strip_mxp as _STRIP_MXP + return _STRIP_MXP(txt) + return txt + + _ERROR_INPUT = "Inputfunc {name}({session}): Wrong/unrecognized input: {inp}" @@ -74,6 +88,9 @@ def text(session, *args, **kwargs): if txt.strip() in _IDLE_COMMAND: session.update_session_counters(idle=True) return + + txt = _maybe_strip_incoming_mxp(txt) + if session.account: # nick replacement puppet = session.puppet @@ -112,6 +129,9 @@ def bot_data_in(session, *args, **kwargs): if txt.strip() in _IDLE_COMMAND: session.update_session_counters(idle=True) return + + txt = _maybe_strip_incoming_mxp(txt) + kwargs.pop("options", None) # Trigger the execute_cmd method of the corresponding bot. session.account.execute_cmd(session=session, txt=txt, **kwargs) @@ -122,6 +142,9 @@ def echo(session, *args, **kwargs): """ Echo test function """ + if _STRIP_INCOMING_MXP: + txt = strip_mxp(txt) + session.data_out(text="Echo returns: %s" % args) diff --git a/evennia/server/portal/mxp.py b/evennia/server/portal/mxp.py index 7f5b39d392..80bad0e566 100644 --- a/evennia/server/portal/mxp.py +++ b/evennia/server/portal/mxp.py @@ -14,6 +14,7 @@ http://www.gammon.com.au/mushclient/addingservermxp.htm """ import re +from django.conf import settings LINKS_SUB = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL) URL_SUB = re.compile(r"\|lu(.*?)\|lt(.*?)\|le", re.DOTALL) @@ -60,7 +61,8 @@ class Mxp: """ self.protocol = protocol self.protocol.protocol_flags["MXP"] = False - self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp) + if settings.MXP_ENABLED: + self.protocol.will(MXP).addCallbacks(self.do_mxp, self.no_mxp) def no_mxp(self, option): """ @@ -81,6 +83,9 @@ class Mxp: option (Option): Not used. """ - self.protocol.protocol_flags["MXP"] = True - self.protocol.requestNegotiation(MXP, b"") + if settings.MXP_ENABLED: + self.protocol.protocol_flags["MXP"] = True + self.protocol.requestNegotiation(MXP, b"") + else: + self.protocol.wont(MXP) self.protocol.handshake_done() diff --git a/evennia/settings_default.py b/evennia/settings_default.py index b94a29927a..f3a202e18b 100644 --- a/evennia/settings_default.py +++ b/evennia/settings_default.py @@ -192,6 +192,14 @@ ENCODINGS = ["utf-8", "latin-1", "ISO-8859-1"] # of users with screen readers. Note that ANSI/MXP doesn't need to # be stripped this way, that is handled automatically. SCREENREADER_REGEX_STRIP = r"\+-+|\+$|\+~|--+|~~+|==+" +# MXP support means the ability to show clickable links in the client. Clicking +# the link will execute a game command. It's a way to add mouse input to the game. +MXP_ENABLED = True +# If this is set, MXP can only be sent by the server and not added from the +# client side. Disabling this is a potential security risk because it could +# allow malevolent players to lure others to execute commands they did not +# intend to. +MXP_OUTGOING_ONLY = True # Database objects are cached in what is known as the idmapper. The idmapper # caching results in a massive speedup of the server (since it dramatically # limits the number of database accesses needed) and also allows for diff --git a/evennia/utils/ansi.py b/evennia/utils/ansi.py index 7fc0b34da0..fe2f791c0f 100644 --- a/evennia/utils/ansi.py +++ b/evennia/utils/ansi.py @@ -73,6 +73,8 @@ from evennia.utils import logger from evennia.utils.utils import to_str +MXP_ENABLED = settings.MXP_ENABLED + # ANSI definitions @@ -583,6 +585,14 @@ def strip_unsafe_tokens(string, parser=ANSI_PARSER): return parser.strip_unsafe_tokens(string) +def strip_mxp(string, parser=ANSI_PARSER): + """ + Strip MXP markup. + + """ + return parser.strip_mxp(string) + + def raw(string): """ Escapes a string into a form which won't be colorized by the ansi @@ -792,8 +802,8 @@ class ANSIString(str, metaclass=ANSIMeta): decoded = True if not decoded: # Completely new ANSI String - clean_string = parser.parse_ansi(string, strip_ansi=True, mxp=True) - string = parser.parse_ansi(string, xterm256=True, mxp=True) + clean_string = parser.parse_ansi(string, strip_ansi=True, mxp=MXP_ENABLED) + string = parser.parse_ansi(string, xterm256=True, mxp=MXP_ENABLED) elif clean_string is not None: # We have an explicit clean string. pass