Skip to content

Telegram bot RBAC: reseller role unenforced, unknown roles fail open #131

Description

@rvalitov

reseller role is not enforced — delegated accounts reach the full Admin Control Plane

Affected: v1.4.0-LTS (main, 06ad79a)

Summary

README.md documents the role hierarchy as:

reseller: Delegated commercial management rights restricted to voucher redemption (/redeem), voucher batch generation (/mp_voucher create <cnt> <qta> <dys>), and voucher inventory auditing (/mp_voucher list). Destructive engine commands are automatically blocked with security violation logging.

None of that is enforced by the dispatcher.

_check_tg_role() can return superadmin, reseller, or none, but _process_cmd() only rejected none plus four superadmin-gated commands — a total of five role comparisons in the whole file. 18 of the 22 administrative commands were reachable by a reseller.

Impact

A reseller account — intended as a low-trust, voucher-only commercial role — instead obtains broad operational control of the proxy:

Command Consequence
/mp_link Returns proxy connection links and QR codes for secrets — i.e. working credentials
/mp_secrets Enumerates every secret label with per-user connection and traffic stats
/reply Sends an arbitrary message to an arbitrary chat id, labelled "Support Team Reply" — impersonation of the support team to any user, including the admin
/mp_add Provisions new proxy accounts
/mp_rotate, /mp_enable, /mp_disable Alters the state and keys of existing accounts
/mp_setlimit Changes per-user quotas, connection and IP ceilings
/mp_broadcast Sends a message to every user in bot_users.txt
/mp_status, /mp_traffic, /mp_limits, /mp_upstreams, /mp_health, /mp_digest, /mp_fleet Reveals full operational and infrastructure detail

Because a secret is the access credential for this proxy, /mp_link and /mp_secrets amount to credential disclosure, not merely information disclosure. /reply is a phishing primitive: a message arriving from the operator's own bot, labelled as the support team, is highly credible to an end user.

Secondary: unrecognised roles fail open

_check_tg_role() returns whatever admins.conf holds, unvalidated. The dispatcher only special-cased none and reseller, so any other value falls through into the admin case — for example a typo such as SUPERADMIN, or a stale value like operator. Such an account receives the same access as a reseller: nearly the full control plane, minus the four superadmin-gated commands (those gates are != "superadmin", so they correctly deny by default).

The CLI is not a vector for this. admin_add() normalises the role before writing:

case "$role" in superadmin|reseller) ;; *) role="reseller" ;; esac

and it is the only writer of admins.conf. So the fail-open requires a hand-edited admins.conf — which is a plain text file an operator is free to edit. The safe behaviour (refusing an unrecognised role) is the opposite of what currently happens.

Note this does not soften the primary bug: a coerced reseller already has the same near-total access. The allowlist below is the fix for both.

Secondary: the claimed violation logging does not exist

The README's "security violation logging" is not implemented anywhere — a grep for violation returns nothing in either the manager or the generated bot daemon. Additionally, the four superadmin-gate denials use tg_send(), which delivers the refusal to the admin's chat rather than to the sender, so the offending account gets no feedback at all.

Reproduction

  1. mtproxymax admin add <telegram_id> reseller
  2. From that Telegram account, send /mp_secrets (or /mp_link, or /reply <admin_chat_id> <text>) to the bot.

Expected: refusal, per the documented role.
Actual: the secret list is returned; links and QR codes are returned; the reply is delivered.

Method: validated by invoking _process_cmd() directly with the real _check_tg_role() and the real admins.conf on an Alpine v1.4.0-LTS box, stubbing only the outbound send functions. Since the authorisation decision lives entirely inside that function, this exercises the production code path; nothing was sent to Telegram.

Suggested fix

Make the control plane an explicit allowlist rather than two special-case denials, so any role value that is not superadmin or reseller is refused instead of granted access — deny-by-default for unrecognised roles.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions