Search the Asterisk Blog

Recent Security Issues

By Mark Michelson

Security releases of Asterisk were recently created. In this post, we’d like to go into the depths of two of the security issues and how they affected Asterisk. Before going any further, I want to extend thanks to the following people for their help on this.

  • Sandro Gauci ( Sandro was the person that reported these two security issues to us based on tests he had run. Conversations between him and Asterisk team members went smoothly and he was a pleasure to exchange information with.
  • The PJProject team: The fixes required for the security issues were in PJSIP code. They were happy to accept our patches and work on our proposed timetable to get the patches applied to PJProject.

Security Issue 1: CSeq

Let’s start off by discussing SIP transactions. To oversimplify things, a transaction consists of a single SIP request, and its corresponding responses. In SIP 2.0, it is mandatory to include a Via header in SIP requests and responses. This Via header must include a “branch” parameter, like this:

This branch parameter is used in order to identify which transaction an incoming request or response belongs to. In SIP 1.0, there was no such requirement, so transaction matching was more complicated. You can tell if someone is complying with SIP 1.0 instead of SIP 2.0 because they either will not have a branch parameter in their Via, or their branch parameter will not start with the magic cookie of z9hG4bK . One of the headers used when attempting to do transaction matching in SIP 1.0 is the CSeq header, which looks like this:

An example for an INVITE might look like this

Sandro found that if he sent the following packet to Asterisk, then Asterisk would crash:

Notice that the CSeq SIP method is a ridiculously long string. Notice also that the Via header has no branch parameter. When this SIP message arrives , PJSIP needs to determine which transaction this message is associated with. Because there is no branch in the Via header, PJSIP falls back to doing SIP 1.0-based matching. It allocates a buffer to hold the transaction key as follows:

It takes into account the fields that are going to be in the transaction key plus a little extra breathing room. The problem is, the next thing PJSIP does is this:

Unfortunately, that pj_memcpy()  call is dangerous here. Why? Go back to the memory allocation. Notice anything missing? That’s right, when allocating the buffer, the code did not factor in the SIP method from the CSeq header. In the case like this one where the CSeq header has an absurdly long SIP method, this results in writing data past the end of the allocation and thus trashing the underlying memory management. This results in a crash every time.

The fix we proposed (and which was accepted) was to take into account the size of the method name in the CSeq header during allocation, like so:

Security Issue 2: Multipart Bodies

SIP requests sometimes contain bodies. On INVITEs, for instance, the most common type of body to find is an SDP. However, it’s valid to have a body that consists of multiple parts. Sandro found a packet he could send to Asterisk that would result in Asterisk crashing. The packet he used was a bit complicated, so I found a simpler packet that would exhibit the issue.

Let’s examine this a bit more closely. First, notice the “content-type” header. It has a ;boundary=++  parameter on it. In multipart messages, this boundary is used to determine where parts begin and end. The delimiter of multipart messages is “–“, followed by the specified boundary. So for our body, the delimiter between parts is --++ . The final delimiter of a multipart message is followed by an additional “–” as an indicator that the end of the multipart body has been reached. For our body, the final part ends with --++-- . Understanding that, you can see that in our multipart body, we have only one part, and that part is zero-length.

PJSIP’s parser keeps track of body parts using two pointers, “start_body” and “end_body”. These pointers are aptly named, in that start_body points to the beginning of the body part, and end_body points to the end of the body part. This way, you can determine the length of the body part by simply subtracting start_body from end_body. When parsing this particular body part, start_body points to the beginning of the body part, and end_body points to the beginning of the next delimiter. In this particular case, that means they’re both pointing to the same address since there are no characters between the start of the body part and the next delimiter. However, the following piece of code then gets run:

Do you see what happens there? Those --end_body  lines end up moving end_body to an address less than start_body! Under normal circumstances, the next delimiter would not be on the same line as the start of the body part. So in order to make sure that the end_body pointer points at the end of the current body part, you need to move end_body to point to the end of the previous line of the body. Here’s the next line of code:

Notice how end_body - start_body  is passed as a parameter. The result of this subtraction is a negative number (-2 in this case). This negative number is passed to a function that expects an unsigned value. This negative number gets interpreted as a massive positive number (18446744073709551614 in this case). So the function that is currently parsing the body thinks the body is over eighteen quintillion bytes long instead of what it actually is: zero. This then becomes a problem in the following loop:

“end” in this case is eighteen quintillion bytes beyond the start of the body, meaning it is possible to read data past the end of the block where the body was allocated. Unlike the first issue, you couldn’t always guarantee that reading the invalid memory would result in a crash. Likelihood of the crash can be increased by sending many copies of the INVITE packet to Asterisk at once, though.

The fix we proposed, and which was accepted, was to add the following:

This way, the end_body pointer only gets adjusted if the body part is greater than zero-length.

Final Thoughts

First and foremost, these are two very serious issues. Both errors occur during the initial reception of SIP requests in PJSIP, prior to any authentication. This means that if you have not yet taken the steps to ensure your system is not vulnerable, please take the time to follow the recommendations in the security advisories as soon as you can.

Finally, I want to point out how amazing it is to work in an ecosystem like this one. In this particular case, an independent security researcher found some serious issues and reported them to us. In turn, we looked into the problems and found them to happen in PJProject. The result of all of this is that PJProject’s code has become more secure, and that’s a win not only for the Asterisk project, but for all the other projects out there that use PJSIP. This sort of inter-project assistance is exactly what open source development is all about, and we look forward to getting to collaborate on this sort of work again in the future, only hopefully on something less critical.

No Comments Yet

Get the conversation started!

Add to the Discussion

Your email address will not be published. Required fields are marked *

About the Author

Mark Michelson

Mark Michelson is a software developer and open source team lead at Digium and ten-year veteran to Asterisk development. His largest contributions to Asterisk include being one of the architects of the call completion supplementary services, being one of the architects of the PJSIP-based SIP channel driver that was introduced in Asterisk 12. He has his fingers just about everywhere in the code, though. Mark loves keeping up-to-date on software trends and applying what he learns to the Asterisk project. One of his biggest boasts is that he has actually removed more lines of code than he has added. Mark's interests outside of software development include cooking, skiing, travel, video games, and beer.

See All of Mark's Articles