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);

Reply via email to