On a SIP trunk, security is not a deployment option, it is a contractual responsibility
Toll fraud, STIR/SHAKEN, mTLS, SBC hardening. What protects a SIP trunk in 2026, layer by layer, with the configurations and the checklist.
Telecom fraud accounted for $38.95 billion in losses in 2023 according to the CFCA, or 2.5% of the industry's revenue. In 2026, attacks on voice infrastructure remain an operational risk: DDoS aimed at registrars, brute-force against SIP credentials, exploitation of SBC misconfigurations. For an integrator deploying voice at customer sites, this stops being an option and gets written into the contract.
Here are the measures, layer by layer, from transport termination through to monitoring.
Three vectors, and the first one is discovered on the invoice
Toll fraud is the most expensive. An attacker compromises a SIP account or exploits a loosely closed trunk to route premium international calls, and the customer finds out on the monthly invoice, usually several thousand euros too late.
Registration hijacking goes after redirection. The attacker intercepts or forges REGISTER requests to send inbound calls to their own endpoints, and without mutual authentication a simple network sniff is enough.
SIP DDoS, finally, saturates. Massive volumes of INVITE or OPTIONS requests drown the SBC or the proxy, and the usual web mitigations (CDN, WAF) do not protect SIP traffic over UDP.
In 2026, a SIP trunk in clear UDP is professional negligence
Transport encryption is the first line.
# Replayed: kamailio -c -f, version 5.7.4, output "config file ok".
# enable_tls comes BEFORE the module is loaded: the other way around, tls.so
# loads while announcing that TLS is disabled. Without both, the tls: socket
# is refused at startup and the configuration below does nothing.
enable_tls=1
loadmodule "tls.so"
listen=tls:203.0.113.20:5061 # the signalling interface, not 0.0.0.0
modparam("tls", "tls_method", "TLSv1.2+")
modparam("tls", "verify_certificate", 1)
modparam("tls", "require_certificate", 1)
modparam("tls", "private_key", "/etc/kamailio/tls/server.key")
modparam("tls", "certificate", "/etc/kamailio/tls/server.crt")
modparam("tls", "ca_list", "/etc/kamailio/tls/ca.pem")
The parameter is called tls_method, not method. The intuitive spelling shows up in plenty of examples doing the rounds, and Kamailio refuses it at startup: parameter <method> ... not found in module <tls>, exit 255. A trunk configuration that does not start gets noticed immediately. It is the one that starts while leaving a hole open that costs money, which is why the three points below matter.
TLS 1.2 as a minimum, because 1.0 and 1.1 are obsolete and vulnerable. Everyone does that one.
mTLS is the highest level available on a trunk, and it is where most people stop too early. Plain TLS only authenticates the server: your SBC proves its identity, the peer proves nothing. require_certificate set to 1 reverses that and requires the client to present its certificate too. The difference is stark: without mTLS, anyone can open a TLS session towards your SBC and attack the layer above it while encrypted, which means invisible to network inspection.
With one caveat that cancels the whole benefit if you miss it. verify_certificate validates the chain against the ca_list, it does not check that the certificate presented belongs to the expected peer. Point that ca_list at a bundle of public authorities and any valid certificate in the world clears the handshake, while you believe you have authenticated your carrier. A trunk's ca_list is a dedicated private CA, with one certificate per peer. Identity control happens above it, through an IP ACL or a match on the certificate subject.
And SRTP for media, because TLS protects signalling while the RTP streams travel on a separate channel.
# Illustrative: not replayed on an SBC. AudioCodes parameter names change
# between major versions, so read them again in the docs for yours.
SRTPMode: RequireSRTP
SRTPOfferedSuites: AES_CM_128_HMAC_SHA1_80
DTLSMode: Enabled
Digest authentication is not enough
Weak or default credentials remain the first vector. My rule on engagements: 32 characters minimum, generated automatically and never chosen by hand, with a quarterly rotation automated through the provider's API or the provisioning script. Length matters less than generation: a password a human picked stays guessable at 32 characters.
IP ACLs come on top, because digest alone lets through whoever knows the secret.
# Replayed: the six rules insert, in this order, on iptables 1.8.
# Allow only TLS, from known peers. Clear-text 5060 is closed on both
# transports. Forgetting tcp/5060, which Kamailio listens on by default,
# leaves an ACL you get around by simply changing transport.
iptables -A INPUT -p tcp --dport 5061 -s 203.0.113.10 -j ACCEPT
iptables -A INPUT -p udp --dport 5060 -j DROP
iptables -A INPUT -p tcp --dport 5060 -j DROP
iptables -A INPUT -p tcp --dport 5061 -j DROP
# The media plane, to align with the RTP range actually configured on the SBC.
iptables -A INPUT -p udp --dport 10000:20000 -s 203.0.113.10 -j ACCEPT
iptables -A INPUT -p udp --dport 10000:20000 -j DROP
Three caveats on this example. -A appends to the end of the chain, so on a host already carrying a catch-all REJECT (firewalld, a default RHEL policy) the lines added here may never be reached, the ACCEPTs included, which cuts the trunk instead of protecting it: check the real chain with iptables -L INPUT -n --line-numbers rather than trusting the order you wrote them in. Nothing is persisted, so this control disappears at reboot unless you put it in your configuration tool. And every rule needs its ip6tables twin: an IPv4-only ACL is bypassed exactly like the forgotten tcp/5060 if the SBC also listens on v6.
Rate limiting on the SBC
This is the most effective measure against brute-force and scanning. A properly tuned SBC caps the number of requests per source per second.
# Replayed: kamailio -c -f, version 5.7.4, output "config file ok".
# xlog.so is loaded explicitly: without it the block fails at startup on
# "failed to find command xlog", while the rate-limiting rule itself is
# correct. A missing module only announces itself at the next restart.
loadmodule "pike.so"
loadmodule "xlog.so"
modparam("pike", "sampling_time_unit", 2)
modparam("pike", "reqs_density_per_unit", 30)
modparam("pike", "remove_latency", 4)
route[REQINIT] {
if (!pike_check_req()) {
xlog("L_WARN", "Blocked SIP flood from $si\n");
exit;
}
}
With those values, a source sending more than 30 requests per 2-second window is blocked for 4 seconds. Tune the thresholds to your infrastructure's legitimate volume, or you will block your own traffic at peak hours.
STIR/SHAKEN is not your project, it is the carrier's
This needs saying plainly, because a lot of content presents it as integrator work. STIR/SHAKEN cryptographically signs the caller's identity to limit number spoofing, and it is the carriers who hold the certificates, do the signing and carry the regulatory obligation. Mandatory in the United States for the voice providers concerned, in Europe it is handled as a standard to know about in cross-border architectures, not as a uniform obligation.
What concerns you fits in two lines. On inbound calls, the attestation level the carrier passes says what it actually verified: at A, the caller's identity and their right to use the number; at B, the identity alone; at C, nothing beyond the fact that the call came from a gateway. That is information to use in routing, not a component to deploy. And the commercial corollary: do not sell it as a deliverable, you do not control it.
Hardening the SBC: the checklist
The SBC is the termination point exposed to the internet, so that is where hardening pays.
| Measure | Priority | Impact |
|---|---|---|
| Disable unused protocols (H.323, MGCP) | High | Reduces the attack surface |
| Restrict allowed codecs | Medium | Prevents exploitation through exotic codecs |
| Enable SIP anomaly detection | High | Blocks malformed requests |
| Log every authentication attempt | High | Detects scanning and brute-force |
| Separate management and media interfaces | High | Prevents admin access from the internet |
| Update firmware regularly | High | Fixes known CVEs |
| Alarm on the INVITE failure rate | Medium | Early fraud detection |
| Restrict international destinations | High | Caps the impact of toll fraud |
The measure that pays fastest: block the prefixes nobody uses
Against toll fraud, this is what has an immediate effect. It is also where the examples doing the rounds pick the wrong table.
On an AudioCodes, blocking a destination belongs in the IP-to-IP Routing table, with the rule placed ahead of the normal routing rules. IPOutboundManipulation cannot reject a call: it rewrites numbers, it does not decide their fate. I have seen more than one snippet give it an Action: Reject field, which does not exist. That kind of configuration imports without a visible error and blocks nothing.
What goes in the list deserves the same attention as its syntax. A prefix unassigned in the E.164 plan routes nowhere, so blocking it protects you from nothing. And a country code is not a tariff: 976 is Mongolia, an entire country, not a premium-rate service. What you usefully block are the real revenue-share ranges, like North American 1900 or French 899, plus the destinations the customer never calls and whose termination rate is high. That second list comes from their own call history, not from an article.
Live CDR analysis, and the cut before the invoice
This is the measure that catches everything prevention let through, and the one I put in place first after hardening.
One clarification first, because it changes how to read what follows: this mechanism is designed and documented, it has not yet cut traffic in production. What you are reading is a design, not a field report.
The principle is a one-hour sliding window. CDRs are analysed as they arrive, not at month end, and the rule that fires is not raw volume but volume towards a destination that is unusual for this customer. A customer who calls the Maghreb every day has nothing abnormal about thirty calls there in an hour. The same volume towards a destination that appears nowhere in their history is fraud in progress, not a hypothesis.
What follows detection is the real decision: the cut is preventive, and the end customer is told. Cut first, explain after. It is uncomfortable, because you are interrupting traffic on the strength of a statistical pattern, and you will get false positives. But the alternative has a price: one uninterrupted night of toll fraud costs more than the half-day of explaining that follows an unjustified cut.
What you never cut, and it has to be written before you code anything. The cut targets the suspicious destination class, like the prefix blocking described above, never the whole trunk and never the calling extension.
Emergency numbers are exempted by name, through an allow rule placed above the anti-fraud rule. By name, because relying on the blocking rule not matching them is a bet, whereas an explicit allow can be verified.
With two caveats, and the first one cost me time. Whether the rule placed above actually wins depends on the SBC family: table evaluation order is not the same everywhere, and some platforms apply a manipulation before a routing decision. Test the exemption on your platform, with a real call to an emergency number while the anti-fraud rule is armed. Do not infer it from where the line sits in the table.
The second caveat is the list itself, which depends on the country. Every national numbering plan has its own, and a multi-country customer has as many as they have locations. For France, Arcep makes it an enforceable list of twelve numbers routed free of charge: 112, 15, 17, 18, 114, 115, 119, 116000, 116117, 191, 196, 197. The first six are the ones everyone thinks of. 119 for children at risk, 196 for maritime emergencies and 197 for terrorist and abduction alerts are no less real, and for a school or coastal customer they are not optional. An anti-fraud automaton that deprives a customer of any of them is not an SLA problem, it is a threat to people's safety.
Three conditions go with it, and they get settled in the contract, not in the code. The alert reaches the customer at the same time as the cut, never after: a silent cut destroys trust far more reliably than the fraud does. The mandate to cut is written down, because interrupting a customer's production without an enforceable clause is a contractual fault even when the detection is right. And the path back to service is defined, with a reachable on-call, or the Friday-evening false positive lasts until Monday.
That leaves the cold start. A new customer has no history, so every destination is unusual and the first weeks produce nothing but false positives. You need a learning period during which the rule alerts without cutting.
Two more axes complete the setup. Fail2ban automatically blocks IPs after N failed authentication attempts. And a centralised syslog aggregates the logs from every SBC and proxy onto a single platform, Grafana and Loki or ELK.
# Illustrative: not replayed. The `kamailio-auth` filter referenced here is
# yours to write, fail2ban does not ship one for Kamailio.
[kamailio-auth]
enabled = true
filter = kamailio-auth
action = iptables-allports[name=kamailio, protocol=all]
logpath = /var/log/kamailio/kamailio.log
maxretry = 5
bantime = 3600
findtime = 300
What this changes commercially
No single measure is enough on its own, and that is the point: transport encryption, strong authentication, regulatory compliance per country and route, SBC hardening and monitoring all hold each other up.
A customer who suffers SIP fraud does not renew. A customer whose infrastructure is audited, hardened and watched talks about it.
A SIP trunk to harden or audit, on the integrator, carrier, or B2B side? Get in touch.
Sources
- CFCA, "Telecommunications fraud increased 12% in 2023 equating to an estimated $38.95 billion lost to fraud". Verified 2026-08-04: the page resolves, the title matches, and it puts the loss at 2.5% of telecom revenue.
- eCFR, 47 CFR § 64.6301, Caller ID authentication, for the U.S. STIR/SHAKEN regulatory framework.
- Arcep, decision 2022-2372 amending decision 02-1179, for the list of emergency numbers routed free of charge in France.
- The blocks marked "replayed" were run through
kamailio -c -fon 5.7.4 and throughiptables1.8, in an Ubuntu 24.04 container. The ones marked "illustrative" were not, for want of the equipment, and they say so where you would copy them.
Field note by qaryon
Nicolas Marxer
Enterprise communications solution architect focused on operator, integrator, and B2B deployments.
Need a field view on your communications architecture?
Audit, scoping, or deployment: I work directly on UCaaS, CCaaS, IPBX, SBC, automation, and applied AI.
Discuss a telecom projectRelated reading
Isolating a VoIP datacenter in broad daylight, without dropping a call
Working on a datacenter during business hours without cutting a customer. The drain runs through DNS, not the SBC: pull the IP, let sessions finish, restore by mirroring. The real trap is the TTL.
In SIP, what's negotiated isn't what's supported
Three trunks that refused to place a call between two vendors. Each time, a peer that signals one thing and does another. The SBC absorbs it, the trace settles it.
I don't automate the decision, I automate the fact that it expires
A CVE lands on a component you run. The faulty dependency was fixed upstream weeks ago, but the image publisher never rebuilt. You can't patch, you can't ship, and you can't forget. What happens next doesn't delegate to a model, and it's the best explanation I know of what AI cannot do.