You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Dirk-Willem van Gulik <di...@webweaving.org> on 2003/12/18 18:32:19 UTC

[patch] digest replay protection

Right now we do not verify the nonce using in digest. This means that
an attacker can replay the response from another site or section
on the web site if

->	the users username+password is the same across the site.
->	the realm name is the same

Unfortunately that is often the case (and for the real, there
is a lot of DAV and webdav out there).

Below somewhat addresses that by veryfing that the nonce
is actually our own.

Dw

Index: include/http_core.h
===================================================================
RCS file: /home/cvs/apache-1.3/src/include/http_core.h,v
retrieving revision 1.71
diff -u -r1.71 http_core.h
--- include/http_core.h	7 Jul 2003 00:34:09 -0000	1.71
+++ include/http_core.h	18 Dec 2003 17:30:29 -0000
@@ -162,6 +162,7 @@

 API_EXPORT(const char *) ap_auth_type (request_rec *);
 API_EXPORT(const char *) ap_auth_name (request_rec *);
+API_EXPORT(const char *) ap_auth_nonce (request_rec *);
 API_EXPORT(int) ap_satisfies (request_rec *r);
 API_EXPORT(const array_header *) ap_requires (request_rec *);

@@ -244,6 +245,7 @@
     int satisfy;
     char *ap_auth_type;
     char *ap_auth_name;
+    char *ap_auth_nonce;	/* digest auth */
     array_header *ap_requires;

     /* Custom response config. These can contain text or a URL to redirect to.
Index: main/http_core.c
===================================================================
RCS file: /home/cvs/apache-1.3/src/main/http_core.c,v
retrieving revision 1.327
diff -u -r1.327 http_core.c
--- main/http_core.c	17 Nov 2003 17:14:53 -0000	1.327
+++ main/http_core.c	18 Dec 2003 17:30:30 -0000
@@ -236,6 +236,9 @@
     if (new->ap_auth_name) {
         conf->ap_auth_name = new->ap_auth_name;
     }
+    if (new->ap_auth_nonce) {
+        conf->ap_auth_nonce= new->ap_auth_nonce;
+    }
     if (new->ap_requires) {
         conf->ap_requires = new->ap_requires;
     }
@@ -577,6 +580,29 @@
     return conf->ap_auth_name;
 }

+API_EXPORT(const char *) ap_auth_nonce(request_rec *r)
+{
+    core_dir_config *conf;
+    conf = (core_dir_config *)ap_get_module_config(r->per_dir_config,
+						   &core_module);
+    if (conf->ap_auth_nonce)
+	return conf->ap_auth_nonce;
+
+    /* Ideally we'd want to mix in some per-directory style
+     * information; as we are likely to want to detect replay
+     * across those boundaries and some randomness. But that
+     * is harder due to the adhoc nature of .htaccess memory
+     * structures, restarts and forks.
+     *
+     * But then again - you should use AuthNonce in your config
+     * file if you care. So the adhoc value should do.
+     */
+    return ap_psprintf(r->pool,"%lu%lu%lu%lu%lu%s",
+	*(unsigned long *)&((r->connection->local_addr).sin_addr ),
+	ap_user_name, ap_listeners, ap_server_argv0, ap_pid_fname
+    );
+}
+
 API_EXPORT(const char *) ap_default_type(request_rec *r)
 {
     core_dir_config *conf;
@@ -2797,6 +2823,28 @@
     return NULL;
 }

+/*
+ * Load an authorisation nonce into our location configuration, and
+ * force it to be in the 0-9/A-Z realm.
+ */
+static const char *set_authnonce (cmd_parms *cmd, void *mconfig, char *word1)
+{
+    core_dir_config *aconfig = (core_dir_config *)mconfig;
+    int i;
+
+    aconfig->ap_auth_nonce = ap_escape_quotes(cmd->pool, word1);
+
+    if (strlen(aconfig->ap_auth_nonce) > 510)
+		return "AuthNonce lenght limited to 510 chars for browser compatibility";
+
+    for(i=0;i<strlen(aconfig->ap_auth_nonce );i++)
+	if (!ap_isalnum(aconfig->ap_auth_nonce [i]))
+		return "AuthNonce limited to 0-9 and A-Z range for browser compatibilty";
+
+    return NULL;
+}
+
+
 #ifdef _OSD_POSIX /* BS2000 Logon Passwd file */
 static const char *set_bs2000_account(cmd_parms *cmd, void *dummy, char *name)
 {
@@ -3411,6 +3459,9 @@
   "An HTTP authorization type (e.g., \"Basic\")" },
 { "AuthName", set_authname, NULL, OR_AUTHCFG, TAKE1,
   "The authentication realm (e.g. \"Members Only\")" },
