CVE-2015-3185: Permissions, Privileges, and Access Controls (CWE-264)
value: The ap_some_auth_required function in server/request.c in the Apache HTTP Server 2.4.x before 2.4.14 does not consider that a Require directive may be associated with an authorization setting rather than an authentication setting, which allows remote attackers to bypass intended access restrictions in opportunistic circumstances by leveraging the presence of a module that relies on the 2.2 API behavior.
CWE description: Weaknesses in this category are related to the management of permissions, privileges, and other security features that are used to perform access control.
Published: 2015-07-20T23:59Z
Repository: https://github.com/apache/httpd
Language: C
Files changed: include/ap_mmn.h, include/http_request.h, server/request.c
Commit date: 2015-06-09 20:41:28 +0000
Commit message: SECURITY: CVE-2015-3185 (cve.mitre.org)

Replacement of ap_some_auth_required (unusable in Apache httpd 2.4)
with new ap_some_authn_required and ap_force_authn hook.

Submitted by: breser


git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1684524 13f79535-47bb-0310-9956-ffa450edef68

Diff: diff --git a/include/ap_mmn.h b/include/ap_mmn.h
index 2821f97cfc..b8d7da88bb 100644
--- a/include/ap_mmn.h
+++ b/include/ap_mmn.h
@@ -480,18 +480,20 @@
  * 20150121.1 (2.5.0-dev)  Add cmd_parms_struct.parent to http_config.h
  * 20150121.2 (2.5.0-dev)  Add response_code_exprs to http_core.h
  * 20150222.0 (2.5.0-dev)  ssl pre_handshake hook now indicates proxy|client
  * 20150222.1 (2.5.0-dev)  Add keep_alive_timeout_set to server_rec
  * 20150222.2 (2.5.0-dev)  Add response code 418 as per RFC2324/RFC7168
+ * 20150222.3 (2.5.0-dev)  Add ap_some_authn_required, ap_force_authn hook.
+ *                         Deprecate broken ap_some_auth_required.
  */
 
 #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
 
 #ifndef MODULE_MAGIC_NUMBER_MAJOR
 #define MODULE_MAGIC_NUMBER_MAJOR 20150222
 #endif
-#define MODULE_MAGIC_NUMBER_MINOR 2                 /* 0...n */
+#define MODULE_MAGIC_NUMBER_MINOR 3                 /* 0...n */
 
 /**
  * Determine if the server's current MODULE_MAGIC_NUMBER is at least a
  * specified value.
  *
diff --git a/include/http_request.h b/include/http_request.h
index 2317826f3a..97591e3e3e 100644
--- a/include/http_request.h
+++ b/include/http_request.h
@@ -183,10 +183,13 @@ AP_DECLARE(void) ap_internal_fast_redirect(request_rec *sub_req, request_rec *r)
 /**
  * Can be used within any handler to determine if any authentication
  * is required for the current request
  * @param r The current request
  * @return 1 if authentication is required, 0 otherwise
+ * @bug Behavior changed in 2.4.x refactoring, API no longer usable
+ * @deprecated @see ap_some_authn_required()
+ *
  */
 AP_DECLARE(int) ap_some_auth_required(request_rec *r);
 
 /**
  * @defgroup APACHE_CORE_REQ_AUTH Access Control for Sub-Requests and
@@ -539,10 +542,20 @@ AP_DECLARE_HOOK(void,insert_filter,(request_rec *r))
  *         or HTTP_... (deny access)
  * @ingroup hooks
  */
 AP_DECLARE_HOOK(int,post_perdir_config,(request_rec *r))
 
+/**
+ * This hook allows a module to force authn to be required when
+ * processing a request.
+ * This hook should be registered with ap_hook_force_authn().
+ * @param r The current request
+ * @return OK (force authn), DECLINED (let later modules decide)
+ * @ingroup hooks
+ */
+AP_DECLARE_HOOK(int,force_authn,(request_rec *r))
+
 /**
  * This hook allows modules to handle/emulate the apr_stat() calls
  * needed for directory walk.
  * @param finfo where to put the stat data
  * @param r The current request
@@ -585,10 +598,21 @@ AP_DECLARE(apr_bucket *) ap_bucket_eor_make(apr_bucket *b, request_rec *r);
  * @return The new bucket, or NULL if allocation failed
  */
 AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_bucket_alloc_t *list,
                                               request_rec *r);
 
