상세 컨텐츠

본문 제목

리눅스 커널 로컬 권한 상승 취약점 (CVE-2022-0847)

취약점 분석 리포트

by Hexer 2022. 3. 17. 14:02

본문

 

1. 개요

지난 3월 7일, 보안 연구원 Max Kellermann은 리눅스 커널의 로컬 권한 상승 취약점을 공개하였습니다. 해당 취약점은 Dirty Pipe(CVE-2022-0847)로 명명되었으며, 리눅스 파일 시스템 내 읽기 권한이 있는 모든 파일에 임의의 데이터를 쓸 수 있습니다. 예를 들어, /etc/passwd와 같은 민감한 파일에 임의의 계정정보를 삽입하여 루트 권한의 계정을 생성할 수 있으며, setuid-root binary를 악성코드로 덮어씀으로써 루트 권한으로 악성코드를 실행할 수도 있습니다. 즉, 로컬 권한 상승으로 이어질 수 있는 취약점입니다. 이는 읽기 전용 리소스에 데이터 쓰기를 허용한다는 점에서 2016년에 발견된 Dirty Cow(CVE-2016-5195) 취약점과 유사하지만 악용하기는 더 쉬운 것으로 알려졌습니다.

 

날짜 내용
2021-04-29 웹서버 엑세스 로그에서 파일 손상 현상을 최초 인지
2022-02-19 Linux 커널 버그로 인한 파일 손상으로, 악용 가능한 취약점으로 판명
2022-02-20 버그 리포트, 익스플로잇 및 패치를 Linux 커널 보안팀에 공유
2022-02-21 Google Pixel 6에서도 버그 재현 확인, Android 보안팀에 버그 리포트 공유
2022-02-23 버그가 수정된 Linux 커널 패치 릴리스 (5.16.11, 5.15.25, 5.10.102)
2022-02-24 Google은 Android 커널에서 버그를 수정
2022-03-07 CVE-2022-0847 취약점 공개

[표 1] Dirty Pipe(CVE-2022-0847) 취약점 관련 타임라인

 

해당 취약점은 Linux Kernel 5.8을 포함한 이후 버전을 사용하는 시스템에 영향을 미치며, Linux 5.16.11, 5.15.25, 5.10.102 및 최신 Android 커널에서 패치 되었습니다. 사용하는 리눅스 커널 버전을 확인하여 취약한 버전인 경우 패치를 적용할 것을 권고합니다.

 

영향받는 버전 패치 버전
Linux Kernel 5.8을 포함한 이후 버전을 사용하는 시스템
(5.8 <= 리눅스 커널 < 5.16.11 / 5.15.25 / 5.10.102)
Linux Kernel 5.16.11, 5.15.25, 5.10.102

[표 2] 영향받는 버전과 패치 버전 

 

2. CVE-2022-0847 취약점 공격 과정 분석

Dirty Pipe(CVE-2022-0847) 취약점은 리눅스 커널에서 파이프 버퍼의 플래그에 대한 적절한 초기화가 이루어지지 않아 발생하는 취약점입니다. 이번 장에서는 공개된 PoC와 함께 해당 취약점에 대한 공격 구현 과정에 대해 알아보고자 합니다. 먼저, 이를 위해 필요한 사전 개념인 ‘파이프’와 ‘페이지 캐시’에 대해 간략히 살펴보도록 하겠습니다.

 

2.1 사전 지식

1) 파이프(PIPE)

파이프란, 프로세스간 통신을 위해 사용되는 IPC(Inter Process Communication) 기법 중 하나이며, 프로세스 간 단방향 통신을 지원합니다. 하나의 파이프를 생성하면 쓰기전용 파일 디스크립터(fd[1])와 읽기전용 파일 디스크립터(fd[0])가 생성됩니다. 쓰기전용 파일 디스크립터(fd[1])는 파이프 버퍼에 데이터를 쓸 때 사용되며, 읽기전용 파일 디스크립터(fd[0])는 파이프 버퍼에서 데이터를 읽을 때 사용됩니다.

※ 파일 디스크립터 : 유닉스 시스템에서는 파일, 디렉토리, 소켓, 파이프 등 모든 객체들을 파일로 관리합니다. 그리고 시스템 내부적으로 현재 오픈 되어 있는 파일들의 목록을 테이블 형태로 관리하여 각 파일에 인덱스 값(음이 아닌 정수 값)을 부여합니다. 이 값이 바로 ‘파일 디스크립터’이며, 프로세스는 파일에 접근이 필요한 경우 이러한 파일 디스크립터를 이용하여 접근하게 됩니다. 윈도우의 핸들(Handle)과 유사한 개념이라 볼 수 있습니다.

 