+{ "AuthNonce", set_authnonce, NULL, OR_AUTHCFG, TAKE1,
+  "An authentication token which should be different for each logical realm. "\
+  "A random value or the servers IP may be a good choise.\n" },
 { "Require", require, NULL, OR_AUTHCFG, RAW_ARGS,
   "Selects which authenticated users or groups may access a protected space" },
 { "Satisfy", satisfy, NULL, OR_AUTHCFG, TAKE1,
Index: main/http_protocol.c
===================================================================
RCS file: /home/cvs/apache-1.3/src/main/http_protocol.c,v
retrieving revision 1.330
diff -u -r1.330 http_protocol.c
--- main/http_protocol.c	3 Feb 2003 17:13:22 -0000	1.330
+++ main/http_protocol.c	18 Dec 2003 17:30:32 -0000
@@ -76,6 +76,7 @@
 #include "util_date.h"          /* For parseHTTPdate and BAD_DATE */
 #include <stdarg.h>
 #include "http_conf_globals.h"
+#include "util_md5.h"		/* For digestAuth */

 #define SET_BYTES_SENT(r) \
   do { if (r->sent_bodyct) \
@@ -1391,11 +1392,24 @@

 API_EXPORT(void) ap_note_digest_auth_failure(request_rec *r)
 {
+    /* We need to create a nonce which:
+     * a) changes all the time (see r->request_time)
+     *    below and
+     * b) of which we can verify that it is our own
+     *    fairly easily when it comes to veryfing
+     *    the digest coming back in the response.
+     * c) and which as a whole should not
+     *    be unlikely to be in use anywhere else.
+     */
+    char * nonce_prefix = ap_md5(r->pool,
+	ap_psprintf(r->pool, "%s%lu",
+		ap_auth_nonce(r), r->request_time));
+
     ap_table_setn(r->err_headers_out,
 	    r->proxyreq == STD_PROXY ? "Proxy-Authenticate"
 		  : "WWW-Authenticate",
-	    ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%lu\"",
-		ap_auth_name(r), r->request_time));
+	    ap_psprintf(r->pool, "Digest realm=\"%s\", nonce=\"%s%lu\"",
+		ap_auth_name(r), nonce_prefix, r->request_time));
 }

 API_EXPORT(int) ap_get_basic_auth_pw(request_rec *r, const char **pw)
Index: modules/standard/mod_digest.c
===================================================================
RCS file: /home/cvs/apache-1.3/src/modules/standard/mod_digest.c,v
retrieving revision 1.52
diff -u -r1.52 mod_digest.c
--- modules/standard/mod_digest.c	3 Feb 2003 17:13:27 -0000	1.52
+++ modules/standard/mod_digest.c	18 Dec 2003 17:30:32 -0000
@@ -316,6 +316,23 @@

 /* The actual MD5 code... whee */

