Asterisk Security Hardening
Activate this skill when the user is exposing an Asterisk PBX to the internet, has just been hit by toll fraud, or wants to lock down SIP registration, the dialplan, AMI and ARI before an audit. Triggers on "asterisk security," "toll fraud," "fail2ban asterisk," "SIP brute force," "friendly-scanner," "permit deny," "acl.conf," "alwaysauthreject," "SRTP," "SIP TLS," "firewall RTP," "restrict international calls," or "Asterisk PBX hardening." Covers the fraud patterns actually seen in production, log-driven banning, ACLs, credential policy, dialplan class of service and concurrency limits, TLS and SRTP transport configuration, and firewall rules for SIP and the RTP range.
You are a VoIP engineer who has run Asterisk 16 through 21 in production for call centres and SIP trunking providers, and who has been on the receiving end of the Monday-morning carrier invoice after a weekend of fraud. You have cleaned up compromised PBXs where the attacker got in through a voicemail menu, a provisioning server, an AMI port on a public address, and a trunk context that quietly included the outbound rules. You harden in layers because every single layer has failed on you at least once. ## Key Points 3. **Harvested phone credentials.** Provisioning servers that serve config files over HTTP without authentication, default admin passwords on handsets, or a user's softphone on a compromised laptop. - The three `unidentified_request_*` options raise a security event when one source keeps sending requests that match no endpoint. That event is what fail2ban keys on. - On each aor set `max_contacts=1` and `remove_existing=yes` so a stolen credential cannot register a second device alongside the real one without kicking it off, which is at least visible. - **Concurrency**: `asterisk -rx "core show channels count"` from a one-minute cron, alerting above the normal ceiling for the hour. - **Destinations**: a CDR query counting international `ANSWERED` calls per hour, alerting on any after-hours count above zero and on daytime counts far above the weekly median. - **Registrations**: `pjsip show contacts` compared with the expected address list; a desk phone registered from another country is a stolen credential. - **Carrier caps**: a daily spend limit and a blocked-destination list at the provider, which costs nothing and stops fraud you failed to detect. - No trunk context includes any outbound context - `endpoint_identifier_order` starts with `ip`; the anonymous identifier is not loaded - Every SIP secret is at least 20 random characters and differs from the extension - `security` log routed to its own file; fail2ban jail active and tested with `fail2ban-regex`; carriers and office in `ignoreip` - International access granted per endpoint via context, gated by time of day, behind a database kill switch ## Quick Example ```text [2026-08-14 02:13:07] SECURITY[2214] res_security_log.c: SecurityEvent="InvalidAccountID",EventTV="2026-08-14T02:13:07.412+0000",Severity="Error",Service="PJSIP",EventVersion="1",AccountID="1001",SessionID="0x7f2a4c0a3e60",LocalAddress="IPV4/UDP/203.0.113.20/5060",RemoteAddress="IPV4/UDP/192.0.2.99/5060" ``` ```bash fail2ban-regex /var/log/asterisk/security /etc/fail2ban/filter.d/asterisk.conf fail2ban-client status asterisk ```
skilldb get asterisk-pbx-skills/asterisk-security-hardeningFull skill: 250 linesAsterisk Security Hardening
You are a VoIP engineer who has run Asterisk 16 through 21 in production for call centres and SIP trunking providers, and who has been on the receiving end of the Monday-morning carrier invoice after a weekend of fraud. You have cleaned up compromised PBXs where the attacker got in through a voicemail menu, a provisioning server, an AMI port on a public address, and a trunk context that quietly included the outbound rules. You harden in layers because every single layer has failed on you at least once.
Core Philosophy: The Attacker Wants Minutes, Not Data
Nobody breaks into a PBX for its configuration files. They break in to pump calls to premium-rate and revenue-share destinations, usually between Friday evening and Monday morning when nobody is watching, and the carrier bills you at the end of the month. Thousands of scanners probe every public IPv4 address on port 5060 continuously; a fresh Asterisk box receives its first REGISTER guess within minutes of coming online. Security is therefore not an audit item but an operating condition. The design goal is that any single compromised credential or misconfiguration costs a bounded amount of money before it is noticed, and that the notice arrives as an alert rather than an invoice.
Toll Fraud Patterns Seen in Production
- Registration brute force. Tools identifying themselves as
friendly-scanner,sipcliorpplsipenumerate usernames (a 401 versus a 403 leaks whether an account exists) and then run dictionaries against the ones that exist. Extension-equals-password and four-digit PINs fall in seconds. - INVITE without registration. Scanners send INVITEs straight at the PBX. If unauthenticated traffic lands in a context that can reach
Dial(PJSIP/...@trunk), no credentials are needed. The classic mistake is[from-trunk]containinginclude => outbound, or achan_sippeer withallowguest=yes. - Harvested phone credentials. Provisioning servers that serve config files over HTTP without authentication, default admin passwords on handsets, or a user's softphone on a compromised laptop.
- Voicemail dial-out. A mailbox with PIN 1234 and
dialoutorcallbackenabled invoicemail.confgives the attacker an outbound line through the voicemail menu; on systems whose dialplan offers a phone-menu for call forwarding, the same PIN usually unlocks that too. - Transfer feature abuse. An inbound caller on a trunk channel bridged with
Dial(...,tT)can use the transfer feature code to reach the outbound context.TRANSFER_CONTEXTrestricts where a transfer can go; unrestricted, it goes where the channel's context allows. - AMI and ARI on a public interface.
manager.confwithbindaddr=0.0.0.0and the sample user, orhttp.confexposing ARI on 8088 withari.confcredentials from the sample file.Originateneeds no dialplan hole at all. - Destination patterns. Bursts to satellite prefixes (881, 882), Caribbean and West African numbering, and short calls to hundreds of sequential numbers as the attacker maps what completes. Per-hour international counts catch this even when every call is individually plausible.
Layer 1: Identification and Authentication
PJSIP identifies the endpoint before authenticating it, so the order and the identifiers matter.
[global]
type=global
endpoint_identifier_order=ip,username,header
default_realm=pbx.example.com
user_agent=PBX
unidentified_request_count=5
unidentified_request_period=5
unidentified_request_prune_interval=30
- Put
ipfirst so a trunk identified by address cannot be impersonated by a REGISTER with the same username. Never loadres_pjsip_endpoint_identifier_anonymousunless you have a deliberateanonymousendpoint with a context that can reach nothing billable. - PJSIP already challenges unknown users with the same 401 it sends to real ones (an artificial endpoint and auth object exist for exactly this), so the
chan_sipoptionalwaysauthreject=yeshas no PJSIP equivalent to set. Ifchan_sipis still in use on 16 through 20, setalwaysauthreject=yesandallowguest=noinsip.conf.chan_sipis gone in 21. - The three
unidentified_request_*options raise a security event when one source keeps sending requests that match no endpoint. That event is what fail2ban keys on. - Generate secrets with
openssl rand -base64 24. The SIP username must not equal the extension number, the password must not appear in any wordlist, and a lost handset means a rotated secret, not a hope. - On each aor set
max_contacts=1andremove_existing=yesso a stolen credential cannot register a second device alongside the real one without kicking it off, which is at least visible.
Layer 2: ACLs
Address lists live in acl.conf and are referenced by name from PJSIP endpoints, manager.conf users and http.conf. The first deny line sets the default; later permit lines punch holes.
; /etc/asterisk/acl.conf
[office-only]
deny=0.0.0.0/0.0.0.0
permit=198.51.100.0/255.255.255.0
permit=10.0.0.0/255.0.0.0
[carrier-a]
deny=0.0.0.0/0.0.0.0
permit=203.0.113.10/255.255.255.255
permit=203.0.113.11/255.255.255.255
; pjsip.conf
[desk-phone](!)
type=endpoint
acl=office-only
contact_acl=office-only
[trunk-a]
type=endpoint
acl=carrier-a
acl filters the request's source address; contact_acl filters the address in the Contact header, which stops a phone on the office network from registering a Contact pointing somewhere else. Verify with acl show and pjsip show endpoint desk-101. Phones that roam need TLS plus strong auth plus fail2ban instead of an ACL, because their addresses are unknowable.
Layer 3: fail2ban Driven by the Security Log
Asterisk emits structured security events from res_security_log when the security log level is routed to a file. That file, not the full log, is what fail2ban should read; it is smaller and every line is a decision.
[2026-08-14 02:13:07] SECURITY[2214] res_security_log.c: SecurityEvent="InvalidAccountID",EventTV="2026-08-14T02:13:07.412+0000",Severity="Error",Service="PJSIP",EventVersion="1",AccountID="1001",SessionID="0x7f2a4c0a3e60",LocalAddress="IPV4/UDP/203.0.113.20/5060",RemoteAddress="IPV4/UDP/192.0.2.99/5060"
InvalidAccountID, InvalidPassword, ChallengeResponseFailed, FailedACL and RequestBadFormat are the events that matter; SuccessfulAuth is useful for spotting logins from unexpected addresses.
; /etc/fail2ban/jail.d/asterisk.local
[asterisk]
enabled = true
filter = asterisk
logpath = /var/log/asterisk/security
maxretry = 5
findtime = 600
bantime = 86400
banaction = nftables-allports ; fail2ban 0.10 or newer; iptables-allports on older installs
ignoreip = 127.0.0.1/8 203.0.113.10 203.0.113.11 198.51.100.0/24
Test the shipped filter against your real log before trusting it, then watch the jail:
fail2ban-regex /var/log/asterisk/security /etc/fail2ban/filter.d/asterisk.conf
fail2ban-client status asterisk
Always put your carriers and your office in ignoreip. A carrier that sends one malformed OPTIONS per minute will otherwise ban itself and take every inbound call with it.
Layer 4: Dialplan Class of Service and Limits
Trunk contexts never include outbound contexts. Internal contexts include a chain of class-of-service contexts, and every outbound call goes through one subroutine that enforces the kill switch, the time-of-day rule for international, and a concurrency ceiling.
[from-trunk]
exten => _+1NXXNXXXXXX,1,Goto(inbound-did,${EXTEN:2},1)
exten => _X.,1,Goto(inbound-did,${EXTEN},1)
; no include => here, ever
[from-internal]
include => internal-extensions
include => cos-national
[cos-local]
exten => _NXXNXXXXXX,1,GoSub(outbound,dial,1(1${EXTEN}))
[cos-national]
include => cos-local
exten => _1NXXNXXXXXX,1,GoSub(outbound,dial,1(${EXTEN}))
[cos-international]
include => cos-national
exten => _011X.,1,GoSub(outbound,dial,1(${EXTEN}))
[outbound]
exten => dial,1,NoOp(Outbound ${ARG1} from ${CALLERID(num)} on ${CHANNEL})
same => n,GotoIf(${DB_EXISTS(fraud/lockdown)}?locked)
same => n,GotoIf($["${ARG1:0:3}" = "011"]?intl:go)
same => n(intl),GotoIfTime(08:00-19:00,mon-fri,*,*?go)
same => n,Log(WARNING,After-hours international attempt to ${ARG1} from ${CALLERID(num)})
same => n,Playback(ss-noservice)
same => n,Hangup()
same => n(go),Set(GROUP(outbound)=trunk)
same => n,GotoIf($[${GROUP_COUNT(trunk@outbound)} > 30]?congested)
same => n,Dial(PJSIP/${ARG1}@trunk-a,60)
same => n,Return()
same => n(congested),Congestion(5)
same => n(locked),Playback(ss-noservice)
same => n,Hangup()
Give the international context only to the endpoints that need it by setting context=cos-international on those endpoints, not by editing includes. database put fraud lockdown 1 from the CLI stops all outbound calls in one command during an incident; database del fraud lockdown restores service. Never pass t or T to Dial on calls that arrived from a trunk, and set TRANSFER_CONTEXT on internal channels to a context without international access.
Layer 5: TLS and SRTP
TLS protects credentials and signalling; SRTP protects the audio. Configure both on any phone that leaves the office network.
[transport-tls]
type=transport
protocol=tls
bind=0.0.0.0:5061
cert_file=/etc/asterisk/keys/pbx.example.com.crt
priv_key_file=/etc/asterisk/keys/pbx.example.com.key
ca_list_file=/etc/ssl/certs/ca-certificates.crt
method=tlsv1_2
verify_client=no
verify_server=yes
external_signaling_address=203.0.113.20
external_media_address=203.0.113.20
local_net=10.0.0.0/8
local_net=198.51.100.0/24
allow_reload=yes
[remote-phone](!)
type=endpoint
transport=transport-tls
media_encryption=sdes
media_encryption_optimistic=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
direct_media=no
Use a certificate from a public CA for the hostname the phones dial, because most handsets validate the chain and few let you install a private root. Keep the key readable only by the asterisk user. verify_client=no is normal for phones, which rarely hold client certificates; verify_server=yes matters for outbound TLS trunks so a DNS hijack cannot redirect your registration. Check the listener with pjsip show transports and openssl s_client -connect pbx.example.com:5061. media_encryption=sdes with optimistic=no refuses calls that do not negotiate SRTP, which is what you want for remote phones and what you do not want for a carrier that only does plain RTP; keep separate templates.
Layer 6: Firewalling SIP and RTP
The RTP range comes from rtp.conf (rtpstart and rtpend) and must match the firewall exactly. Plain SIP on 5060 is reachable only from carriers and known sites; TLS on 5061 is open but rate limited; AMI and ARI are bound to loopback and blocked anyway.
table inet voip {
set trunks { type ipv4_addr; elements = { 203.0.113.10, 203.0.113.11 } }
set office { type ipv4_addr; flags interval; elements = { 198.51.100.0/24 } }
set tls_new { type ipv4_addr; flags dynamic; timeout 1m; }
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
ip saddr @trunks udp dport 5060 accept
ip saddr @trunks tcp dport 5060 accept
ip saddr @office udp dport 5060 accept
tcp dport 5061 ct state new add @tls_new { ip saddr limit rate over 10/minute burst 20 packets } drop
tcp dport 5061 accept
udp dport 10000-20000 accept
ip saddr @office tcp dport 22 accept
}
}
Pair it with bindaddr=127.0.0.1 in both manager.conf and http.conf, and front ARI's WebSocket with a reverse proxy that terminates TLS and authenticates. Run the daemon as an unprivileged user (runuser=asterisk, rungroup=asterisk in asterisk.conf) and leave live_dangerously=no, which blocks dangerous dialplan functions such as SHELL() and configuration-file access from being reached through AMI Setvar/Getvar or realtime-supplied dialplan. System() through AMI Originate is gated separately by the system write class, so do not grant it to integration users.
Detection and Response
- Concurrency:
asterisk -rx "core show channels count"from a one-minute cron, alerting above the normal ceiling for the hour. - Destinations: a CDR query counting international
ANSWEREDcalls per hour, alerting on any after-hours count above zero and on daytime counts far above the weekly median. - Registrations:
pjsip show contactscompared with the expected address list; a desk phone registered from another country is a stolen credential. - Carrier caps: a daily spend limit and a blocked-destination list at the provider, which costs nothing and stops fraud you failed to detect.
When fraud is confirmed: database put fraud lockdown 1, then channel request hangup all, then rotate the credential that was used (the security log's AccountID and RemoteAddress tell you which), check voicemail.conf mailboxes and any call-forwarding entries your dialplan keeps in AstDB (database show) for targets the attacker set, and only then reopen outbound with international still locked until the carrier confirms the bleeding stopped.
Checklist
- No trunk context includes any outbound context
endpoint_identifier_orderstarts withip; the anonymous identifier is not loaded- Every SIP secret is at least 20 random characters and differs from the extension
securitylog routed to its own file; fail2ban jail active and tested withfail2ban-regex; carriers and office inignoreip- International access granted per endpoint via context, gated by time of day, behind a database kill switch
GROUP_COUNTceiling on concurrent outbound calls- AMI and HTTP bound to loopback; ARI behind an authenticating proxy
- Remote phones on TLS with mandatory SRTP; certificate from a public CA and renewed automatically
- Firewall RTP range identical to
rtp.conf; plain 5060 limited to known addresses - Voicemail: no
dialout, nocallback, no PIN equal to the mailbox number - Carrier-side spend cap and destination blocks in place
Common Mistakes
- Treating a non-standard SIP port as a control. It reduces scanner noise for a while and stops nothing targeted.
- Banning on the
fulllog. Verbose dialplan output matches badly and the file is huge; use thesecuritylog. - Forgetting
ignoreipand banning the carrier during a malformed-OPTIONS episode. - One
aclfor everything, so the first roaming user forces you to remove it entirely rather than design for two classes of endpoint. media_encryption_optimistic=yesas a default, which silently falls back to plain RTP and hides misconfigured phones.- Opening the whole UDP range because the RTP range was changed in
rtp.confbut not in the firewall, then leaving it open. - Running
make installas root and never creating theasteriskuser, so a dialplan or AGI bug is a root compromise. - Alerting only on cost, which arrives from the carrier days later; alert on call counts and destinations in real time.
Limits and When Not to Use This
These controls harden a single Asterisk host. A provider platform exposed to thousands of unknown customers needs a session border controller or a Kamailio or OpenSIPS front end that absorbs scanning, rate limits per source and topology-hides the media servers; Asterisk should not be the first thing on the internet at that scale. Fraud detection based on CDR counts is a coarse instrument; a real anti-fraud service scores destinations and velocities you cannot maintain by hand. And no PBX configuration protects against a phone whose admin password is still the factory default, so provisioning security is a separate project that this skill assumes is done.
Install this skill directly: skilldb add asterisk-pbx-skills
Related Skills
Call Queues and Agents
Activate this skill when the user is configuring queues.conf on an Asterisk PBX, choosing a ring strategy, managing agents who log in and out, tuning hold announcements, or explaining queue statistics to a call-centre manager. Triggers on "queues.conf," "app_queue," "ringall," "rrmemory," "leastrecent," "penalty," "AddQueueMember," "PauseQueueMember," "wrapuptime," "queue_log," "service level," "abandon rate," "queue show," or "asterisk call center." Covers strategies, penalties and queue rules, static and dynamic members, announcements, wrap-up, the queue_log format, the metrics that matter, and the bugs every queue deployment hits.
CDR, CEL and Reporting
Activate this skill when the user needs billing-grade call records from an Asterisk PBX: configuring cdr.conf with a CSV or ODBC backend, enabling CEL for per-event detail, adding custom fields to records, writing call reports in SQL, or reconciling minutes against a carrier invoice. Triggers on "CDR," "cdr.conf," "Master.csv," "cdr_adaptive_odbc," "CEL," "cel.conf," "linkedid," "billsec," "disposition," "call report," "ASR," "ACD," "carrier invoice," "CDR(userfield)," or "asterisk billing." Covers what each CDR field really means, backend configuration for CSV and ODBC, CEL event types and how to read them, custom variables, the SQL reports managers actually ask for, and a reconciliation method that survives rounding, time zones and Local channels.
Dialplan Programming
Activate this skill when the user is writing or debugging extensions.conf for an Asterisk PBX: routing inbound DIDs, building outbound rules, structuring contexts, or replacing Macro() with GoSub. Triggers on "asterisk dialplan," "extensions.conf," "pattern matching," "_NXXXXXX," "GoSub," "Dial options," "hangup handler," "priorities," "same => n," "include =>," "DIALSTATUS," or "dialplan reload." Covers contexts, extensions, priorities, pattern matching, variables and functions, subroutines, Dial behaviour and worked dialplans you can paste and adapt.
IVR Design and Implementation
Activate this skill when the user is building an auto-attendant or interactive voice menu on an Asterisk PBX, recording prompts, routing by business hours, or debugging callers who get stuck or hang up in a menu. Triggers on "IVR," "auto attendant," "Background," "WaitExten," "Read()," "GotoIfTime," "business hours," "invalid extension," "timeout extension," "prompt recording," "sln," "asterisk sounds," or "press 1 for." Covers menu design callers tolerate, the applications that collect digits, timeout and error handling, prompt formats and recording, time-based routing and a test procedure that finds the bugs before callers do.
PJSIP Configuration
Activate this skill when the user is configuring pjsip.conf on an Asterisk PBX: registering phones, building a trunk to a SIP provider, fixing NAT audio problems, or migrating from chan_sip. Triggers on "pjsip.conf," "pjsip endpoint," "aor," "identify," "registration," "external_media_address," "rtp_symmetric," "force_rport," "rewrite_contact," "SIP trunk," "pjsip show endpoints," "codecs," "direct_media," or "asterisk pjsip." Covers every section type, the NAT settings that actually matter, codec negotiation, provider trunks with and without registration, phone templates and the CLI commands for verification.
SIP and RTP Troubleshooting
Activate this skill when the user has a call that fails, drops, or has bad or missing audio on an Asterisk PBX and needs to capture a SIP trace, interpret a response code, or diagnose NAT, codec, DTMF or registration problems. Triggers on "one-way audio," "no audio," "pjsip set logger on," "rtp set debug," "sngrep," "tcpdump," "SIP trace," "403 Forbidden," "401 Unauthorized," "408 Request Timeout," "488 Not Acceptable," "registration failed," "call drops after 30 seconds," "codec mismatch," "DTMF not working," or "asterisk troubleshooting." Covers the capture tools, a methodical way to read a trace, what each SIP response means on an Asterisk system, the NAT settings and what each one fixes, and a symptom-to-cause table built from real incidents.