Reversing SIP digest authentication in JavaScript (node.js)


SIP digest authentication RFC 3261 is based on HTTP digest authentication RFC 2617.
The purpose of digest authentication is not having to exchange the password in plaintext.

When a client issues a request that needs to be authenticatd (e.g. a SIP REGISTER request) the server will respond with a “401 Unauthorized” response, which includes a “WWW-Authenticate” header, for example:

WWW-Authenticate: Digest algorithm=MD5, realm=”asterisk”, nonce=”40787c47″

The client will go through the digest authentication procedure and resend the request, including an “Authorization” header:

Authorization: Digest username=”myusername”, realm=”asterisk”, nonce=”40787c47″, uri=”sip:sip.example.com”, response=”3bc3cedaa7ee0f0b9bec12c50c8827cb”,algorithm=MD5

The client has calculated the “repsonse” like this:

HA1 = MD5(“myusername:asterisk:password”)

HA2 = MD5(“REGISTER:sip:sip.example.com”)

response = MD5(HA1+”:40787c47:”+HA2);

On the server side exactly the same computation is performed and the result is compared to the “response” transmitted by the client. If both hashes are equal then the request has been authenticated.

If an attacker can get access to the “Authorization” header of the client request (e.g. by sniffing packets on the client or server LAN) then the password can be recovered by bruteforcing it. Transport layer security (TLS) can protect against packet sniffing. However there are mechanisms for getting access to the “Authorization” header without needing to sniff packets.

Bruteforcing the password is a pretty straightforward process. You generate a list of all possible passwords (depending on which characters you are expecting), run it through the digest authentication procedure and compare the hashes.

The HA2 part of the calculation is constant (it is a hash of the request type and the SIP URI) and can be precomputed. Trying each password involves two MD5 hash operations.

I made another simple node.js application to which you feed the parameters obtained from a sniffed “Authorization” header (username, realm, nonce, SIP URI, response hash) and the length of the password to be bruteforced, e.g.:

node sipdigest-bruteforce.js myuser asterisk 40787c47 sip:sip.example.com ac4d7f3684ec6c176ced252917af449f 5

Depending on the length of the password you can now go and grab a coffee (or go on vacation):

Alphabet: “,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z”
Password length: 5
Number of permutations: 14348907
Precomputed HA2: 2c207d3d673349fe58bb06d3d5f7ae67
found password: keins
real    0m58.892s

It took less than a minute on my laptop to find my super secret password. Please note that i have replaced all values in this example with dummy values. That means if you try to run the above example you will not find the password.

On my 2 Ghz i7 CPU i get around 200k passwords / second (when utilizing just 1 CPU core). You can easily adapt the application to use multiple cores by splitting different regions of permutations over several threads.

The ability to parallelize the computation makes it very suitable to run it on a GPU (or several GPUs). Current of the shelf GPUs (even two or three years old) can achieve crazy performance for MD5 calculation, e.g. a AMD Radeon HD5790 can deliver up to 4 GHash/sec (MD5) which translates to 2 billion passwords per second.

A nice upgrade path exists as node.js has support for OpenCL (through WebGL).

You can download my node.js application here.