[Pentesting] HTTP auth, part II: digest

(Previously.)

Let's use the 'testpass' again:

htdigest -c .htpasswd_test testrealm testuser

( src )

AuthType in .htaccess changed:

AuthUserFile /home/i/web/.htpasswd_test
AuthName "testrealm"
AuthType Digest
<Limit GET POST>
require valid-user
</Limit>

Contents of .htpasswd_test:

testuser:testrealm:ebff225e1ceb73e026fcc645af3e84f6

In fact, this is:

 % echo -ne testuser:testrealm:testpass | md5sum
ebff225e1ceb73e026fcc645af3e84f6  -

Now try to access that web page, the server may return:

HTTP/1.1 401 Unauthorized
...
WWW-Authenticate: Digest realm="testrealm", nonce="sgIhliA3BgA=93e0dd849ec95fa55d43d0797c2cdfb0c2a2be78", algorithm=MD5, qop="auth"
...

And what my web browser sends:

GET /test HTTP/1.1
Host: ...
...
Authorization: Digest username="testuser", realm="testrealm", nonce="sgIhliA3BgA=93e0dd849ec95fa55d43d0797c2cdfb0c2a2be78", uri="/test", algorithm=MD5, response="d8c2a49300fd548a4b91cee9d285198f", qop=auth, nc=00000001, cnonce="4fcb938b78ad9041"

This is a bit more complex protocol, explained in Wikipedia.

See the code for calculating response: client.py.

Ouch... but how a server knows password, if it is MD5-hashed? Yes, but server knows MD5(user || realm || password), which is a part of that protocol.

Thus password cannot be wiretapped during MITM attack.

See: server.py.

However, another interesting problem:

A server can store HA1 = MD5(username:realm:password) instead of the password
itself. However, if the stored HA1 is leaked, an attacker can generate valid
responses and access documents in the realm just as easily as if they had
access to the password itself. The table of HA1 values must therefore be
protected as securely as a file containing plaintext passwords.[12]

( Wikipedia )

Now how to crack the exchange using hashcat. The 'sip' mode can be used, because auth in SIP was almost the same as here. (Inherited from HTTP. See RFC3261.)

The full string format:

$sip$***[username]*[realm]*GET*[uri_protocol]*[uri_ip]*[uri_port]*[nonce]*[clientNonce]*[nonceCount]*[qop]*MD5*[response]

But uri_protocol and uri_port is to be omitted.

The hashcat string:

$sip$***user*realm*method**uri**nonce*nonceCount*cnonce*auth*MD5*response

In our case, this is:

$sip$***testuser*testrealm*GET**/test**sgIhliA3BgA=93e0dd849ec95fa55d43d0797c2cdfb0c2a2be78*4fcb938b78ad9041*00000001*auth*MD5*d8c2a49300fd548a4b91cee9d285198f

Put it to for-hashcat and:

% hashcat -m 11400 for-hashcat

(Tune it with -a option...)

There are two nonces used -- from server ('nonce') and from client ('cnonce'). As a protection against replay attacks.

MITM attacker can't provoke any side to leak any important information.

This scheme would be still robust, if not to use outdated and cracked MD5. But the current Apache 2.4.63 still uses MD5.

If you still need to use HTTP auth (maybe for compatibility/legacy reasons), at least upgrade to TLS and both basic/digest auth would be OK.

(the post first published at 20251121.)


List of my other blog posts.

Subscribe to my news feed,

Some time ago (before 24-Mar-2025) there was Disqus JS script for comments. I dropped it --- it was so motley, distracting, animated, with too much ads. I never liked it. Also, comments didn"t appeared correctly (Disqus was buggy). Also, my blog is too chamberlike --- not many people write comments here. So I decided to switch to the model I once had at least in 2020 --- send me your comments by email (don"t forget to include URL to this blog post) and I"ll copy&paste it here manually.

Let"s party like it"s ~1993-1996, in this ultimate, radical and uncompromisingly primitive pre-web1.0-style blog and website.