Exploit-DB #34133
Description: Apache 2.4.7 mod_status - Scoreboard Handling Race Condition
Type: dos
Platform: linux
Date: 2014-07-21
Author: Marek Kroemeke

Exploit:
--[ 0. Sparse summary
Race condition between updating httpd's "scoreboard" and mod_status,
leading to several critical scenarios like heap buffer overflow with
user
supplied payload and leaking heap which can leak critical memory
containing
htaccess credentials, ssl certificates private keys and so on.
--[ 1. Prerequisites

Apache httpd compiled with MPM event or MPM worker.
The tested version was 2.4.7 compiled with:

    ./configure --enable-mods-shared=reallyall --with-included-apr

The tested mod_status configuration in httpd.conf was:
        SetHandler server-status
    ExtendedStatus On
--[ 2. Race Condition

Function ap_escape_logitem in server/util.c looks as follows:

    1908AP_DECLARE(char *) ap_escape_logitem(apr_pool_t *p, const char
*str)
    1909{
    1910    char *ret;
    1911    unsigned char *d;
    1912    const unsigned char *s;
    1913    apr_size_t length, escapes = 0;
    1914
    1915    if (!str) {
    1916        return NULL;
    1917    }
    1918
    1919    /* Compute how many characters need to be escaped */
    1920    s = (const unsigned char *)str;
    1921    for (; *s; ++s) {
    1922        if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) {
    1923            escapes++;
    1924        }
    1925    }
    1926
    1927    /* Compute the length of the input string, including NULL
*/
    1928    length = s - (const unsigned char *)str + 1;
    1929
    1930    /* Fast path: nothing to escape */
    1931    if (escapes == 0) {
    1932        return apr_pmemdup(p, str, length);
    1933    }

In the for-loop between 1921 and 1925 lines function is computing the
length of
supplied str (almost like strlen, but additionally it counts special
characters
which need to be escaped). As comment in 1927 value says, function
computes count
of bytes to copy. If there's nothing to escape function uses
apr_pmemdup to duplicate
the str. In our single-threaded mind everything looks good, but tricky
part starts
when we introduce multi-threading. Apache in MPM mode runs workers as
threads, let's
consider the following scenario:

    1) ap_escape_logitem(pool, "") is called
    2) for-loop in 1921 line immediately escapes, because *s is  in
first loop run
    3) malicious thread change memory under *s to another value
(something which is not )
    4) apr_pmemdup copies that change value to new string and returns
it

Output from the ap_escape_logitem is considered to be a string, if
scenario above would occur,
then returned string would not be zeroed at the end, which may be
harmful. The mod_status
code looks as follows:

    833                    ap_rprintf(r, "%s%s"
    834                                  "%snn",
    835                               ap_escape_html(r->pool,
    836                                             
ws_record->client),
    837                               ap_escape_html(r->pool,
    838                                             
ws_record->vhost),
    839                               ap_escape_html(r->pool,
    840                                             
ap_escape_logitem(r->pool,
    841                                                     
ws_record->request)));

The relevant call to ap_escape_html() is at line 839 after the
evaluation of ap_escape_logitem().
The first argument passed to the ap_escape_logitem() is in fact an apr
pool associated with
the HTTP request and defined in the request_rec structure.

This code is a part of a larger for-loop where code is iterating over
worker_score structs which is
defined as follows:

    90struct worker_score {
    91#if APR_HAS_THREADS
    92    apr_os_thread_t tid;
    93#endif
    94    int thread_num;
    95    /* With some MPMs (e.g., worker), a worker_score can
represent
    96     * a thread in a terminating process which is no longer
    97     * represented by the corresponding process_score.  These
MPMs
    98     * should set pid and generation fields in the worker_score.
    99     */
    100    pid_t pid;
    101    ap_generation_t generation;
    102    unsigned char status;
    103    unsigned short conn_count;
    104    apr_off_t     conn_bytes;
    105    unsigned long access_count;
    106    apr_off_t     bytes_served;
    107    unsigned long my_access_count;
    108    apr_off_t     my_bytes_served;
    109    apr_time_t start_time;
    110    apr_time_t stop_time;
    111    apr_time_t last_used;
    112#ifdef HAVE_TIMES
    113    struct tms times;
    114#endif
    115    char client[40];            /* Keep 'em small... but large
enough to hold an IPv6 address */
    116    char request[64];           /* We just want an idea... */
    117    char vhost[32];             /* What virtual host is being
accessed? */
    118};

The 'request' field in a worker_score structure is particularly
interesting - this field can be changed inside
the copy_request function, which is called by the
update_child_status_internal. This change may occur when the
mod_status is iterating over the workers at the same time the
ap_escape_logitem is called within a different
thread, leading to a race condition. We can trigger this exact
scenario in order to return a string without a
trailing . This can be achived by running two clients, one triggering
the mod_status handler and second
sending random requests to the web server. Let's consider the
following example:

    1) the mod_status iterates over workers invoking
update_child_status_internal()
    2) at some point for one worker mod_status calls
ap_escape_logitem(pool, ws_record->request)
    3) let's asume that ws_record->request at the beginning is ""
literally  at the first byte.
    4) inside the ap_escape_logitem function the length of the
ws_record->request is computed, which is 1
       (an empty string consisting of )
    5) another thread modifies ws_record->request (in fact it's call