
| 項目 | 内容 |
|---|---|
| CVE ID | CVE-2023-4911 |
| 攻撃タイプ | ヒープバッファオーバーフロー → ローカル権限昇格(Local Privilege Escalation) |
| CVSS 3.1 | 7.8 (High) |
| 公開日 | 2023-10-03 |
| 脆弱ポイント | glibc 動的ローダー(ld.so)の GLIBC_TUNABLES パーサー |
| 脆弱バージョン | glibc 2.34~2.38 |
CVE-2023-4911 は、GNU C Library (glibc) の動的ローダーが GLIBC_TUNABLES 環境変数を解析する際に発生するヒープバッファオーバーフローの脆弱性です。攻撃者はこのオーバーフローを利用して動的ローダーのライブラリ検索パス (RPATH) を操作することで、SUID root バイナリ (su, sudo など) が実行される際に、攻撃者が用意した悪意のある共有ライブラリを代わりにロードさせ、任意のコードを root 権限で実行することができます。glibc は事実上すべての主要な Linux ディストリビューションのコアコンポーネントであるため、この脆弱性は 2021年4月以降にリリースされたほとんどの glibc ベースのディストリビューションに影響を与えました。
while (true)
{
char *name = p;
size_t len = 0;
/* 이름(name) 길이 찾기 */
while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
len++;
/* '=' 없이 끝나면 종료 */
if (p[len] == '\0')
{
if (__libc_enable_secure)
tunestr[off] = '\0';
return;
}
/* ':'를 먼저 만나면 잘못된 항목 */
if (p[len] == ':')
{
p += len + 1;
continue;
}
/* '='를 만났으므로 value 시작으로 이동 */
p += len + 1;
/* 원본 문자열에서 value 계산 */
char *value = &valstring[p - tunestr];
len = 0;
/* value 길이 찾기 */
while (p[len] != ':' && p[len] != '\0')
len++;
...
/* tunestr에 복사 */
...
if (p[len] != '\0')
p += len + 1;
}
__tunables_init() が環境変数リストから GLIBC_TUNABLES を探します。tunables_strdup() が __minimal_malloc() でバッファを割り当て、元の文字列をコピーします (この時点の malloc はまだ完全に初期化されていない非常に初期の malloc 実装です)。parse_tunables() がこのバッファを : (コロン) で区切りながら各 key=value ペアを分離し、対応するチューナブルに値を代入します。parse_tunables() は1つのチューナブルを name 解析 → p 移動 → value 解析 → p 移動 の順序で処理します。正常な入力では、value をすべて処理した後、次のチューナブルの開始位置に p が移動し、次の項目を解析します。
しかし、次のような name=name=value 形式の入力が与えられると、
GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=AAAA...(긴 문자열)
最初の解析過程で glibc.malloc.mxfast=AAAA... 全体が1つの value として認識され、tunestr バッファにコピーされます。その後、value の後に次のチューナブルを区切るコロン (:) が存在しないため、解析ポインタ (p) が次の項目に移動できず、すでにコピーした value の開始位置を再び指すことになります。
問題は、この value 自体が name=value の形式を持っていることです。次の反復でパーサーはこれを新しいチューナブルと誤認識し、重複したデータをバッファに書き込みます。tunestr は元の文字列サイズ分しか割り当てられていないため、重複して書き込まれる部分が発生し、ヒープバッファオーバーフローが発生します。
発生したヒープバッファオーバーフローは、__minimal_malloc() で連続して割り当てられた隣接ヒープ領域まで上書きします。攻撃者はこれを利用して、動的ローダー (ld.so) の内部構造体である link_map の l_info[DT_RPATH] ポインタを、攻撃者が制御するスタックアドレスに変更することができます。
そのスタック領域には、事前に操作された Elf64_Dyn 構造体が配置されており、この構造体は攻撃者が望むディレクトリを新しいライブラリ検索パス (RPATH) として指定します。その結果、ld.so は正常なシステムライブラリの代わりに、攻撃者が用意した共有ライブラリを優先的にロードすることになります。
name=name=value 形式の GLIBC_TUNABLES 環境変数を構成します。su など) を実行します。parse_tunables() でヒープバッファオーバーフローが発生します。link_map の l_info[DT_RPATH] ポインタを、攻撃者が用意した偽の Elf64_Dyn 構造体が配置されたスタックアドレスに操作します。libc.so.6 をロードします。libc.so.6 の初期化コード (または改変された開始ルーチン) が root 権限で実行され、setuid(0), setgid(0) および /bin/sh の実行を行います。su の認証ロジックが実行される前に行われるため、パスワード検証なしで root シェルを取得することになります。PoC は、ランダム化 (ASLR) の影響で目的のメモリレイアウトが形成されるまで execve() を繰り返し実行するブルートフォース方式を使用します。そのため、攻撃の成功可否や所要時間は環境によって異なる可能性があり、一般的には数百から数千回の試行が必要です。
まず git にある内容をディレクトリにクローンします
git clone https://github.com/baeseungwon1010/CVE-2023-4911

以下のコマンドで docker イメージをビルドします
cd C* && docker compose run --rm cve-2023-4911-lab

コンテナに入った後、エクスプロイトコードを実行します
cd /home/student/exploit && ./exp
実行後、待つと一般ユーザーから sudo(0) に変わったことが確認できます

glibc のバージョンを脆弱バージョンから修正バージョンにパッチ適用します。パッチ後、可能であれば再起動/再起動により以前のバージョンの glibc がメモリに残らないようにします。即時のパッチ適用が難しい場合、不要な SUID, SGID などのプロセスを削除する一時的な対策が存在します。