Home / exploits Apache Scoreboard / Status Race Condition
Posted on 22 July 2014
::: ::::::::: ::: :::::::: ::: ::::::::::::: ::: :::::::::::::::::::::::::::::::::: ::::::::: :+: :+: :+: :+: :+: :+: :+: :+::+: :+::+: :+: :+: :+: :+: :+: :+::+: :+: +:+ +:+ +:+ +:++:+ +:+ +:+ +:+ +:++:+ +:+ +:+ +:+ +:+ +:+ +:++:+ +:+ +#++:++#++:+#++:++#++#++:++#++:+#+ +#++:++#+++#++:++# +#++:++#++ +#+ +#+ +#++:++#+ +#+ +:+ +#+ +#++#+ +#+ +#++#+ +#+ +#++#+ +#+ +#+ +#+ +#+ +#+ +#+ +#+ #+# #+##+# #+# #+##+# #+##+# #+##+# #+# #+# #+# #+# #+# #+# #+# ### ###### ### ### ######## ### ############# ### ### ### ### ### ######### :::::::: ::: ::::::::::::: :::::::: ::::::: ::: ::: ::::::: :::::::: :::::::: :::::::: :+: :+::+: :+::+: :+: :+::+: :+::+:+: :+: :+: :+::+: :+::+: :+::+: :+: +:+ +:+ +:++:+ +:+ +:+ :+:+ +:+ +:+ +:+ +:+ :+:+ +:+ +:+ +:+ +#+ +#+ +:++#++:++# #++:++ +#+ +#+ + +:+ +#+ +#+ +:+ #++:++ #+ + +:+ +#+ +#+ +#++:++#+ +#+ +#+ +#+ +#+ +#+ +#+# +#+ +#++#+#+#+#+#+ +#+# +#+ +#+ +#+ +#+ +#+ #+# #+# #+#+#+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# #+# ######## ### ########## ########## ####### ####### ### ####### #################### ######## +:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:++:+:+:+:+:+:+ +#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#++#+#+#+#+#+#+ Hi there, --[ 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: <Location /foo> SetHandler server-status </Location> 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, "</td><td>%s</td><td nowrap>%s</td>" 834 "<td nowrap>%s</td></tr> ", 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 called ws->request in update_child_status_internal function but it's exactly the same location in memory) and puts there i.e. "GET / HTTP/1.0" 6) the ap_pmemdup(pool, str, 1) in ap_escape_logitem copies the first one byte from "GET / HTTP/1.0" - "G" in that case and returns it. The ap_pmemdup looks as follows: 112APR_DECLARE(void *) apr_pmemdup(apr_pool_t *a, const void *m, apr_size_t n) 113{ 114 void *res; 115 116 if (m == NULL) 117 return NULL; 118 res = apr_palloc(a, n); 119 memcpy(res, m, n); 120 return res; It allocates memory using apr_palloc function which returns "ditry" memory (note that apr_pcalloc overwrite allocated memory with NULs). So it's non-deterministic what's after the copied "G" byte. There might be or might be not. For now let's assume that the memory allocated by apr_palloc was dirty (containing random bytes). 7) ap_escape_logitem returns "G....." .junk. "
