Breaking DirecTV's DVR Authentication

by noir & GreedyHaircut

A friend recently came to me with the desire to build his own app to interact with his DirecTV DVR.  DirecTV already has a mobile app to do this, but their app leaves much to be desired.

The first place to start was to inspect the network traffic between the mobile app and DVR on the same network with a proxy tool like mitmproxy.  When doing this, we observed an interesting pattern with the traffic.  Every time the app sent a request to the server, the server would respond with 401 Unauthorized.  The app would then send a second request, identical to the first, but this time with an authorization header.  The server would accept this second request and respond.  This wouldn't just happen once at the beginning of a session.  Every single request would get a 401 the first time, then be repeated with authorization headers.

Inspecting the server's 401 response, it contained a "WWW-Authenticate" header which included four keys: realm, qop, nonce, and opaque.  A quick Google of these keys reveals the server seems to be issuing a digest authentication challenge.

A digest authentication challenge is part of adigest access authentication, an authentication method that can be used with web servers.  The way digest authentication works is that the client and server each know a pre-shared secret (a password).  When the server is responding to the client with the digest authentication challenge, it's telling the client how to authenticate itself.  The client will generate two strings:

string1 = md5(username:realm:password)
string2 = md5(method:digestURI)

These two strings are then used by the client to generate the authentication response:

response=md5(string1:nonce:nonceCount:cnonce:qop:string2)

If we want to talk to this DVR server, we needed to figure out how to authenticate ourselves.  In order to authenticate our response we needed username, realm, password, method, digestURI, nonce, nonceCount, cnonce, and qop.

The server's challenge response gives us the realm, qop, and nonce.  From the client's plaintext HTTP response we were also able to obtain the username (c0pi10t), method (HTTP GET), and digestURI (path in the requested URL).

This leaves us still needing the password, nonceCount, and cnonce.  The cnonce is an arbitrary value chosen by the client (us!) and the nonceCount can just always be 00000001.  So really we just need the password.  The password is the very thing that makes digest authentication secure.  The client and server ship with the shared password known to both of them, and they never have to transmit it over the wire.

In order to obtain the password, one option is to try brute-force.  Digest authentication is used with SIP, for which a couple of brute-forcing tools have already been created.  However, if the password being used is sufficiently complex, brute force is impractical.  We took an existing tool and tweaked it a bit to at least start a brute-force script while working on some other ideas.

While that ran, we attempted to inspect the application binary itself.  Sometimes developers do silly things and leave files around with interesting information, store secret values in insecure places, or don't bother to obfuscate strings in their binary.  Knowing the username gave me a known value to search for.  Unfortunately, cursory searches didn't reveal any clues inside the binary and couldn't even find a match for our username, so they seemed to at least be doing something to obfuscate the strings in the application binary.

Somewhere in all of this, we started skimming through the RFC for digest access authentication (RFC 2069).  Looking through the table of contents, one section immediately jumped out: Security Considerations.  This section covered some of the benefits that digest access authentication has over basic auth, as well as possible attacks.

"Section 3.3 - Man-in-the-Middle - A simple but effective attack would be to replace the Digest challenge with a Basic challenge to spoof the client into revealing their password."

Sadly, it goes on to explain how this could be combated.  In our case, the developers are likely to have simply written the client code in a way that it wouldn't respond to such a challenge.  It knows that the server will be using Digest authentication and there's no reason it should accept Basic auth as a challenge, especially when an RFC that's over 20 years old clearly outlines this attack.

But you know what, with the brute-force script still chugging along and having made no progress there, let's give it a shot.

There are several options for proxying tools that allow us to easily manipulate traffic.  Some personal favorites are Charles Proxy and mitmproxy.  While going into detail on how to modify traffic is beyond the scope of this article, both tools have extensive documentation that should make it easy to learn how in under an hour.

Using our tool of choice, when the client tries an unauthenticated request and the server responds with a digest challenge, we will modify that response to have an Authenticate: Basic header, indicating to the client that it should authenticate itself with Basic auth (Base64-encoded username and password).

When we do this, our client receives our spoofed server response, and obviously we can see that - holy shit... the client responded with Basic auth.  It's a Base64, colon-delimited string, which decoded gives us: c0pi10t:8th5Bre$Wrus.  We already had the username (the first part), and now we also had the password.

At this point, it's game over for the DirecTV.  We have all the pieces we need to write a client to interact with the DVR.  And not just this specific DVR, but any DirecTV DVR that's capable of working with the mobile app.  Due to the nature of digest access authentication, the password must must be the same for any DVRs that want to work with the mobile app.

In order for DirecTV to re-secure these communications, they will have to simultaneously update their mobile apps and their DVRs to use a new pre-shared password.

Return to $2600 Index