Apache HTTP コンポーネントの権限昇格の脆弱性の悪用プロセスを詳細に分析する方法
Apache HTTP には、ローカル権限昇格の脆弱性 (CVE-2019-0211) があることが判明しました。この脆弱性の作成者は、直ちに WriteUp と脆弱性 EXP を提供しました。Alpha Labs は、EXP の詳細な分析も実施しました。誰もがこの脆弱性を理解するのに役立つことを願って、メモを編集して共有しました。以下の内容では、主にEXPの実行手順をステップバイステップで説明し、また、利用過程でわかりにくい点についても詳しく説明します。
1. 脆弱性の原因
脆弱性の原因となったコードは作者の WriteUp で既に紹介されていますが、ここでは簡単に説明するだけとし、読む負担を軽減するためにソース コードの大部分は省略します。 。
Apache の MPM プリフォーク モードでは、メイン サーバー プロセスは root 権限で実行され、HTTP リクエストを処理するために低権限のワーカー プロセス (ワーカー) プールを管理します。メインプロセスとワーカー間の通信は、共有メモリ (SHM) を介して行われます。
1. Apache httpd サーバーが正常に (グレースフルに) 再起動すると、httpd メイン プロセスは古いワーカーを強制終了し、新しいワーカーに置き換えます。これにより、prefork_run()
関数が呼び出され、新しいワーカーを 1 つ生成します:
1 2 3 4 5 6 7 8 |
|
2. この関数で make_child() を呼び出し、ap_get_scoreboard_process(child_slot)->bucket をパラメーターとして使用します。 make_child() 関数は新しい子プロセスを作成し、バケット インデックスに従って all_buckets 配列を my_bucket に読み取ります:
1 2 3 4 5 6 7 8 |
|
3. child_main() を呼び出します。Apache が複数のポートをリッスンする場合、SAFE_ACCEPT(< code> ;) マクロ内の が実行され、ここで apr_proc_mutex_child_init() が実行されます: </p>
<div class="code" style="position:relative; padding:0px; margin:0px;"><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(&my_bucket->mutex,
apr_proc_mutex_lockfile(my_bucket->mutex),
pchild));
/* ... */</pre><div class="contentsignin">ログイン後にコピー</div></div>
<p>4. 上記の関数はさらに (*mutex)->meth->child_init( mutex, pool , fname):</p>
<div class="code" style="position:relative; padding:0px; margin:0px;"><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)->meth->child_init(mutex, pool, fname);
}</pre><div class="contentsignin">ログイン後にコピー</div></div>
<p>全体の簡略化されたプロセスは次のとおりです: </p>
<div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:php;toolbar:false">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()的指针来指向代码</pre><div class="contentsignin">ログイン後にコピー</div></div>
<p><code>prefork_child_bucket
構造 (つまり、all_buckets 配列の要素) を偽造するとします。 ) を共有メモリ内で変更し、all_buckets
配列 bucket
のインデックスを 3 行目のコード my_bucket
で制御して、構造体を指すようにすることができます。
その後のコード実行my_bucket->mutex->meth->child_init(mutex, pool, fname)
では、meth
構造体には次のポイントが含まれます。関数のポインタが複数あるため、child_init
関数のポインタを実行したい関数のポインタで上書きすることで、脆弱性を悪用する目的を達成でき、プロセスはまだ進行中です。現時点では root 権限を持っていますが、後で引き下げられます。
2. 脆弱性の悪用
著者は、WriteUp の中で悪用のプロセスを 4 つのステップに分けていますが、実際の悪用は彼が書いたものよりも少し複雑で、順序も少し異なります。以下は、exp の実行ステップに従って整理されたプロセスであり、いくつかの詳細が追加されています。
PHP を使用してワーカーの /proc/self/maps ファイルを読み取り、ワーカーのモジュールと脆弱性の悪用に必要なモジュール。関数アドレス
- #/proc/*/cmdline および /proc/*/status ファイルを列挙し、すべてのワーカー プロセスの PID を取得します
- PHP UAF の脆弱性を悪用して、ワーカー プロセスで読み取り/書き込み SHM 権限を取得します。
- Apache のメモリをトラバースし、
all_buckets## で配列アドレスを見つけます。 # メモリ パターン マッチングに基づく
グレースフル リスタートの後、 - all_buckets
の位置が変更されるため、「適切な」バケット インデックスを計算する必要があります。
all_buckets[bucket]
鍛造されたprefork_child_bucket
構造をまだ指していることを確認します SHM でペイロードを構築します - #ペイロードをスプレーした後の残りの SHM 領域。ステップ 5 で all_buckets[bucket]
- がこの領域を指していることを確認してから、ペイロード
にジャンプできるようにします。 ##process_score->bucket 手順 5 で計算したバケットに変更します。さらに、成功率をさらに向上させるために、SHM 領域内のすべての - process_score
構造体を列挙し、各ワーカーの
process_score->pid
を取得した PID と比較することもできます。ステップ 2. では、一致するのは正しいprocess_score
構造であり、各ワーカーのprocess_score->bucket
が変更されます。#Apache が正常に再起動して脆弱性をトリガーするまで待ちます (毎朝 6:25 に自動的に実行されます。または、手動で再起動して結果を確認することもできます)
- 具体的な詳細は以下のとおりです。
2.1 exp
get_all_addresses()
関数はそれぞれ取得します。キー メモリ アドレスとワーカーの PID は、後続のアプリケーションで使用するためにグローバル変数
$addresses と $worker_pids
に配置されます。 exp 実行時に shm
と apache
のアドレスが解決できない場合は、環境内の shm
のサイズが不一致である可能性があることに注意してください。 exp で検索された範囲でマップ ファイルを自分で確認し、if ($msize >= 0x10000 && $msize を正しい値に変更できます。 <code><p><code>real()
函数有两个作用,一是触发PHP的UAF漏洞。二是开始真正的漏洞利用过程,因为Z
中定义了jsonSerialize()
方法,它会在类实例被序列化的时候调用,即后面执行json_encode()
时调用,而所有的利用代码都在jsonSerialize()
中。
下面的代码只保留了EXP的基本框架,只为了让大家有一个整体上的概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
接下来具体看看jsonSerialize()
中的代码。
2.2 利用PHP的UAF获取读写SHM的权限
还是先概括的讲一讲PHP这个UAF漏洞原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
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
。
PHP字符串的内部实现如下,用一个zend_string表示,通过成员变量len来判断字符串长度,从而实现二进制安全。我们修改DateInterval的属性间接修改len的大小就可以通过this->abc读写SHM区域了。当然,为了能够成功利用漏洞,还有许多细节需要考虑。
1 2 3 4 5 6 |
|
2.2.1 填充空闲内存块
在脚本运行之前可能发生了大量的分配/释放,因此同时实例化的两个变量也不一定是连续的,为解决这个问题,实例化几个DateInterval对象填充不连续空闲块,以确保后面分配的内存是连续的:
1 2 3 4 5 |
|
2.2.2 创建保护内存块
为了保证UAF后我们控制的结构属于一块空闲内存,如果我们之后创建其他变量,那么这些变量可能会破坏我们已经控制的结构,为了避免这种情况,这里分配了很多对象Z的实例,后面的代码中会将其释放,由于PHP的堆LIFO的特点,这些释放掉的内存会优先于UAF的那块内存分配,从而保护被我们控制的结构。
1 2 3 |
|
函数ptr2str的作用相当于在内存中分配一个大小为78的zend_string结构,为什么是78这个大小接下来会提到。
1 |
|
2.2.3 分配UAF的字符串
接着创建字符串$this->abc,也就是一个zend_string结构,通过对它进行UAF,进而读写共享内存。
1 2 |
|
创建$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结构。
1 2 |
|
此时内存布局如下:
然后我们将分配一个与其大小相同的字符串($protector),由于PHP堆LIFO的特点,因此字符串将取代timelib_rel_time结构的位置。
1 2 |
|
接着就是最重要的一步:
1 |
|
再次创建一个DateInterval,它的timelib_rel_time结构将刚好占据上图中free的内存位置,同时$this->abc仍然是可以访问free这块内存的,即:&timelib_rel_time == &zend_string。因此我们可以通过修改DateInterval对象来修改zend_string.len,从而控制可以读/写内存的长度。
完成上述步骤后,我们还需要验证UAF是否成功,看一下DateInterval的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
因为有&timelib_rel_time == &zend_string,所以这里的$d和$y分别对应zend_string里的len和val。可以将$x(DateInterval)的h属性设置为0x13121110,再通过$this->abc字符串(zend_string)访问来判断UAF成功与否。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
最后别忘了释放掉$room,产生的空闲块将保护我们控制的结构,后面再新建变量都会优先使用这些内存。
1 |
|
2.2.5 控制并修改UAF的结构
利用这个PHP漏洞的目的是为了能够获取读写SHM的权限,现在我们能够读写zend_string.val的内容,能读写的长度是zend_string.len,因此只要将len的值增加到包括SHM的范围。
这时我们已经知道了SHM的绝对地址,还需要知道abc的绝对地址,得到两者之间的偏移量才可以修改len。因此需要找到字符串$this->abc在内存中的位置:
1 2 3 4 |
|
然后我们就可以计算两者间的偏移量了,还要注意的是,因为后面我们需要在内存中查找all_bucket,而它在apache的内存中所以我们的len需要将SHM和apache的内存都覆盖到,所以作者的WriteUp中说SHM和apache的内存都需要在PHP堆之后,而它们也确实都在PHP堆之后。
找SHM和apache的内存两者间较大的值,减去abc的地址,将得到的偏移通过DateInterval的d属性修改来修改zend_string.len。
1 2 3 |
|
这等同于将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[]结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
顺便将meth结构中所有函数指针打印出来,第6个就是我们要用到的(*child_init)()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
这是meth的结构,可以对照着看一看:
1 2 3 4 5 6 7 8 9 10 |
|
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结构由三个指针组成:
1 2 3 4 5 |
|
如果在SHM中大量喷射一个指向payload的地址,只要让&all_buckets[bucket]落在该区域内,payload就能得到执行,如下图中所示:
并且在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结构的总大小得到。
1 2 3 4 5 6 |
|
然后找喷射区域地址的中间值,计算它和all_buckets地址的偏移,再除以prefork_child_bucket结构的大小,就可以得到一个all_buckets数组下标索引,但别忘了SHM在all_buckets之前,所以这个索引还要取负值,这个值用$bucket_index_middle表示。
1 2 |
|
这样做的目的在于,在每优雅重启后,即便all_buckets的地址有所变化,&all_buckets[bucket]指向的位置会在$spray_middle上下浮动,最大程度上保证了该指针落在喷射的内存范围内,如下图所示:
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结构:
1 |
|
所以原本传给child_init的&my_bucket->mutex(prefork_child_bucket结构的一部分)就和zend_object相叠加了。
zend_object_std_dtor的执行又导致以下调用链:
1 2 3 4 5 6 7 8 9 10 |
|
上面的代码properties是一个zend_array结构,如下所示,我们控制其中的arData,pDestructor,如果我们将上面&ht->arData[0]->val放入要执行的命令,pDestructor()覆盖为system的地址,就可以实现命令执行了。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
回到exp中,首先构造bucket部分,放入要执行的命令,没有参数时默认执行"chmod +s /usr/bin/python3.5",但是自定义的命令长度也不能超过152字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
然后是meth,将原本child_init的指针改为zend_object_std_dtor:
1 2 3 4 5 6 7 8 9 10 11 |
|
经过调试也可以看到child_init被覆盖:
然后是properties(zend_array和apr_proc_mutex_t结构的叠加),u-nTableMask的位置将用作apr_proc_mutex_t结构的meth,而arData指向payload中的bucket。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
将各部分组合:
1 2 3 4 5 |
|
通过前面UAF控制的字符串abc写入SHM未使用部分的开头:
1 2 3 4 5 6 7 8 9 10 11 |
|
打印信息,将SHM剩下的部分喷射为properties的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
讲到这里可以再回头看看文章刚开始的图,应该就更容易理解了。
2.6 进一步提高成功率
前面还讲到,可以修改每个worker的process_score->bucket结构,这样一来,利用成功率就可以再乘以Apache Worker的数量,因为2.4中计算出的bucket索引能落在了SHM之外,如果有多个worker,如下图所示,就能提高&all_buckets[bucket]落在SHM中的概率:
迭代查找每个process_score结构直到找到每个PID,再将找到的PID$workers_pids中的PID对比,匹配的就说明是正确的结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
将所有workerprocess_score.bucket都进行修改,而非修改其中一个:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
到这里,整个漏洞利用过程就结束了,可以等到6:25AM查看利用是否利用成功,也可以手动执行apachectl graceful验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|
以上がApache HTTP コンポーネントの権限昇格の脆弱性の悪用プロセスを詳細に分析する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック







