Skip to content

AMI: write-permission (write_perm) is never enforced — any authenticated user can Originate / run CLI Command / write config #126

Description

@ryanmurf

Summary

The AMI action dispatcher authorizes on authentication only. It never consults the authenticated user's write_perm before executing dangerous actions. AmiUser::can_write / AmiSession::write_perm exist but are dead outside tests — every authenticated session can run every action.

Location

  • crates/asterisk-ami/src/actions.rs:66-96ActionRegistry::dispatch
  • The only gate is crates/asterisk-ami/src/actions.rs:75:
    if name_lower != "login" && name_lower != "challenge" && !session.authenticated {
        return AmiResponse::error("Permission denied") ...
    }
  • write_perm is set on login (session.rs:87-88) then read nowhere. Proof:
    $ grep -rn 'can_write\|write_perm' crates/asterisk-ami/src
    # every hit is a definition, a field assignment, or a test — no call site in dispatch/handlers
    

Attack

A user authenticated with any credentials (including one an operator intended as read-only) can invoke:

  • Originate (actions.rs:489) — place arbitrary calls / run dialplan apps.
  • Command (actions.rs:1005, execute_cli_command) — CLI execution surface.
  • Redirect / Hangup / Bridge / Park / SetVar / UpdateConfig / QueueAdd/Remove/Pause / Atxfer / ConfBridgeKick/Mute / PJSIPNotify — call control, dialplan-variable writes, config writes.

There is no per-action authority check, so a low-privilege AMI account is equivalent to a full-privilege one.

Impact

Privilege escalation / missing authorization on the manager interface. Combined with # (all users are created with write_perm = ALL anyway), every AMI user today is effectively omnipotent. This is the AMI half of the PBX trust boundary.

Recommended fix (auth-model change — needs a human/coordinator call, do NOT silently auto-merge)

  1. Give each dangerous action a required write EventCategory (mirroring Asterisk's per-action authority): Command→COMMAND, UpdateConfig→CONFIG, Originate/Redirect/Hangup/Bridge/Park/SetVar/Atxfer/QueueCALL (or SYSTEM for Originate, per Asterisk's system/call split), ConfBridgeCALL, PJSIPNotify→SYSTEM.
  2. In dispatch, after the auth gate, look up the required category and reject with Permission denied if !session.write_perm.contains(required).
  3. Read-only status actions (Ping, Core*Status, CoreShowChannels, List, Show) stay ungated for any authenticated session, matching Asterisk.

Risk note: this changes the AMI authorization model. It is backwards-compatible for existing deployments only if the default remains write_perm = ALL (today every configured user is ALL — see the manager.conf-perms issue), so no current user loses access; it only lets operators create genuinely restricted users. Ship together with honoring read=/write= in manager.conf. A ready patch + red-able tests (a write_perm = CALL user is denied Command; an ALL user is allowed) can be provided.

Filed by the security/authz hardening pass. Not auto-merged because it alters the auth model.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions