Wednesday, May 25, 2011

Dynamically Restrict Access to Asterisk SIP 5060 using iptables

We have an asterisk deployment where we have users who work from home so their phones need to be able to connect to asterisk remotely.  In order to limit the number of security threats to our system we lock it down by using the permit and deny settings of SIP Peers to make sure that only those users can get in.

In order to do this for external users we originally allowed every IP to connect, simply relying on using very strong random passwords (40+ character random passwords, unique per extension), and fail2ban to block repeated attempts.  This has been working for us for the last year and a half but I decided to take our security one step further and lock it down to the exact IP of the phone.

We use Aastra 6731i and 6757i devices which have the ability to grab a URL when they boot up, the startup event.  In the aastra.cfg you would have this line:
action uri startup: http://asteriskpbx/aastraphone.php?action=register&ext=$$SIPUSERNAME$$

This tells the phone to grab aastraphone.php at startup.  We use this to keep track of phones, so I decided I would use it to add an addtional layer of IP security to our system.

First, you need to have access to iptables from your web server (or you can pass commands another way, this is a quick and dirty method).

Install & Configure Sudo
apt-get install sudo
add the following lines to /etc/sudoers
#give access to iptables
Cmnd_Alias IPT=/sbin/iptables

#give access to iptables to the account apache is running under, for me www-data
# User privilege specification
www-data ALL=NOPASSWD: IPT

Build aastraphone.php
This will insert an allow rule for the given IP at the top of the input chain.  It will not be removed automatically, so you should probably use some mechanism for going through and cleaning up these entries.  A quick and dirty method would be to put the allow rules in a chain and then flush the chain nightly.  The next time the phone polls the web page it would then be re-authorized for access.  Not a great solution, but for 9-5 shops it would work great at midnight.  A more elegant solution would be to track them in a database and check the database every once in a while for IP's that need to be removed.  


Additionally, the realtime version of this is able to check and see if an IP is supposed to be allowed to connect in from the outside world, this solution does not do that it assumes that if the phone knows how to get to the aastraphone.php file that it is allowed to connect externally.  Because of this it is important to have configured the permit and deny options in sip.conf to prevent peers from being used externally if they shouldn't.

aastraphone.php:
$ext = $_REQUEST['ext'];
if($ext > 0 && is_numeric($ext)){
system("sudo /sbin/iptables -I INPUT 1 -s {$_SERVER['REMOTE_ADDR']} -p udp --dport 5060 -j ACCEPT");
}


Configure IPTables Default Rules
Your system will need to be configured to allow internal hosts to connect, and reject external hosts to connect to your SIP port by default.  Using the following commands should handle this for you.  Using the related/established rule is important so that your communication with your SIP providers stays fully functional.
#by default block all access from the outside work to your asterisk system
iptables -A INPUT -s 0.0.0.0/0.0.0.0 -d 0.0.0.0/0.0.0.0 -m state --state INVALID -j DROP
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -I INPUT -p udp --dport 5060 -s 192.168.0.0/16 -j ACCEPT #allow all 192.168.x.x hosts to connect
iptables -I INPUT -p udp --dport 5060 -j DROP #block all others that don't match a rule.  Alternatively you could use the default policy for input drop.
iptables -I INPUT -p tcp --dport 80 -j ACCEPT #allow web traffic for the aastraphone.php file to function


Notes
This solution assumes you leave the web server open to the world, as the phones will need to be able to get to that to gain access to SIP/5060.  

Tuesday, May 24, 2011

Dynamically Restrict Access to Realtime Asterisk SIP Peer by IP

We have an asterisk deployment where we have users who work from home so their phones need to be able to connect to asterisk remotely.  In order to limit the number of security threats to our system we lock it down by using the permit and deny settings of SIP Peers to make sure that only those users can get in.

In order to do this for external users we originally allowed every IP to connect, simply relying on using very strong random passwords (40+ character random passwords, unique per extension).  This has been working for us for the last year and a half but I decided to take our security one step further and lock it down to the exact IP of the phone.

We use Aastra 6731i and 6757i devices which have the ability to grab a URL when they boot up, the startup event.  In the aastra.cfg you would have this line:
action uri startup: http://asteriskpbx/aastraphone.php?action=register&ext=$$SIPUSERNAME$$

This tells the phone to grab aastraphone.php at startup.  We use this to keep track of phones, so I decided I would use it to add an addtional layer of IP security to our system.

First, I had to add a new column to my Realtime Asterisk sippeers table called externalaccess, which I will only set to true for devices that are allowed to connect to asterisk from the outside world.
mysql> ALTER TABLE  `sippeers` ADD  `externalaccess` BOOL NOT NULL ;

Second, I added the following code to aastraphone.php:


$ext = $_REQUEST['ext'];
if($ext > 0 && is_numeric($ext)){
$query="update sippeers set deny = '0.0.0.0/0.0.0.0', permit = '{$_SERVER['REMOTE_ADDR']}/255.255.255.255' where name = '$ext' and externalaccess = true";
mysql_query($query);
}


Finally, I tested it out by setting externalaccess to 0, booting the phone and verifying that the phone's ip did NOT get updated, which it did not.  Then i set it to 1, and rebooted the phone and it was succesful in updating the realtime peer.  This instantly allows the phone to register, and because the page load happens quickly enough the phone has no problem registering right away. 

Please note that this does still have vulnerabilities, it is not foolproof, just an additional layer of security.  At the moment if you query aastraphone.php with the right extension you automatically grant yourself access to that peer, assuming you know the password you could then make calls, and if you didn't you could then attempt to brute force the system.  It may be prudent to add a little more security to this system by passing another parameter from the phone to the aastraphone.php script to help verify the peer is who it says it is.  Maybe pass through the callerid of the extension, or the context that extension should be dialing from and verify that against sippeers.

Additionally this solution should work for other brands of phones that are able to poll a URL at an interval, or at startup such as Cisco, and Polycom.  I haven't touched my Polycom IP 650 in a while but I'll test it out on that phone in the next few days and update this post with the results.  

Monday, May 23, 2011

After upgrading from CRM 4.0 to CRM 2011 I get javascript errors

We just upgraded to CRM 2011 and whenever we opened the accounts page we were getting a javascript error saying:
"There was an error with this field's customized event.
Field:window
Event:onload
Error:The value of the property 'Form_onload' is null or undefined, not a Function object"
I did not build our original CRM implementation so I'm not familiar with a lot of the JavaScript code in use, so I simply tried to disable the code from executing, and that actually solved the problem I was having.  


To do so:

  1. Open the form that has the error by Clicking New to add a new Record
  2. Click Customize
  3. Click Form
  4. Once you do that you should be at this screen:


  1. Now you need to click the Form Properties button and you should land at the window on the right
  2. I found the offending javascript
  3. Clicked Edit
  4. Unchecked the Enabled button
  5. Clicked Okay, Okay, and then Save and Publish on the Prior window.  


Now go back and try to create a new account and it won't error out anymore.  So something is wrong with the javascript from the old CRM 4.0 system and I'll need to figure out if we need it, and how to fix it if we do.

Thursday, May 19, 2011

How To Protect Asterisk From SIP Attacks Using fail2ban

There are many scanners running around attempting to find open SIP servers that they can easily guess passwords for in order to make free calls at your expense.  If they get ahold of an account they could easily start calling all over the world racking up huge bills for you, so it's important to protect yourself.  I've blocked IP's that were scanning me and had them rack up 50GB of traffic over the course of a weekend!  All these scanners do is test hundreds of different SIP credentials per second trying to find a valid set.  Installing fail2ban will prevent them from being able to scan you for valid credentials.

One more level of protection is to configure your SIP Peer with a valid subnet that it must connect from.  We set all of our peers to only be usable in house unless it's a work from home or road warrior person, in which case they have a more lax security set.  There are more ways to implement more advanced security which I'll write about in a few weeks.


I have a Debian based Asterisk 1.6.2.11 system in production in our call center and implemented fail2ban to prevent this from happening to me, and use this as a default level of protection on any Asterisk system I install.

Configuration is very simple:

>apt-get install fail2ban


#Configure the asterisk jail, be sure to set your email address in here so you can receive notifications of blocked IP's (every once in a while i've had valid devices manage to get themselves blocked).  Additionally set the log path to where you asterisk is logging.
Max Retry is the number of failregex attempts fail2ban will allow before blocking the IP for $bantime
Bantime is the number of seconds an IP will be blocked at the IPTables level.

>echo "[asterisk-iptables]
enabled  = true
filter   = asterisk
action   = iptables-allports[name=ASTERISK, protocol=all]
           sendmail-whois[name=ASTERISK, dest=your_email_address@your_domain.com, sender=asterisk@your_domain.com]
logpath  = /var/log/syslog
maxretry = 5
bantime = 3600" >> /etc/fail2ban/jail.conf

#Configure the strings you want to look for to identify rogue devices / users.  These regex patterns seem to work great for me.
>echo "[INCLUDES]
#before = common.conf
[Definition]
#_daemon = asterisk
failregex = NOTICE.* .*: Registration from '.*' failed for '<HOST>' - Wrong password
            NOTICE.* .*: Registration from '.*' failed for '<HOST>' - No matching peer found
            NOTICE.* .*: Registration from '.*' failed for '<HOST>' - Username/auth name mismatch
            NOTICE.* .*: Registration from '.*' failed for '<HOST>' - Device does not match ACL
            NOTICE.* .*: Registration from '.*' failed for '<HOST>' - Peer is not supposed to register
            NOTICE.* <HOST> failed to authenticate as '.*'$
            NOTICE.* .*: No registration for peer '.*' \(from <HOST>\)
            NOTICE.* .*: Host <HOST> failed MD5 authentication for '.*' (.*)
            NOTICE.* .*: Failed to authenticate user .*@<HOST>.*

