Summary:ASTERISK-19601: Failure of domain matching on authentication of INVITE request produces misleading NOTICE message; bypasses alwaysauthreject logic
Reporter:Matt Jordan (mjordan)Labels:
Date Opened:2012-03-28 13:20:33Date Closed:2012-06-25 09:26:20
Versions:Frequency of
Environment:Attachments:( 0) ASTERISK-19601-401.patch
( 1) ASTERISK-19601-no401.patch
( 2) extensions.conf
( 3) full
( 4) inv-00009.pcap
( 5) sip.conf
Description:Given the following configuration:

allowguest = no
udpbindaddr =
allowexternaldomains = no
domain = asterisk

context = default
type = peer
host = dynamic
disallow = all
allow = ulaw
allow = alaw
secret = secret

I would expect that an INVITE request that was authenticated, sent from peer phone_a, would fail if phone_a sent said INVITE request from something other then phone_a@asterisk.  What occurs with alwaysauthreject = False is a failed look up of an extension within Asterisk (even if that extension exists in a valid context), with a 404 Not Found response code.  In a similar scenario where the request is a REGISTER attempt, a 404 Not Found (unknown domain) would be sent.

Note that the INVITE request handling does determine that its rejecting the request due to an invalid domain; however, it then later produces an erroneous NOTICE message:

[Mar 28 13:21:37] DEBUG[26206] chan_sip.c: Got SIP INVITE to non-local domain ''; refusing request.
[Mar 28 13:21:37] NOTICE[26206] chan_sip.c: Call from 'phone_a' ( to extension '1000' rejected because extension not found in context 'default'.

In this particular case, that's relatively minor; however, what is odd is that the alwaysauthreject is never executed.  Thus, setting alwaysauthreject = True does not send a 403, but rather sends the 404 Not Found.
Comments:By: Mark Michelson (mmichelson) 2012-05-04 12:33:04.384-0500

The description is a bit all over the place here. So let me try to understand what's going on.

Problem 1: Misleading NOTICE message. This is because handle_request_invite() groups both SIP_GET_DEST_EXTEN_NOT_FOUND and SIP_GET_DEST_REFUSED into a single case. They should be separated so that they at least do not print the same message.

Problem 2: Something about alwaysauthreject. I suspect you may have a typo in your last sentence of the description (you meant 401 instead of 403). Are you expecting that because the domain match failed, we should employ alwaysauthreject logic here and send a 401 (making it look like we still are expecting valid auth credentials)?

There are a few things about your scenario that don't make sense to me. You have allowexternaldomains = no, and you have phone_a with host = dynamic and type = peer. Then, in your NOTICE message, it says "Call from phone_a...". This doesn't add up. In order for Asterisk to recognize that the call was from phone_a, it had to match the peer's IP address against the source IP address of the INVITE. In order for Asterisk to have the peer's IP address saved, the peer must have registered. If the peer attempted to register from an external domain, then the registration attempt should have been rejected. So what happened here? How did the phone successfully register in the first place?

Even with such anomalies in place, I think adding the alwaysauthreject logic to the case where an INVITE is rejected because a bad domain is used can be altered to send a 401. If an attacker somehow successfully authenticates with a system from an unallowed domain, then this is a way to keep the attacker thinking that his authentication attempts were unsuccessful.

By: Matt Jordan (mjordan) 2012-05-04 14:24:52.198-0500

This looks contrived because it is :-)  You may want to look at the original test steps on the wiki page
https://wiki.asterisk.org/wiki/display/~mjordan/Testing+***+DRAFT+***.  It should be INV-00009.

The use of type = peer and host = dynamic was to make it easier to set up for a set of particular tests that this issue came from.  The test actually has the operator add the allowexternaldomain = no and perform a sip reload between the REGISTER request sequence and the INVITE request sequence, so no, you shouldn't be able to get into this scenario easily.  This contrived example was set up in order to test an INVITE request being rejected due to a disallowed external domain; I'm sure there's a better set up for this that doesn't require such a convoluted setup/set of test steps.

That being said, the last sentence isn't a typo.  With alwaysauthreject set to True, the response to the second INVITE request (with the Authorization header) should always be a 403.  If you look at the pcap, the 401 is already sent in this particular case.

By: Mark Michelson (mmichelson) 2012-05-07 09:09:18.257-0500

I now understand how you got the situation to occur.

Regarding the 403 vs. 401 response, I suppose it's worth trying to decide what the "spirit" of the alwaysauthreject option is. To quote what's in the sample config file:

When an incoming INVITE or REGISTER is to be rejected,
for any reason, always reject with an identical response
equivalent to valid username and invalid password/hash
instead of letting the requester know whether there was
a matching user or peer for their request.  This reduces
the ability of an attacker to scan for valid SIP usernames.
This option is set to "yes" by default.

The key words to me here are "for any reason". We've already sent a 401 to request authentication from the endpoint, and they have replied in kind with proper credentials. However, since the INVITE is to be rejected, we should be sending a 401 again in order to minimize the amount of information we give to the sender of the INVITE.

To illustrate why I think a 401 would make sense here, consider the following scenario.

An attacker wishes to place a call through an Asterisk server and sends an INVITE from an external domain. Initially, Asterisk asks for authentication. Through some sort of wizardry, the attacker is able to give proper credentials for the authentication challenge. Since the INVITE has passed the authentication stage, Asterisk now notices that the INVITE has originated from an external domain, which is not allowed since allowexternaldomains is set to no. So now Asterisk can do one of two things. Either Asterisk can send a 403 or a 401. In either case, the attacker's call is rejected, but in the case where the 401 is sent, the attacker has no idea that he actually successfully cracked the authentication but was rejected simply because of his domain. In the 403 case, the attacker is aware that something different has occurred, and with the source code available may be able to deduce that the issue is the external domain. One spoofed IP address later and the attacker has successfully cracked the system.

By: Matt Jordan (mjordan) 2012-06-12 13:58:27.701-0500

Thanks for the explanation; I think that resolves my confusion on this issue.

I think you can safely ignore everything about this issue except the "NOTICE" message portion.

By: Mark Michelson (mmichelson) 2012-06-13 10:53:23.729-0500

I've attached ASTERISK-19601-401.patch and ASTERISK-19601-no401.patch

The 401 patch makes the following changes:

* SIP_GET_DEST_REFUSED case in handle_request_invite does not print the misleading NOTICE message.
* SIP_GET_DEST_REFUSED (as well as the default case) will send a 403 by default.
* If alwaysauthreject is set, then _any_ of the failure cases in that switch statement will lead to a 401 being sent.

The no401 patch contains only the first two changes of the 401 patch. In other words, alwaysauthreject setting has no bearing on which of the various 4xx responses get sent.

Let me know which of the two you feel is a better fit.

By: Matt Jordan (mjordan) 2012-06-25 08:38:09.218-0500

The no401 patch sounds like the correct solution and is far less intrusive on a release branch.  That sounds like the correct solution.