[그림 1] 파이프 기본 동작 구조
 

 

2) 페이지 캐시

리눅스는 파일 I/O의 성능 향상을 위해 페이지 캐시라는 메모리 영역을 만들어서 사용합니다. 한번 읽은 파일의 내용을 페이지 캐시에 저장해 두었다가 다시 한번 동일한 파일에 대한 접근이 발생하면 디스크에서 읽지 않고 페이지 캐시에서 읽습니다. 파일의 내용을 수정하는 경우에도 마찬가지로 페이지 캐시의 데이터를 수정하며, 변경된 사항은 일정 조건에 따라 디스크로 동기화(Write-Back) 됩니다.

 

[그림 2] 페이지 캐시를 통한 Read/Write
 

 

3) 파이프 버퍼와 페이지 캐시의 병합

파이프에는 pipe_buffer 구조체가 존재합니다. pipe_buffer 구조체에는 ‘flags’ 멤버가 있으며, 이는 파이프 버퍼의 플래그를 나타냅니다. [그림 3]에서 볼 수 있듯이 파이프 버퍼의 플래그는 ‘PIPE_BUF_FLAG_CAN_MERGE’ 값을 가질 수 있으며, 파이프 버퍼가 해당 플래그 값을 가지면 타 메모리 영역과 병합될 수 있습니다.

 

[그림 3] pipe_buffer 구조체 (pipe_fs_i.h)

 

Dirty Pipe(CVE-2022-0847) 취약점 공격은 파이프 버퍼의 플래그를 ‘PIPE_BUF_FLAG_CAN_MERGE’ 로 세팅하여 타 메모리 영역과 병합이 가능한 상태로 만들어 둔 후, 이를 페이지 캐시와 병합하여 페이지 캐시의 데이터를 덮어씌우는 방식으로 동작합니다. 파이프 버퍼와 페이지 캐시가 병합된 상태에서 (읽고 쓰는 작업에 아무런 권한이 요구되지 않는) 파이프 버퍼에 데이터를 쓰게 되면 해당 데이터는 병합된 페이지 캐시 메모리를 덮어쓰게 됩니다.

 


2.2 PoC에 기반한 취약점 공격 구현 과정 분석

공개된 PoC를 기반으로 Dirty Pipe(CVE-2022-0847) 취약점 공격의 구현 과정에 대해 살펴보도록 하겠습니다. 

먼저, 명령 줄 인수를 검증하는 코드입니다. 취약점 공격을 위해 충족 되어야하는 전제 조건을 알 수 있으며, 크게 4가지 조건이 있습니다.

- 오프셋은 페이지 경계에 위치해서는 안됨 (PAGE_SIZE = 4096)

- 쓰기는 페이지 경계를 넘을 수 없음

- 대상 파일에 대한 읽기 권한이 있어야함

- 쓰기는 파일 크기를 초과할 수 없음

※ 오프셋은 대상 파일에서 덮어쓸 데이터의 시작 위치를 뜻합니다.

 

[그림 4] 명령 줄 인수 검증 코드 (PoC)

 

다음은 파이프를 생성하고 PIPE_BUF_FLAG_CAN_MERGE 플래그를 세팅하는 코드이며, 크게 3가지 단계로 나눌 수 있습니다.

 

[그림 5] 파이프 생성 후 PIPE_BUF_FLAG_CAN_MERGE 플래그 세팅 (PoC)

 

(1) 먼저, pipe() 함수를 이용하여 파이프를 생성합니다. 이로 인해 파일 디스크립터 쌍(읽기전용, 쓰기전용)이 pipe() 함수의 인자로 받은 p배열에 저장됩니다. 읽기전용 파일 디스크립터는 p[0]이 되고, 쓰기전용 파일 디스크립터는 p[1]이 됩니다.

 

(2) 쓰기전용 파일 디스크립터(p[1])를 이용하여 파이프 버퍼를 임의의 데이터로 완전히 채웁니다. 이로 인해 파이프 버퍼는 PIPE_BUF_FLAG_CAN_MERGE 플래그가 세팅 됩니다.

 

(3) 읽기전용 파일 디스크립터(p[0])를 이용하여 파이프 버퍼의 데이터를 모두 읽어 들여 비웁니다. 이때, 파이프 버퍼의 PIPE_BUF_FLAG_CAN_MERGE 플래그는 초기화되지 않고 유지됩니다.

 

 