ignoreregex =" > /etc/fail2ban/filter.d/asterisk.conf

Wednesday, May 18, 2011

Why you shouldn't purchase anything Panasonic (Including RP-HC700)

Last October I went to astricon in Washington DC and managed to lose my noise cancelling headphones somewhere. I ordered a new pair of Panasonic RP-HC700 Noise-Cancelling Headphones and had them shipped to the hotel for the flight home.  They worked better than my $50 phillips NC headphones that were 4 or 5 years old, and continued to do so for the first 2 times I used them.  Going to Hawaii for new years I noticed a staticy buzzing sound and I found it annoying so I simply turned off the NC function and assumed it was a dead battery.  I replaced the battery, and in January on a short Bombardier Q400 Turbo Prop flight to Montana I noticed it again, and had to turn it off again.

This week I put them on so I wouldn't have to listen to the Vacuum cleaner at home, and the static is horrible with the Noise Cancelling function turned on, and with the NC function turned off I still hear it, although at a reduced level, in my right ear.  Crap, but no problem things break, that's why they normally provide a 1 to 5 year warranty.  I went to panasonic.com and filled out the contact form saying what happened and received this reply 24 hours later:

Thank you for contacting Panasonic.

Based on the information you have provided, we recommend the product be sent to our Customer Service Center in McAllen Texas. In accordance with the warranty, the product will be replaced with a reconditioned model. Please send the product along with a copy of the receipt and a letter to explain the problem to: Panasonic Customer Service Center 4900 George McVay Dr. Suite B Door #12 McAllen, TX 78503

The warranty period for the product is ninety days for labor and parts coverage for service or replacement with a reconditioned model. The warranty period begins at the time of purchase. The original sales receipt or a copy of the sales receipt is required to validate warranty service.

If the unit was purchased more than ninety days, you can still ship the product to the address listed above, however, it will be replaced with a reconditioned model for a fee. To obtain information regarding the cost to replace the product, please call (800) 211-7262.



So I called the phone number, told it that i was calling about headphones, gave it the model number, and the IVR then said "You may ship these back to us at (5 second pause) and we will replace them for a fee, but call before you do.  Can I help you further?"
When I said yes it went back to the beginning of the IVR.  Isn't that helpful, it failed to give me the address, and provided no way for me to call, considering I was already on the phone and answering yes kicked me back to the main menu.  I gave up and said operator and pressed 0 on my phone 10 times and eventually I got put on hold.  The hold music was a deafening white noise static(similar to what my headphones produce), after 5 minutes of this I was connected to a surly woman who did not sound like she enjoyed her job.  Amazingly she was dumbfounded that I was told to call them because according to her they provide no warranty to Panasonic equipment unless bought from Panasonic Direct.  Yes, you read that right.  
1) The email said to call, and I did, and the phone number was dumbfounded.
2) If you go to best buy, amazon, or any other retailer and buy a panasonic product they will not provide warranty support.  Your only recourse is to return the product to the retailer.  Most retailers have a 30 day return policy, meaning your product has zero support 30 days from the date of purchase.
3) The answer I got via email was not the same answer I got via phone, but neither one was anywhere close to acceptable.


Moral of the story, I got one flight out of my $110 headphones, and Panasonic does not stand behind their product or help you get your product replaced when it fails.  This is the only Panasonic product I can remember having purchased in the last 5 years, and it will be the absolute last Panasonic product I ever buy if they refuse to stand by the quality of their product.

Tuesday, May 17, 2011

Upgrading from Microsoft Dynamics CRM 4.0 to CRM 2011 Installation Failures

Update: I fixed the problem and you can see the short answer version at the bottom of this post
We have an existing Microsoft Dyanmics CRM 4.0 implementation and I decided to see if I could upgrade it to 2011 without too much hassle (the answer was no), and I ran in to several problems that I couldn't find very much information about.  Because our CRM 4.0 solution is set up on a Windows Server 2003 machine I had to build a new server with Windows 2008 R2 and SQL Server 2008 R2 to support Microsoft Dynamics CRM 2011.  In order to avoid breaking our production system I took a backup of our existing CRM implementation, copied it to the new server, restored it under the same name, and then did all of my import testing there.  Please note that when you import a database it makes tons of modifications to it making the old implementation useless, so it's smart to do all of your testing off of a copy of your production database.

Problems:
1) I didn't read the full requirements list to find out SQL Express wasn't supported, so when I saw it on the ISO I assumed that it would work.  Think again, CRM requires a full version of SQL.  Why is the express version on the ISO if it's not supported?  Seriously?