+/* Check that a given nonce is actually one which was
+ * issued by this server in the right context.
+ */
+static int check_nonce(pool *p, char * prefix, const char * nonce) {
+    char * timestamp = (char *)nonce + 2 * MD5_DIGESTSIZE;
+    char * md5;
+
+    if (strlen(nonce) < MD5_DIGESTSIZE)
+     	return AUTH_REQUIRED;
+
+    md5 = ap_md5(p, ap_pstrcat(p, prefix, timestamp, NULL));
+
+    return strncmp(md5, nonce, 2 * MD5_DIGESTSIZE);
+}
+
+/* Check the digest itself.
+ */
 static char *find_digest(request_rec *r, digest_header_rec * h, char *a1)
 {
     return ap_md5(r->pool,
@@ -339,7 +356,6 @@
 /* Determine user ID, and check if it really is that user, for HTTP
  * basic authentication...
  */
-
 static int authenticate_digest_user(request_rec *r)
 {
     digest_config_rec *sec =
@@ -355,7 +371,15 @@

     if (!sec->pwfile)
 	return DECLINED;
-
+
+    /* Check that the nonce was one we actually issued. */
+    if (check_nonce(r->pool, ap_auth_nonce(r), response->nonce)) {
+	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
+		    "Client us using a nonce not issued by us for this context: %s", r->uri);
+	ap_note_digest_auth_failure(r);
+	return AUTH_REQUIRED;
+    }
+
     if (!(a1 = get_hash(r, c->user, sec->pwfile))) {
 	ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, r,
 		    "user %s not found: %s", c->user, r->uri);

Re: [patch] digest replay protection

Posted by Cliff Woolley <jw...@virginia.edu>.
On Thu, 18 Dec 2003, Dirk-Willem van Gulik wrote:

> +    if (strlen(aconfig->ap_auth_nonce) > 510)
> +		return "AuthNonce lenght limited to 510 chars for browser compatibility";

s/lenght/length/

> +
> +    for(i=0;i<strlen(aconfig->ap_auth_nonce );i++)
> +	if (!ap_isalnum(aconfig->ap_auth_nonce [i]))
> +		return "AuthNonce limited to 0-9 and A-Z range for browser compatibilty";

s/compatibilty/compatibility/

--Cliff

Re: [patch] digest replay protection

Posted by Ben Laurie <be...@algroup.co.uk>.
Dirk-Willem van Gulik wrote:

>>This doesn't appear to check that the timestamp is anywhere near now,
>>which would prevent same-site replays...
> 
> 
> Correct - the trouble with timestap checks is that ?most/some? browsers
> will NOT cache the password the user has entered; but the 'response' (i.e.
> nonce+realm+password). So if one sets a 5 minute time out on the time
> stamp - then users will be prompted for a password every 5 minutes or so.

That's crap. So, we should do it right and get the browsers fixed.

Cheers,

Ben.


-- 
http://www.apache-ssl.org/ben.html       http://www.thebunker.net/

"There is no limit to what a man can do or how far he can go if he
doesn't mind who gets the credit." - Robert Woodruff

Re: [patch] digest replay protection

Posted by Dirk-Willem van Gulik <di...@webweaving.org>.
> This doesn't appear to check that the timestamp is anywhere near now,
> which would prevent same-site replays...

Correct - the trouble with timestap checks is that ?most/some? browsers
will NOT cache the password the user has entered; but the 'response' (i.e.
nonce+realm+password). So if one sets a 5 minute time out on the time
stamp - then users will be prompted for a password every 5 minutes or so.

Setting it to any thing shorter, which from a security perspective would
even make more snese, of course means typing the password for every page
:)

Bear in mind that unlike experimental/mod_auth_digest - most browsers and
mod_digest does not do anything like fancy/decent qop or other counts; and
as far as I understand any re-newing of the nonce requires a full
rejection round trip.

Dw

Re: [patch] digest replay protection

Posted by Ben Laurie <be...@algroup.co.uk>.
Dirk-Willem van Gulik wrote:

> Right now we do not verify the nonce using in digest. This means that
> an attacker can replay the response from another site or section
> on the web site if
> 
> ->	the users username+password is the same across the site.
> ->	the realm name is the same
> 
> Unfortunately that is often the case (and for the real, there
> is a lot of DAV and webdav out there).
> 
> Below somewhat addresses that by veryfing that the nonce
> is actually our own.

This doesn't appear to check that the timestamp is anywhere near now, 
which would prevent same-site replays...

Cheers,

Ben.

-- 
http://www.apache-ssl.org/ben.html       http://www.thebunker.net/

"There is no limit to what a man can do or how far he can go if he
doesn't mind who gets the credit." - Robert Woodruff