본문 바로가기

카테고리 없음

[CVE-2019-11045] PHP DirectoryIterator에서 NULL 문자(\x00) 뒤의 문자를 임의로 제거하는 취약점

아 ~ 오랫만이네요 ... :)

역시 2019년의 그 약속은 지켜지지 않았습니다. 꾸준히 블로깅하려고 했는데 ...

 

2020년도 되었고, 작년에 지키지 못한 약속도 있고해서 ... 오늘은 PHP 취약점 하나를 분석하고, 그 내용을 블로깅하려고 합니다.

뭐 대단한 내용은 아니니... 잘 아시는 분들은 여기서 뒤로가기를 ...

 

CVE-2019-11045 취약점은 "Ubuntu Securify Notices"(https://usn.ubuntu.com/)를 모니터링하다가 발견한 취약점입니다. "Ubuntu Security Notices", 줄여서 USN은 꾸준하게 모니터링하는 사이트는 아닌데 ... 혹시 놓친 취약점이 있을까봐 틈틈히 모니터링하고 있습니다.

 

USN에 있는 취약점 링크는 https://usn.ubuntu.com/4239-1/ 입니다. 총 4개의 취약점이 패치되었습니다. 그 중에서 개인적으로 CVE-2019-11045 취약점이 대상으로 적당할 것 같아서 취약점과 관련된 이슈를 트래킹하고, 분석 및 테스트하는 방법에 대해서 블로깅하려고 합니다.

 

USN-4239-1: PHP vulnerabilities | Ubuntu security notices

USN-4239-1: PHP vulnerabilities

usn.ubuntu.com

 

USN에는 취약점에 대한 간략한 정보와 CVE ID 정도가 공개되기 때문에 빠르게 정보를 습득하고, 관심있는 취약점을 추린 다음, Debian/Redhat/Ubuntu 등의 배포판에서 관리하는 이슈 트래커에서 CVE ID로 검색해서 대상 프로젝트의 이슈 트래커나 git 커밋 로그 등을 확인합니다. CVE-2019-11045에 대한 내용은 아래 링크에서 확인할 수 있습니다.

 

CVE-2019-11045 in Ubuntu

Description In PHP versions 7.2.x below 7.2.26, 7.3.x below 7.3.13 and 7.4.0, PHP DirectoryIterator class accepts filenames with embedded \0 byte and treats them as terminating at that byte. This could lead to security vulnerabilities, e.g. in applications

people.canonical.com

배포판에서 관리하는 이슈 트래커에서 프로젝트에서 관리하는 이슈 트래커나 git의 커밋 로그 같은 중요 정보를 얻을 수 있습니다.

PHP의 이슈 트래커에 들어가서 취약점과 관련된 내용을 확인해보면 아래와 같이 "취약한 소스 코드", "PoC", "패치"가 잘 공개되어 있는 것을 확인할 수 있습니다.

PoC와 패치된 코드가 아주 간단합니다. PoC를 보면 DirectoryIterator에 전달되는 문자열 중간에 \x00(null character)가 포함되어 있습니다. 취약점 이슈 제목이 "DirectoryIterator class silently truncates after a null byte"이니, 대충 "../../ryat\x00/php"를 전달했는데 PHP가 허락도 없이 마음대로 \x00 이후의 문자열을 제거하고, "../../ryat"만 사용하는 것으로 예상할 수 있습니다.

 

PoC를 테스트하기 위해 github에서 PHP 프로젝트의 소스를 체크아웃하고, 취약한 버전으로 체크아웃 한 뒤 빌드합니다.

 

$ git clone https://github.com/php/php-src.git

$ cd php-src

$ git checkout php-7.2.25

$ ./buildconf --force

$ ./configure && make

 

제가 테스트한 코드와 디렉토리 구조는 아래와 같습니다. 테스트 결과에 보이는 것처럼 \x00 이후의 문자열이 제거되어 ./ddd 디렉토리까지만 파싱되는 것을 확인할 수 있습니다.

그렇다면 패치된 코드는 어떤 역할을 할까요 ? zend_parse_parameters에 들어가는 2번째 파라미터에서 's' 문자가 'p'로 변경되었습니다. PHP 공식 사이트에서 확인해보면 zend_parse_parameters는 사용자가 전달한 파라미터를 파싱하는 역할을 하는데, 이중에서 두번째 파라미터는 printf 같은 함수의 포멧스트링과 똑같다고 생각하면 됩니다. 지금 파싱하는 파라미터가 어떤 타입인지 정의하는 역할을 하는거죠. s는 string, 즉 파싱하려는 파라미터가 문자열일 때 사용하고, p는 파싱하려는 파리미터가 경로명일 때 사용합니다.

https://www.php.net/manual/en/internals2.funcs.php

 

그럼 zend_parse_parameters 함수는 두번째 파라미터를 변경하면, 어떤 동작을 하길래 취약점이 제거되는 것일까요 ? 답은 PHP의 Zend/zend_API.c 파일을 분석하면 찾을 수 있습니다. zend_parse_parameters 함수에 전달된 파리미터는 zend_parse_va_args -> zend_parse_arg -> zend_parse_arg_impl 함수에 전달됩니다. zend_parse_arg_impl 함수는 아래 그림에 보이는 것처럼 두번째 파라미터 값에 따라 분기하는 코드가 포함되어 있습니다. 두번째 파라미터에 'p' 문자가 있으면 zend_parse_arg_path 함수가 호출됩니다.

PHP 소스코드의 Zend/zend_API.c
gdb의 backtrace 로그

zend_parse_arg_path 함수는 파라미터에 전달된 값이 경로명인지 검증하는 함수인데, 이 취약점의 PoC에 사용된 것처럼 \x00 문자 사용해서 문자열을 임의로 자르는 행위를 방지하기 위해 CHECK_NULL_PATH 매크로를 사용해서 경로명에 \x00 문자가 포함되어 있는지 확인합니다.

zend_parse_arg_path_str 함수

CHECK_NULL_PATH 매크로는 strlen() 함수를 사용해서 구한 문자열 길이와 PHP 소스코드에서 전달한 문자열의 길이를 비교해서 두 값이 같으면 \x00 문자가 포함되지 않은 것이고, 두 값이 다를 경우 \x00 문자가 포함되어 있는 것으로 확인하는 매크로입니다. 즉, zend_parse_parameters 함수의 2번째 파라미터에 'p' 문자를 전달하면 경로명에 \x00 문자가 포함되어 있는지 확인하고, 예외처리하기 때문에 이 간단한 패치만으로 취약점이 해결되는 것입니다.

 

패치 후 동일한 코드를 테스트하면 아래와 같이 예외 처리 코드가 실행되는 것을 확인할 수 있습니다.

PHP에 새로운 기능이 추가되거나, 기존 코드가 변경될 때 zend_parse_parameters 함수의 두번째 파라미터가 잘 설정되었는지 확인해야겠네요 :)

 

두서 없지만... 또 이렇게 새로운 CVE 하나를 분석했습니다.