Time Lost: 20 Minutes to install and then uninstall it.

2) After getting over that hurdle I opened the CRM splash.exe launcher.  I clicked install on "Install Microsoft Dynamics CRM  Server" and it crashed like so:

I did some searching through the ISO and found the Server\amd64\SetupServer.exe file and ran that.  From some google searches it looks like having IE9 installed on your system causes this to happen, with no explanation or solution other than to go to the setup files directly.  Irritating.  I did confirm this theory by uninstalling Internet Explorer 9 from the View Installed Updates of the Add Remove Programs section in the control panel, and then the splash screen worked as it should.

Time Lost: 10 minutes to find each application and the SRS Extensions executables to run by hand.  
10 more minutes to uninstall IE9 to verify that was the problem for the purposes of this post.

3) In order to upgrade my existing installation without breaking it I copied the CRM database to my 2011 server and imported that.  10 minutes in to the upgrade process it fails with this error: "Column names in each table must be unique. 
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Data.SqlClient.SqlException: Column names in each table must be unique. Column name 'IsIntegrationUser' in table 'SystemUserBase' is specified more than once.

This is a confusing error because the column does exist in the database once so I'm curious what the upgrade process was attempting to do when it caused this problem.  I looked at the SystemUserBase table in the CRM 2011 database and it looks fairly similar to the 4.0 database but with a few more columns.  Searching around I found very little, there was one post saying it may be a case sensitivity issue but the column is named exactly as it should be so that isn't it.  

I decided to rename the column to IsIntegrationUserOld because the column was set to 0 for every user.  I ran the import again and this time I got a new error:
System.Exception: Action Microsoft.Crm.Tools.Admin.UpgradeDatabaseAction failed. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Data.SqlClient.SqlException: There is already an object named 'DF_SystemUserBase_IsIntegrationUser' in the database.

Okay so that didn't work, I deleted my import, restored my database again and this time I simply deleted the IsIntegrationUser column.  10 minutes later it failed again, but with a new error!:
 Error| Exception occured during Microsoft.Crm.Tools.Admin.OrganizationUpgrader: Action Microsoft.Crm.Tools.Admin.UpgradeDatabaseAction failed. InnerException: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.ArgumentException: An item with the same key has already been added..


A suggestion on the CRM Forums in this post was that I shouldn't have this field, so on my production system I should mark it as a custom field then remove it in the Customizability section.  I used this command to do so:
Update attribute set IsCustomField = 1 where name = 'IsIntegrationUser'  
Then I went to this screen and removed the IsIntegrationUser attribute (I took this screenshot after removing it, so you won't see it in my list):


I did this and got a generic error.  I checked the Event Viewer for more detail and it didn't give me anything, so I went to discover how to enable logging.  Turns out you need to use Tracing, so I enabled tracing by updating my registry entry to look like this with help from the KB article here http://support.microsoft.com/kb/907490:




With tracing enabled I performed the delete again, got the error message, and found this error in the log file:
ALTER TABLE SystemUserExtensionBase DROP COLUMN IsIntegrationUser Exception: System.Data.SqlClient.SqlException: ALTER TABLE DROP COLUMN failed because column 'IsIntegrationUser' does not exist in table 'SystemUserExtensionBase'.

So I added this value (IsIntegrationUser,bit) to the table SystemUserExtensionBase thinking maybe if it exists it will let me delete it cleanly, and.... SUCCESS!!!!  I got past this error.  Now I made a new backup of the database, copied it to my CRM 2011 server, imported it in to SQL, and attempted to import the 4.0 implementation in to 2011.

I ended up getting the same error as before:
System.Data.SqlClient.SqlException: Column names in each table must be unique. Column name 'IsIntegrationUser' in table 'SystemUserBase' is specified more than once.

In theory now the IsIntegrationUser record should be deleted, but the column still exists in the SystemUserBase table.  I removed the column from the table and re-ran the import.  It made it past this step and everything seems to be good.

Time Lost: 2 Days through Trial and Error and Troubleshooting (every attempt took 10 to 20 minutes)

Final Solution:

  1. On the CRM 4.0 Server
    1. Update attribute set IsCustomField = 1 where name = 'IsIntegrationUser'  
    2. Modify the SystemUserExtensionBase table and add a column for IsIntegrationUser with type bit.
    3. Navigate to Settings -> Customization > User (Double click) -> Attributes  -> and delete the IsIntegrationUser value
    4. Now go to the SystemUserBase table and delete the IsIntegrationUser column if it still exists.
    5. Backup the CRM Database from SQL Management Studio
  2. On the CRM 2011 Server
    1. Restore the 4.0 CRM Database to SQL
    2. Navigate to Organizations, and import the old CRM Database