+/**
+ * Can be used within any handler to determine if any authentication
+ * is required for the current request.  Note that if used with an
+ * access_checker hook, an access_checker_ex hook or an authz provider; the
+ * caller should take steps to avoid a loop since this function is
+ * implemented by calling these hooks.
+ * @param r The current request
+ * @return TRUE if authentication is required, FALSE otherwise
+ */
+AP_DECLARE(int) ap_some_authn_required(request_rec *r);
+
 #ifdef __cplusplus
 }
 #endif
 
 #endif  /* !APACHE_HTTP_REQUEST_H */
diff --git a/server/request.c b/server/request.c
index 736c4c444e..fa84c1ab32 100644
--- a/server/request.c
+++ b/server/request.c
@@ -69,10 +69,11 @@ APR_HOOK_STRUCT(
     APR_HOOK_LINK(auth_checker)
     APR_HOOK_LINK(insert_filter)
     APR_HOOK_LINK(create_request)
     APR_HOOK_LINK(post_perdir_config)
     APR_HOOK_LINK(dirwalk_stat)
+    APR_HOOK_LINK(force_authn)
 )
 
 AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name,
                             (request_rec *r), (r), DECLINED)
 AP_IMPLEMENT_HOOK_RUN_FIRST(int,map_to_storage,
@@ -95,10 +96,12 @@ AP_IMPLEMENT_HOOK_RUN_ALL(int, create_request,
 AP_IMPLEMENT_HOOK_RUN_ALL(int, post_perdir_config,
                           (request_rec *r), (r), OK, DECLINED)
 AP_IMPLEMENT_HOOK_RUN_FIRST(apr_status_t,dirwalk_stat,
                             (apr_finfo_t *finfo, request_rec *r, apr_int32_t wanted),
                             (finfo, r, wanted), AP_DECLINED)
+AP_IMPLEMENT_HOOK_RUN_FIRST(int,force_authn,
+                            (request_rec *r), (r), DECLINED)
 
 static int auth_internal_per_conf = 0;
 static int auth_internal_per_conf_hooks = 0;
 static int auth_internal_per_conf_providers = 0;
 
@@ -116,10 +119,43 @@ static int decl_die(int status, const char *phase, request_rec *r)
                       status, r->uri);
         return status;
     }
 }
 
+AP_DECLARE(int) ap_some_authn_required(request_rec *r)
+{
+    int access_status;
+
+    switch (ap_satisfies(r)) {
+    case SATISFY_ALL:
+    case SATISFY_NOSPEC:
+        if ((access_status = ap_run_access_checker(r)) != OK) {
+            break;
+        }
+
+        access_status = ap_run_access_checker_ex(r);
+        if (access_status == DECLINED) {
+            return TRUE;
+        }
+
+        break;
+    case SATISFY_ANY:
+        if ((access_status = ap_run_access_checker(r)) == OK) {
+            break;
+        }
+
+        access_status = ap_run_access_checker_ex(r);
+        if (access_status == DECLINED) {
+            return TRUE;
+        }
+
+        break;
+    }
+
+    return FALSE;
+}
+
 /* This is the master logic for processing requests.  Do NOT duplicate
  * this logic elsewhere, or the security model will be broken by future
  * API changes.  Each phase must be individually optimized to pick up
  * redundant/duplicate calls by subrequests, and redirects.
  */
@@ -234,19 +270,12 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r)
                 return decl_die(access_status,
                                 "check access (with Satisfy All)", r);
             }
 
             access_status = ap_run_access_checker_ex(r);
-            if (access_status == OK) {
-                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
-                              "request authorized without authentication by "
-                              "access_checker_ex hook: %s", r->uri);
-            }
-            else if (access_status != DECLINED) {
-                return decl_die(access_status, "check access", r);
-            }
-            else {
+            if (access_status == DECLINED
+                || (access_status == OK && ap_run_force_authn(r) == OK)) {
                 if ((access_status = ap_run_check_user_id(r)) != OK) {
                     return decl_die(access_status, "check user", r);
                 }
                 if (r->user == NULL) {
                     /* don't let buggy authn module crash us in authz */
@@ -260,10 +289,18 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r)
                 }
                 if ((access_status = ap_run_auth_checker(r)) != OK) {
                     return decl_die(access_status, "check authorization", r);
                 }
             }
+            else if (access_status == OK) {
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                              "request authorized without authentication by "
+                              "access_checker_ex hook: %s", r->uri);
+            }
+            else {
+                return decl_die(access_status, "check access", r);
+            }
             break;
         case SATISFY_ANY:
             if ((access_status = ap_run_access_checker(r)) == OK) {
                 ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
                               "request authorized without authentication by "
@@ -271,19 +308,12 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r)
                               r->uri);
                 break;
             }
 
             access_status = ap_run_access_checker_ex(r);
