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/