1. 개요
다중처리 모듈(이하 MPM)로 동작하는Apache Http Server 에서 Out-Of_Bound(이하 OOB) Array Access로 인해 메인 서버 프로세스인 root 권한으로 임의의 함수를 실행 가능합니다. 표준 Linux에서 logrotate는 로그 파일 핸들을 재설정 하기 위해 오전 6:25분 apache2ctl graceful 명령어를 실행하여 apache를 재 시작 합니다. Apache가 정상적으로 재 시작 되었을 때 임의의 함수가 root권한으로 실행됩니다.
2. 취약한 버전
- Apache 2.4.17 ~ 2.4.38
- PHP 5.x, 7.2.x
3. 취약점 설명 및 분석
MPM prefork에서 실행되는 메인 서버 프로세스는 root로 동작합니다. 이 프로세스는 http 요청을 처리하기 위한 단일 스레드, 낮은 권한을 가진 www-data(작업자)들을 관리합니다. 이 작업자들의 피드백을 받기 위해 apache scoreboard는 작업자 pid 및 처리한 요청 등 다양한 정보가 포함 된 공유 메모리 영역(SHM)을 유지 및 관리합니다.
각 작업자들은 process_score pid와 관련된 구조를 유지하고 SHM 영역에 대해 rwx 권한을 갖습니다.
각 작업자의 구조는 다음과 같고 bucket 멤버는 메인 서버 프로세스의 all_buckets 배열에 access할 때 사용됩니다.
All_buckets의 중요한 구조들은 다음과 같습니다. all_buckets 배열에 access할 때 bound check가 존재하지 않기 때문에 process_score 구조의 bucket 멤버를 조작하여 apache가 재 시작 될 때 임의의 함수를 호출할 수 있습니다.
정상적인 재 시작 프로세스 (prefork.c)
1. Logrotate가 apache에 재 시작을 요청합니다.
2. Apache의 메인 서버 프로세서가 오래된 작업자를 죽이고 새로운 작업자를 생성하기 위해 prefork_run() 함수를 호출합니다.
3. ap_wait_or_timeout() 함수에 의해 작업자의 pid를 child_slot 변수에 반환합니다.
4. 해당 작업자의 종료가 정상적이라면 make_child() 함수가 호출되고 해당 함수의 인자로 ap_server_conf, child_slot(pid), ap_get_scoreboard_process(child_slot)->bucket을 사용합니다.
5. Make_child() 함수를 호출해 메인 서버 프로세스가 fork()로 새로운 child를 생성합니다.
6. 그 다음 child_main() 함수가 호출 되고 SAFE_ACCEPT code가 실행 됩니다. SAFE_ACCEPT code는 apache가 2개 이상의 port를 수신하고 있을 때 실행 되며 일반적으로 80, 443 port를 사용하기 때문에 해당 code가 실행됩니다.
SAFE_ACCEPT code가 실행되면 apr_proc_mutex_child_init()이 호출되며, 최종적으로 *(mutex)->meth->child_init(mutex,pool,fname)이 호출됩니다.
4. Exploit
Mod_prefork는 Mod_php와 함께 사용되기 때문에 PHP UAF 취약점을 이용해 OOB read/write로 작업자의 bucket(index of all_buckets)을 조작할 수 있고 공유 메모리 영역(SHM)에 fake prefork_child_bucket 구조를 작성할 수 있습니다.
아래 소스코드로 인해 조작된 bucket(index of all_buckets)으로 my_bucket을 제어할 수 있습니다.
임의의 함수 호출 프로세스는 다음과 같습니다.
|
bucket_id = ap_scoreboard_image->parent[id]->bucket my_bucket = all_buckets[bucket_id] mutex = &my_bucket->mutex apr_proc_mutex_child_init(mutex) (*mutex)->meth->child_init(mutex, pool, fname) |
|
적절한 함수 호출을 위해 (*mutex)->meth->child_init() 대신 zend_object_std_dtor(zend_object *object)이 호출될 수 있도록 합니다.
|
mutex = &my_bucket->mutex [object = mutex] zend_object_std_dtor(object) ht = object->properties zend_array_destroy(ht) zend_hash_destroy(ht) val = &ht->arData[0]->val ht->pDestructor(val) |
|
해당 프로세스는 아래 그림과 같습니다.
Fake prefork_child_bucket 구조에서 pDestructor는 libc_system으로 설정하고 &ht->arData[0]->val은 실행시킬 명령어를 가리키도록 합니다. 추가적으로apache가 재시작 될 때 all_buckets의 주소가 변경되므로 남아있는SHM영역에 fake prefork_child_bucket의 pointer를 spraying시킵니다.
최종적인 함수 호출 프로세스와 변조된 메모리 구조는 아래 그림과 같습니다.
|
burn6@ubuntu1804:~/CVE-2019-0211$ curl localhost/exp.php burn6@ubuntu1804:~/CVE-2019-0211$ sudo apache2ctl graceful |
|
PoC 코드를 요청하고 apache2ctl graceful 명령어로 apache가 재 시작되면 root 권한으로 리버스 쉘을 획득할 수 있습니다.
해당 취약점에 대한 공격 시나리오는 다음과 같습니다.
5. 위협요소
공격자가 PHP 웹쉘 업로드 취약점 및 XXE 취약점을 이용해 root 권한으로 서버를 장악할 수 있습니다.
6. 대응방안
Apache 최신 버전 업데이트
──────────────────────────
컨설팅사업본부 모의해킹팀