-            if (access_status == OK) {
-                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
-                              "request authorized without authentication by "
-                              "access_checker_ex hook: %s", r->uri);
-            }
-            else if (access_status != DECLINED) {
-                return decl_die(access_status, "check access", r);
-            }
-            else {
+            if (access_status == DECLINED
+                || (access_status == OK && ap_run_force_authn(r) == OK)) {
                 if ((access_status = ap_run_check_user_id(r)) != OK) {
                     return decl_die(access_status, "check user", r);
                 }
                 if (r->user == NULL) {
                     /* don't let buggy authn module crash us in authz */
@@ -297,10 +327,18 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r)
                 }
                 if ((access_status = ap_run_auth_checker(r)) != OK) {
                     return decl_die(access_status, "check authorization", r);
                 }
             }
+            else if (access_status == OK) {
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
+                              "request authorized without authentication by "
+                              "access_checker_ex hook: %s", r->uri);
+            }
+            else {
+                return decl_die(access_status, "check access", r);
+            }
             break;
         }
     }
     /* XXX Must make certain the ap_run_type_checker short circuits mime
      * in mod-proxy for r->proxyreq && r->parsed_uri.scheme

Vulnerable code: #define MODULE_MAGIC_NUMBER_MINOR 2                 /* 0...n */
            if (access_status == OK) {
                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
                              "request authorized without authentication by "
                              "access_checker_ex hook: %s", r->uri);
            }
            else if (access_status != DECLINED) {
                return decl_die(access_status, "check access", r);
            }
            else {
            if (access_status == OK) {
                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
                              "request authorized without authentication by "
                              "access_checker_ex hook: %s", r->uri);
            }
            else if (access_status != DECLINED) {
                return decl_die(access_status, "check access", r);
            }
            else {
Fixed code:  * 20150222.3 (2.5.0-dev)  Add ap_some_authn_required, ap_force_authn hook.
 *                         Deprecate broken ap_some_auth_required.
#define MODULE_MAGIC_NUMBER_MINOR 3                 /* 0...n */
 * @bug Behavior changed in 2.4.x refactoring, API no longer usable
 * @deprecated @see ap_some_authn_required()
 *
/**
 * This hook allows a module to force authn to be required when
 * processing a request.
 * This hook should be registered with ap_hook_force_authn().
 * @param r The current request
 * @return OK (force authn), DECLINED (let later modules decide)
 * @ingroup hooks
 */
AP_DECLARE_HOOK(int,force_authn,(request_rec *r))

/**
 * Can be used within any handler to determine if any authentication
 * is required for the current request.  Note that if used with an
 * access_checker hook, an access_checker_ex hook or an authz provider; the
 * caller should take steps to avoid a loop since this function is
 * implemented by calling these hooks.
 * @param r The current request
 * @return TRUE if authentication is required, FALSE otherwise
 */
AP_DECLARE(int) ap_some_authn_required(request_rec *r);

    APR_HOOK_LINK(force_authn)
AP_IMPLEMENT_HOOK_RUN_FIRST(int,force_authn,
                            (request_rec *r), (r), DECLINED)
AP_DECLARE(int) ap_some_authn_required(request_rec *r)
{
    int access_status;

    switch (ap_satisfies(r)) {
    case SATISFY_ALL:
    case SATISFY_NOSPEC:
        if ((access_status = ap_run_access_checker(r)) != OK) {
            break;
        }

        access_status = ap_run_access_checker_ex(r);
        if (access_status == DECLINED) {
            return TRUE;
        }

        break;
    case SATISFY_ANY:
        if ((access_status = ap_run_access_checker(r)) == OK) {
            break;
        }

        access_status = ap_run_access_checker_ex(r);
        if (access_status == DECLINED) {
            return TRUE;
        }

        break;
    }

    return FALSE;
}

            if (access_status == DECLINED
                || (access_status == OK && ap_run_force_authn(r) == OK)) {
            else if (access_status == OK) {
                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
                              "request authorized without authentication by "
                              "access_checker_ex hook: %s", r->uri);
            }
            else {
                return decl_die(access_status, "check access", r);
            }
            if (access_status == DECLINED
                || (access_status == OK && ap_run_force_authn(r) == OK)) {
            else if (access_status == OK) {
                ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r,
                              "request authorized without authentication by "
                              "access_checker_ex hook: %s", r->uri);
            }
            else {
                return decl_die(access_status, "check access", r);
            }