Skip to content

Security: thomasbruninx/astergui

Security

docs/security.md

Security model

Privilege separation

  • Web/API runs as the unprivileged astergui user with NoNewPrivileges, ProtectSystem=strict, empty capability set. It cannot write /etc/asterisk, has no sudo, and cannot read /etc/asterisk/webgui (mode 0750 asterisk).
  • Every privileged action goes through astergui-helper (root, stdlib only, ~500 lines). systemd owns the socket /run/astergui-helper.sock (mode 0600, only the astergui user); the client can send only apply, rollback or read-deployed, sip-cert-status, sync-media - no arguments cross the boundary, no sudoers rules. The only commands with an argument are rec-get, rec-delete (a recording file name) and rec-prune (a number of days); each argument is matched against a fixed pattern by the helper itself (YYYYMMDD-HHMMSS_<caller>_<dialled>_<id>.wav), so no path can be smuggled in.
  • The helper never trusts staged input: O_NOFOLLOW reads into memory, filename whitelist, size and line limits, no #include/#exec, deny-list of code-running dialplan applications (System, Shell, AGI, FILE(), CURL() ...), writes only inside /etc/asterisk/webgui/. Snapshots and state live in /var/lib/astergui-helper (root 0700).

Call flow, audio and recordings

  • Audio uploads are validated three times (browser conversion, API, root helper): uncompressed 16-bit mono PCM WAV at 8/16 kHz, at most 25 MB and 60 minutes each, names [a-z0-9_-]. The helper copies them from the staging area (no symlinks, no unexpected entries) into asterisk-owned directories and only ever deletes *.wav/*.wav16 files; a staging area without index.json is refused, so an empty stage can never wipe the library.
  • Recordings are readable, downloadable and deletable by administrators only. Playback is written to the audit log. The helper copies a file to a random name in a directory only the web user can read (/var/lib/astergui/export, removed after streaming, purged after an hour). Recordings are not included in astergui backups. Whether and how you may record calls is a legal matter for you.
  • Dialplan deny-list: besides System/Shell/AGI/FILE()/CURL(), the helper refuses MixMonitor with an absolute path, .. or a third (shell command) argument, so a compromised web app cannot use recording to write arbitrary files or run commands.
  • Voicemail messages are administrator-only in the UI/API (list, play, download, delete); every play and delete is audited. The helper takes a mailbox (2-8 digits), a fixed folder name and msgNNNN, refuses anything else, never follows symlinks, and copies the audio to the same private export area as recordings.
  • Forwarding to external numbers can cost money and lets callers reach your outbound routes through an extension: only administrators can set it.
  • Packet capture: the web service never runs tcpdump. cap-start takes an interface name that must exist in /sys/class/net (or any), one of three presets, an optional IP address, up to eight port numbers and two bounded limits; the BPF filter is assembled from those validated pieces (no free-form expression). The capture runs in its own unit (astergui-capture.service: root but only CAP_NET_RAW, CAP_NET_ADMIN, CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_FOWNER, read-only filesystem except the captures folder) and re-validates the request file itself. Files are root:astergui 0640 in a folder owned by the web user, so listing, download and delete need no privileges. A compromised web service could therefore read existing captures (as it can already reach the recording exports); keep captures short-lived and delete them after analysis.
  • The helper's systemd unit can write only the GUI-owned directories (/etc/asterisk/webgui, the audio and recordings folders, the export folder).

Secrets

  • SIP passwords: only md5_cred digests stored/rendered; plaintext shown once, cannot be retrieved, only regenerated. (Trade-off: MD5 digest auth is what SIP uses anyway; a stolen digest still allows offline guessing, mitigated by 24-char random secrets.)
  • Voicemail PINs and future trunk credentials must be plaintext in Asterisk config; in the DB they are Fernet-encrypted (key in /etc/astergui/astergui.env, 0640 root:astergui). Never returned by the API, never audited or logged.
  • Database uses peer authentication over a unix socket: no DB password exists. PostgreSQL listens on localhost only.
  • AMI: 127.0.0.1 only, permit restricted to loopback, minimal privileges, secret generated at install.

Web

  • Argon2id password hashes; server-side sessions (only a SHA-256 of the token is stored); cookie HttpOnly; Secure; SameSite=Lax.
  • CSRF: per-session token in X-CSRF-Token for all mutating requests + Origin check.
  • Login rate limiting per user and per IP (5 / 20 failures per 15 min, DB-backed, survives restarts); uniform error message and timing.
  • All input validated (extension numbers, names, codecs, e-mail, PIN); renderers refuse newlines/;/NUL a second time.
  • nginx sets CSP (default-src 'self'), X-Frame-Options: DENY, nosniff; HSTS is left commented until a trusted certificate is installed.
  • An optional plain-HTTP listener exists (astergui web http enable, off by default, flagged on the Security page). The Secure cookie flag follows the scheme nginx reports (X-Forwarded-Proto); nginx always overwrites that header, and the app only listens on a unix socket.
  • Web phone: nginx exposes /ws (proxy to Asterisk's loopback-only HTTP server) but asks the astergui API first (auth_request), so only a signed-in administrator can open the WebSocket; Asterisk still requires the SIP digest login of a web-phone extension. The credentials endpoint is administrator-only and every hand-over is audited. The SIP secret of web-phone extensions is stored Fernet-encrypted (all other extensions keep only md5_cred), so a database dump plus the key file would reveal those secrets. Use dedicated extensions for it.
  • The default TLS certificate is self-signed - replace /etc/astergui/tls/astergui.{crt,key}.

Accounts

  • Roles: administrator and read-only. Enforced on the server for every mutating request (a read-only session gets 403 even if the UI is bypassed); user management and backups are administrator-only. Temporary passwords are shown once and must be replaced at first sign-in (until then only the change-password call works). The last active administrator cannot be disabled, demoted or deleted; nobody can lock themselves out.
  • Changing or resetting a password signs the affected user out everywhere else. Sessions can be listed and revoked.
  • Password rules: >= 12 characters, not containing the username, not trivially repetitive; Argon2id hashing.

Hardening features

  • Per-extension network ACL (deny=0.0.0.0/0 + permit=...): registration and calls from other addresses are refused (Not match Endpoint ACL).
  • SIP over TLS, pinned to TLS 1.2 (PJSIP's default would be TLSv1), and SRTP (SDES) per extension; SRTP cannot be enabled without TLS.
  • Trunk contexts are isolated: inbound calls can only reach inbound routes, never dial out or reach extensions directly.
  • Click-to-call uses its own AMI user with only originate; the main AMI user has no originate, neither has system+originate together (which would allow System() through AMI).
  • Backups are 0600, contain secrets, and are verified by checksum before a restore touches the database.
  • CDR export neutralises spreadsheet formula injection (cells starting with = + - @ other than plain phone numbers).
  • Security page (read-only) reports exposed listeners, firewall state, certificate expiry, missing backups, configuration drift and well-known sample-config risks (e.g. the stock voicemail mailbox 1234 with PIN 4242). It proposes firewall rules but never applies them.
  • fail2ban filter/jail for repeated failed SIP logins are provided (opt-in, scripts/enable-fail2ban.sh).

Not covered yet

  • astergui never configures the firewall itself (by design). SIP (udp/5060) and RTP (udp/10000-20000) are open if the host has no firewall.
  • Sample IAX2/Skinny listeners of the stock Asterisk config are reported, not disabled.
  • No 2FA. WebRTC endpoints are not supported.

There aren't any published security advisories