다음은 파이프 버퍼를 대상 파일의 페이지 캐시와 병합하기 위해 사용되는 코드입니다.

splice()함수는 인자로 전달받은 두 파일 디스크립터 간에 데이터를 이동하기 위해 사용되는 함수이며, PoC에서는 대상 파일의 오프셋 직전 1바이트를 파이프로 이동시키고 있습니다. 이러한 코드가 실행이 되면 대상 파일의 데이터는 먼저 페이지 캐시에 로드 된 후, 파이프 버퍼로 이동하게 됩니다. 하지만 현재 이동할 파이프 버퍼의 플래그가 PIPE_BUF_FLAG_CAN_MERGE로 설정되어 있기 때문에 파이프 버퍼는 대상 파일의 페이지 캐시와 병합처리 됩니다. 

 

[그림 6] 파이프 버퍼를 대상 파일의 페이지 캐시와 병합하기 위해 사용되는 코드 (PoC)

 

따라서, 이후 해당 파이프 버퍼로 전송하는 데이터는 병합된 페이지 캐시 영역을 덮어쓰게 됩니다.

[그림 7]은 병합된 파이프 버퍼에 공격자가 원하는 데이터를 쓰는 코드입니다. 이러한 파이프 버퍼에 데이터를 쓰는 작업은 아무런 권한이 요구되지 않기 때문에 공격자는 쓰기 권한이 없는 파일일지라도 데이터를 변조할 수가 있습니다.

 

[그림 7] 병합된 파이프 버퍼에 데이터를 쓰는 코드 (PoC)

 

단, 디스크를 직접적으로 건들지 않고, 페이지 캐시 메모리를 변조하는 것이므로 임의로 캐시 메모리를 삭제(명령어를 통한 삭제, 재부팅 등)하거나 커널이 페이지 캐시를 삭제하는 경우(메모리 부족으로 인한 회수 등)에는 공격을 통해 변경된 사항이 이전으로 되돌려집니다.

 

3. PoC Test

공개된 PoC를 이용하여 /etc/passwd 파일에 root 권한의 새로운 사용자를 추가해보도록 하겠습니다. 먼저, 커널 버전을 확인하여 취약한 버전인지 확인합니다.

 

[그림 8] 커널 버전 확인

 

다음으로, /etc/passwd 파일의 패스워드 필드에 작성할 데이터를 완성합니다. 본 테스트에서는 openssl을 사용하였습니다.

- 알고리즘 : MD5

- salt : mir

- 패스워드 : test

 

[그림 9] openssl을 이용하여 패스워드 필드에 작성할 데이터 완성

 

/etc/passwd 파일에 최종적으로 추가할 사용자 정보는 아래와 같습니다.

cyberone:$1$mir$PbcaUpzn8BSKOxP10prq/.:0:0:cyberone:/root:/bin/bash

 

이제 해당 데이터를 PoC를 이용하여 /etc/passwd 파일의 34 오프셋부터 덮어써보도록 하겠습니다.

 
[그림 10] PoC를 이용하여 /etc/passwd 파일에 root 권한의 새로운 계정을 추가

 

위 과정이 성공적으로 수행되면 [그림 11]과 같이 /etc/passwd 파일에 새로운 root 권한의 계정이 추가된 것을 확인할 수 있으며, 해당 계정으로 접속 시 root 권한임을 확인할 수 있습니다.

 

[그림 11] root 권한의 새로운 계정 생성 확인

 

※ 페이지 캐시 메모리를 삭제 후, /etc/passwd 파일을 다시 확인해보면 PoC를 통해 데이터를 변조하기 이전으로 되돌려진 것을 확인할 수 있습니다.

 

[그림 12] 페이지 캐시 메모리 삭제 후 /etc/passwd 파일 확인

 

4. 참고 자료

https://dirtypipe.cm4all.com/

 

https://www.linuxadictos.com/ko/dirty-pipe-una-vulnerabilidad-que-permite-sobrescribir-datos.html

 

https://www.tomshardware.com/news/dirty-pipe-linux-exploit

 

https://www.kernel.org/doc/htmldocs/filesystems/API-struct-pipe-inode-info.html

 

https://www.uptycs.com/blog/dirtypipe-linux-exploit

 

https://www.datadoghq.com/blog/dirty-pipe-vulnerability-overview-and-remediation/

 

https://elixir.bootlin.com/linux/v5.16.10/source/include/linux/pipe_fs_i.h

 

 

 

보안관제센터 MIR Team

 

관련글 더보기

댓글 영역