Does The Spam Ever Stop?

random_nerd

New Member
Sep 30, 2025
3
0
1
48
Hi all. I'm new to freeswitch. I have some basic setup in place, including using mod_signalwire, mod_callcenter, routing calls with lua, connecting vagents via secure web sockets, etc. Overall I THINK everything is in a pretty good place...

HOWEVER, when I watch logs roll through in fscli, I see hundreds of malicious invites/sip connection attempts from random ip addresses around the World.

Nothing has gotten through (it's all just brute force/dialplans are set to auto hang up), but it does create quite a bit of clutter... is there some form of best practice out there that would lock things down a bit more?

PS. I considered locking the signaling ports to only allow incoming requests from signalwire servers, however their IP addresses don't seem to be publicly available, and they say they often rotate/check domain instead.

Thank You!
 
One thing you can do that will have a big impact immediately is to change your SIP ports to more obscure numbers. This makes it harder for SIP scanners to access you. You can also block unwanted traffic using tools like SBC, SIP proxy, or Fail2Ban.
 
Just a PS to that.....

If I spin up a new server and it doesn't get hit within 20 minutes I start to check if everything is working correctly :)

A few years ago I did some testing with honeypot type of thing. I had about 13 random VPS around the world. They were effectively invisible from the sip side of things in that they did not respond at all to sip packets. They just grabbed them and logged them without reply.

The hackers infrastructure is huge.

I have seen EXACTLY the same invite hit 6 of those honeypots in the space of 5 seconds, I couldn't quite believe my eyes but it was true.
 
Hi all. I'm new to freeswitch. I have some basic setup in place, including using mod_signalwire, mod_callcenter, routing calls with lua, connecting vagents via secure web sockets, etc. Overall I THINK everything is in a pretty good place...

HOWEVER, when I watch logs roll through in fscli, I see hundreds of malicious invites/sip connection attempts from random ip addresses around the World.

Nothing has gotten through (it's all just brute force/dialplans are set to auto hang up), but it does create quite a bit of clutter... is there some form of best practice out there that would lock things down a bit more?

PS. I considered locking the signaling ports to only allow incoming requests from signalwire servers, however their IP addresses don't seem to be publicly available, and they say they often rotate/check domain instead.

Thank You!
I solved this by using Telnyx for SIP trunking. They publish a small set of static IP addresses for both SIP signaling and RTP media (which is uncommon). That lets you lock down your PBX to accept traffic only from Telnyx’s IPs plus your phones’ static IPs. As long as your phones are on static IPs you can allowlist on the PBX, and you set your PBX admin dashboard to only be accessible from a static IP, you have no ports on the PBX accessible from the open internet. The logs are glorious.
 
Last edited:
  • Like
Reactions: random_nerd
Back in the day I tried all sorts to 'close' my pbx to the world, some of which were more successful than others.

One I had was were I ran a kamailio instance on a totally different ip, probably a small vps somewhere.

The main pbx had its ports 5060, 5080 etc closed to the world.

I had the carriers whitelisted.

On the ip phones I had a separate line configured that registered to the kamailio. When the kamailio received a valid registration it would then whitelist the source ip of the phone in iptables on the main pbx, the phone would suddenly appear registered on the main pbx now it was allowed in by iptables.

That worked quite well. It was much more sophisticated in that it would remove the whitelisting etc after a certain amount of time.
 
Back in the day I tried all sorts to 'close' my pbx to the world, some of which were more successful than others.

One I had was were I ran a kamailio instance on a totally different ip, probably a small vps somewhere.

The main pbx had its ports 5060, 5080 etc closed to the world.

I had the carriers whitelisted.

On the ip phones I had a separate line configured that registered to the kamailio. When the kamailio received a valid registration it would then whitelist the source ip of the phone in iptables on the main pbx, the phone would suddenly appear registered on the main pbx now it was allowed in by iptables.

That worked quite well. It was much more sophisticated in that it would remove the whitelisting etc after a certain amount of time.
That's impressive @DigitalDaz . You seem to refer to this in past tense - do you still carry it out this way? And if not, why not?
 
  • Like
Reactions: random_nerd
Another option is to lock down the PBX and whitelist the providers, and basic IP addresses for the building where the phones are. Then implement an OpenVPN server and use it for remote and roving workers. This makes the PBX almost invisible on the internet and eliminates the continual probes and break-in attempts.
 
Another option is to lock down the PBX and whitelist the providers, and basic IP addresses for the building where the phones are. Then implement an OpenVPN server and use it for remote and roving workers. This makes the PBX almost invisible on the internet and eliminates the continual probes and break-in attempts.
How about running the OpenVPN server on its own separate VPS, and using it essentially as a proxy server, so that connecting phones get that server's WAN IP. Then whitelist that WAN IP in the PBX. This allows the PBX to still be 100% locked down as far as external IPs and ports and avoids having to run OpenVPN on the same server the PBX runs on - as whatever server OpenVPN runs on would still need the OpenVPN ports to be open to the world to allow for phones at locations with a non static IP.
 
  • Like
Reactions: random_nerd
Another thing I thought of doing and this was only a thought, I never implemented it......

This is not hiding the pbx, this is more an additional auth factor for a phone....

So, on most sip phones, grandstream excluded, we have a registration expiry time that we can usually set in seconds.

In the sip registration this sets the expire time. In FusionPBX, by default we override this and set it to 120.

It did occur to me that we could perhaps use it as a 'pin' that we then checked. eg set the expires to 123456, we then check when registering that the expires matches that extensions pin, if it does we register and and then override the expires in the usual way in the 200OK. If it doesn't match, we reject and block.
 
Amazing insight everyone! And amazing names too, shoutout @pbxnerd LOL. I'm currently trunking via SignalWire, I considered Telnyx but I have seen them go down FAR too many times (and for too long, when they do). For context, we're dealing with thousands of high value calls a day so, we literally can't afford downtime. I'm likely add Twilio/others for redundancy.

It might not (definitely probably not) be best practice, but so far I have been able to filter out MOST inbound attempts in a dialplan

XML:
<context name="default">

        <extension name="block_scanners">
  <condition field="${sip_user_agent}" expression="(friendly-scanner|sipcli|sipsak|sipvicious|sip-scan|sundayddr|iWar|CSipSimple|sipcli|Asterisk PBX|sipv|VaxIPUserAgent|VaxSIPUserAgent)">
    <action application="log" data="ALERT Blocked scanner: ${sip_user_agent} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_sipvicious_callerid">
  <condition field="${caller_id_name}" expression="^(sipvicious|friendly-scanner|sipcli)$">
    <action application="log" data="ALERT Blocked scanner in caller ID from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_sequential_extensions">
  <condition field="${destination_number}" expression="^(100|101|102|103|1000|1001|1002)$"/>
  <condition field="${sip_authorized}" expression="^false$">
    <action application="log" data="ALERT Blocked extension probe: ${destination_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden - Invalid Extension"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>


        <extension name="block_spoofed_callid">
      <condition field="${sip_call_id}" expression="@0\.0\.0\.0$">
        <action application="log" data="ALERT Blocked spoofed call-id from ${network_addr}"/>
        <action application="respond" data="403 Forbidden - Invalid Call-ID"/>
        <action application="hangup" data="CALL_REJECTED"/>
      </condition>
    </extension>

    <extension name="block_spoofed_from">
      <condition field="${sip_from_host}" expression="^0\.0\.0\.0$">
        <action application="log" data="ALERT Blocked spoofed from-host from ${network_addr}"/>
        <action application="respond" data="403 Forbidden - Invalid From"/>
        <action application="hangup" data="CALL_REJECTED"/>
      </condition>
    </extension>

    <extension name="block_default_user">
      <condition field="${sip_from_user}" expression="^default$">
        <action application="log" data="ALERT Blocked 'default' username from ${network_addr}"/>
        <action application="respond" data="403 Forbidden - Invalid User"/>
        <action application="hangup" data="CALL_REJECTED"/>
      </condition>
    </extension>


        <extension name="block_international_prefix">
  <condition field="${destination_number}" expression="^00">
    <action application="log" data="ALERT Blocked international prefix attempt: ${destination_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>
<!-- Block numeric-only caller IDs (scanners) -->
<extension name="block_numeric_callerid">
  <condition field="${caller_id_name}" expression="^\d{4,6}$">
    <action application="log" data="ALERT Blocked numeric scanner caller ID: ${caller_id_name} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_invalid_destination_length">
  <!-- Block destinations longer than 11 digits (US E.164 max) -->
  <condition field="${destination_number}" expression="^\+?\d{12,}$">
    <action application="log" data="ALERT Blocked oversized destination: ${destination_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_long_callerid">
  <!-- Block caller IDs longer than 15 digits (E.164 max) -->
  <condition field="${caller_id_number}" expression="^\+?\d{16,}$">
    <action application="log" data="ALERT Blocked oversized caller ID: ${caller_id_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>
...

But still, some calls squeak through and appear logged in my postgres database like this one

JSON:
{"id":1628,"uuid":"cef5ed7d-8b8f-48bb-abb7-7d6e3b606d22","caller_id_name":"Ss0599654484##","caller_id_number":"07","destination_number":"14502390262","context":"default","start_stamp":"2025-10-31T11:41:46.947169","answer_stamp":null,"end_stamp":"2025-10-31T11:41:55.687582","duration":9,"billsec":0,"hangup_cause":"ORIGINATOR_CANCEL","queue_name":"","created_at":"2025-10-31T11:41:57.35141","direction":"inbound","connected_agent_id":"","recording_s3_path":"...14502390262-cef5ed7d-8b8f-48bb-abb7-7d6e3b606d22.wav"}

Never hit queue but... yeah I'm assuming there's a better way rather than chasing regex formats... any input would be appreciated.

For what it's worth, I'm I'd be willing to buy beers in exchange for 1-1 FreeSwitch advice with my setup so, if anyone wants to connect with a random nerd from Florida, DM me
 
I like your spam blocking dialplan but when you are spammed it still increases the load on your FreeSWITCH. There is no simple single solution and what works for one situation may not be so good in another.

The two simple rules that I follow are:
1. Have a good firewall, (I have grown to prefer nftables over iptables) and whitelist your carriers, port 5080 is fine if only your carriers can access it.
2. Do not use 5060 for your customer connections. If you provision your customers phones, this is easy to change.

In addition to the simple rules above, in the default DjangoPBX we only allow a maximum of 20 packets per hour to the SIP signalling port from any given IP address. If during this 20 packet allowance a device successfully registers, then an event is generated to whitelist the devices IP address in the firewall, thus bypassing the 20 packet rule. The devices IP address and registration time is added to a database table. During subsequent refreshes of that registration, the timestamp against the IP address is updated. IP addresses with a timestamp more then x days old are automatically removed from the whitelist.

Some of these SIP probing outfits have huge subnets available to them, so as soon as one IP address gets blocked they switch to another one. I implement several "block lists" in nftables so it is easy to simply put a temporary or permanent block on a whole subnet from both the GUI and the command line.

I believe it is always better to let the firewall do the hard work rather than FreeSWITCH.
 
I like your spam blocking dialplan but when you are spammed it still increases the load on your FreeSWITCH. There is no simple single solution and what works for one situation may not be so good in another.

The two simple rules that I follow are:
1. Have a good firewall, (I have grown to prefer nftables over iptables) and whitelist your carriers, port 5080 is fine if only your carriers can access it.
2. Do not use 5060 for your customer connections. If you provision your customers phones, this is easy to change.

In addition to the simple rules above, in the default DjangoPBX we only allow a maximum of 20 packets per hour to the SIP signalling port from any given IP address. If during this 20 packet allowance a device successfully registers, then an event is generated to whitelist the devices IP address in the firewall, thus bypassing the 20 packet rule. The devices IP address and registration time is added to a database table. During subsequent refreshes of that registration, the timestamp against the IP address is updated. IP addresses with a timestamp more then x days old are automatically removed from the whitelist.

Some of these SIP probing outfits have huge subnets available to them, so as soon as one IP address gets blocked they switch to another one. I implement several "block lists" in nftables so it is easy to simply put a temporary or permanent block on a whole subnet from both the GUI and the command line.

I believe it is always better to let the firewall do the hard work rather than FreeSWITCH.
The problem (I think) that I have is the main provider I use (SignalWire) doesn't provide ip addresses/port ranges to whitelist, so I have to open it up. Also, this is part of a larger CRM, so agents are connecting to FS from their home machines, so IP addresses to deliver inbound calls to/handle outbound call requests always rotate. I guess I could build a wrap a VPN into the CRM to fix the IP address but... could be a lot of work lol.
 
Amazing insight everyone! And amazing names too, shoutout @pbxnerd LOL. I'm currently trunking via SignalWire, I considered Telnyx but I have seen them go down FAR too many times (and for too long, when they do). For context, we're dealing with thousands of high value calls a day so, we literally can't afford downtime. I'm likely add Twilio/others for redundancy.

It might not (definitely probably not) be best practice, but so far I have been able to filter out MOST inbound attempts in a dialplan

XML:
<context name="default">

        <extension name="block_scanners">
  <condition field="${sip_user_agent}" expression="(friendly-scanner|sipcli|sipsak|sipvicious|sip-scan|sundayddr|iWar|CSipSimple|sipcli|Asterisk PBX|sipv|VaxIPUserAgent|VaxSIPUserAgent)">
    <action application="log" data="ALERT Blocked scanner: ${sip_user_agent} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_sipvicious_callerid">
  <condition field="${caller_id_name}" expression="^(sipvicious|friendly-scanner|sipcli)$">
    <action application="log" data="ALERT Blocked scanner in caller ID from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_sequential_extensions">
  <condition field="${destination_number}" expression="^(100|101|102|103|1000|1001|1002)$"/>
  <condition field="${sip_authorized}" expression="^false$">
    <action application="log" data="ALERT Blocked extension probe: ${destination_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden - Invalid Extension"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>


        <extension name="block_spoofed_callid">
      <condition field="${sip_call_id}" expression="@0\.0\.0\.0$">
        <action application="log" data="ALERT Blocked spoofed call-id from ${network_addr}"/>
        <action application="respond" data="403 Forbidden - Invalid Call-ID"/>
        <action application="hangup" data="CALL_REJECTED"/>
      </condition>
    </extension>

    <extension name="block_spoofed_from">
      <condition field="${sip_from_host}" expression="^0\.0\.0\.0$">
        <action application="log" data="ALERT Blocked spoofed from-host from ${network_addr}"/>
        <action application="respond" data="403 Forbidden - Invalid From"/>
        <action application="hangup" data="CALL_REJECTED"/>
      </condition>
    </extension>

    <extension name="block_default_user">
      <condition field="${sip_from_user}" expression="^default$">
        <action application="log" data="ALERT Blocked 'default' username from ${network_addr}"/>
        <action application="respond" data="403 Forbidden - Invalid User"/>
        <action application="hangup" data="CALL_REJECTED"/>
      </condition>
    </extension>


        <extension name="block_international_prefix">
  <condition field="${destination_number}" expression="^00">
    <action application="log" data="ALERT Blocked international prefix attempt: ${destination_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>
<!-- Block numeric-only caller IDs (scanners) -->
<extension name="block_numeric_callerid">
  <condition field="${caller_id_name}" expression="^\d{4,6}$">
    <action application="log" data="ALERT Blocked numeric scanner caller ID: ${caller_id_name} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_invalid_destination_length">
  <!-- Block destinations longer than 11 digits (US E.164 max) -->
  <condition field="${destination_number}" expression="^\+?\d{12,}$">
    <action application="log" data="ALERT Blocked oversized destination: ${destination_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>

<extension name="block_long_callerid">
  <!-- Block caller IDs longer than 15 digits (E.164 max) -->
  <condition field="${caller_id_number}" expression="^\+?\d{16,}$">
    <action application="log" data="ALERT Blocked oversized caller ID: ${caller_id_number} from ${network_addr}"/>
    <action application="respond" data="403 Forbidden"/>
    <action application="hangup" data="CALL_REJECTED"/>
  </condition>
</extension>
...

But still, some calls squeak through and appear logged in my postgres database like this one

JSON:
{"id":1628,"uuid":"cef5ed7d-8b8f-48bb-abb7-7d6e3b606d22","caller_id_name":"Ss0599654484##","caller_id_number":"07","destination_number":"14502390262","context":"default","start_stamp":"2025-10-31T11:41:46.947169","answer_stamp":null,"end_stamp":"2025-10-31T11:41:55.687582","duration":9,"billsec":0,"hangup_cause":"ORIGINATOR_CANCEL","queue_name":"","created_at":"2025-10-31T11:41:57.35141","direction":"inbound","connected_agent_id":"","recording_s3_path":"...14502390262-cef5ed7d-8b8f-48bb-abb7-7d6e3b606d22.wav"}

Never hit queue but... yeah I'm assuming there's a better way rather than chasing regex formats... any input would be appreciated.

For what it's worth, I'm I'd be willing to buy beers in exchange for 1-1 FreeSwitch advice with my setup so, if anyone wants to connect with a random nerd from Florida, DM me
Haha, thanks. I'm curious to know more about the Telnyx outages you are referencing. 'Cause they've been extremely reliable for me... Are you speaking of the DDoS attacks intermittent over a few days back in 2021? I'd agree that admittedly left an unbelievably bad taste in my mouth at the time, but they implemented a bunch of mitigating factors including putting their global network behind cloudflare ddos protection, so I suspect they are actually less likely to experience that kind of attack again compared to some other trunking providers. Otherwise, they've been rock solid for me.
 
The problem (I think) that I have is the main provider I use (SignalWire) doesn't provide ip addresses/port ranges to whitelist
If you can work out the AS (Autonomous System) numbers for Signalwire then look them up on the relevant internet registry, ARIN in the USA or RIPE for Europe, then you can get a list of all the public IP addresses that they use.
We use this method for compiling IP address lists to allow us to block access to certain organisations by devices in our offices.