Skip to content

Commit e3d0f3e

Browse files
committed
Update security 3.2.2
1 parent 6faf4c3 commit e3d0f3e

File tree

4 files changed

+53
-7
lines changed

4 files changed

+53
-7
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
77
however, insignificant breaking changes does not guarantee a major version bump, see the reasoning [here](https://github.com/kyb3r/modmail/issues/319).
88

9+
# v3.2.2
10+
11+
Security update!
12+
13+
### Important
14+
15+
- Supporter permission users used to be able to "hack" snippets to reveal all your config vars, including your token and MongoURI.
16+
- Implemented some changes to address this bug:
17+
- All customizable variables used in snippets, close messages, etc, using the `{}` syntax, now forbids chaining 2 or more attributes and attributes that starts with `_`.
18+
- It is advised to update to this version.
19+
- If you felt your credentials have been leaked, consider changing your bot token / mongo uri.
20+
921
# v3.2.1
1022

1123
### Fixed

bot.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "3.2.1"
1+
__version__ = "3.2.2"
22

33
import asyncio
44
import logging
@@ -33,7 +33,7 @@
3333
from core.clients import ApiClient, PluginDatabaseClient
3434
from core.config import ConfigManager
3535
from core.utils import human_join, strtobool, parse_alias
36-
from core.models import PermissionLevel, ModmailLogger
36+
from core.models import PermissionLevel, ModmailLogger, SafeFormatter
3737
from core.thread import ThreadManager
3838
from core.time import human_timedelta
3939

@@ -69,6 +69,7 @@ def __init__(self):
6969
self._session = None
7070
self._api = None
7171
self.metadata_loop = None
72+
self.formatter = SafeFormatter()
7273

7374
self._connected = asyncio.Event()
7475
self.start_time = datetime.utcnow()
@@ -120,7 +121,7 @@ def uptime(self) -> str:
120121
if days:
121122
fmt = "{d}d " + fmt
122123

123-
return fmt.format(d=days, h=hours, m=minutes, s=seconds)
124+
return self.formatter.format(fmt, d=days, h=hours, m=minutes, s=seconds)
124125

125126
def _configure_logging(self):
126127
level_text = self.config["log_level"].upper()
@@ -837,7 +838,7 @@ async def process_commands(self, message):
837838
thread = await self.threads.find(channel=message.channel)
838839
snippet = self.snippets[cmd]
839840
if thread:
840-
snippet = snippet.format(recipient=thread.recipient)
841+
snippet = self.formatter.format(snippet, recipient=thread.recipient)
841842
message.content = f"{self.prefix}reply {snippet}"
842843

843844
ctxs = await self.get_contexts(message)

core/models.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import _string
12
import logging
23
from enum import IntEnum
4+
from string import Formatter
35

46
from discord import Color, Embed
57
from discord.ext import commands
@@ -85,3 +87,33 @@ class _Default:
8587

8688

8789
Default = _Default()
90+
91+
92+
class SafeFormatter(Formatter):
93+
94+
def get_field(self, field_name, args, kwargs):
95+
first, rest = _string.formatter_field_name_split(field_name)
96+
97+
try:
98+
obj = self.get_value(first, args, kwargs)
99+
except (IndexError, KeyError):
100+
return "<Invalid>", first
101+
102+
# loop through the rest of the field_name, doing
103+
# getattr or getitem as needed
104+
# stops when reaches the depth of 2 or starts with _.
105+
try:
106+
for n, (is_attr, i) in enumerate(rest):
107+
if n >= 2:
108+
break
109+
if is_attr:
110+
if str(i).startswith('_'):
111+
break
112+
obj = getattr(obj, i)
113+
else:
114+
obj = obj[i]
115+
else:
116+
return obj, first
117+
except (IndexError, KeyError):
118+
pass
119+
return "<Invalid>", first

core/thread.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,8 @@ async def _close(
393393
else:
394394
message = self.bot.config["thread_close_response"]
395395

396-
message = message.format(
397-
closer=closer, loglink=log_url, logkey=log_data["key"] if log_data else None
396+
message = self.bot.formatter.format(
397+
message, closer=closer, loglink=log_url, logkey=log_data["key"] if log_data else None
398398
)
399399

400400
embed.description = message
@@ -492,7 +492,8 @@ async def _restart_close_timer(self):
492492
)
493493

494494
# Grab message
495-
close_message = self.bot.config["thread_auto_close_response"].format(
495+
close_message = self.bot.formatter.format(
496+
self.bot.config["thread_auto_close_response"],
496497
timeout=human_time
497498
)
498499

0 commit comments

Comments
 (0)