很多人或許對上半年發生的安全問題“心臟流血”(Heartbleed Bug)事件記憶頗深,這兩天,又出現了另外一個“毀滅級”的漏洞——Bash軟件安全漏洞。這個漏洞由法國GNU/Linux愛好者Stéphane Chazelas所發現。隨后,美國電腦緊急應變中心(US-CERT)、紅帽以及多家從事安全的公司于周三(北京時間9月24日)發出警告。 關于這個安全漏洞的細節可參看美國政府計算安全的這兩個漏洞披露:CVE-2014-6271 和 CVE-2014-7169。這個漏洞其實是非常經典的“注入式攻擊”,也就是可以向 bash注入一段命令,從bash1.14 到4.3都存在這樣的漏洞。我們先來看一下這個安全問題的癥狀。
Shellshock (CVE-2014-6271)
下面是一個簡單的測試:
$ env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo Bash Test"
如果你發現上面這個命令在你的bash下有這樣的輸出,那你就說明你的bash是有漏洞的:
Bash is vulnerable! Bash Test
簡單地看一下,其實就是向環境變量中注入了一段代碼 echo Bash is vulnerable。關于其中的原理我會在后面給出。
很快,CVE-2014-6271的官方補丁出來的了——Bash-4.3 Official Patch 25。
AfterShock – CVE-2014-7169 (又叫Incomplete fix to Shellshock)
但隨后,馬上有人在Twitter上發貼——說這是一個不完整的fix,并給出了相關的攻擊方法。
也就是下面這段測試代碼(注意,其中的sh在linux下等價于bash):
env X='() { (a)=>\' sh -c "echo date"; cat echo
上面這段代碼運行起來會報錯,但是它要的就是報錯,報錯后會在你在當前目錄下生成一個echo的文件,這個文件的內容是一個時間文本。下面是上面 這段命令執行出來的樣子。
$ env X='() { (a)=>\' sh -c "echo date"; cat echo sh: X: line 1: syntax error near unexpected token `=' sh: X: line 1: `' sh: error importing function definition for `X' Sat Sep 27 22:06:29 CST 2014
這段測試腳本代碼相當的詭異,就像“天書”一樣,我會在后面詳細說明這段代碼的原理。
原理和技術細節
要說清楚這個原理和細節,我們需要從 bash的環境變量開始說起。
bash的環境變量
環境變量大家知道吧,這個不用我普及了吧。環境變量是操作系統運行shell中的變量,很多程序會通過環境變量改變自己的執行行為。在bash中要定義一個環境變量的語法很簡單(注:=號的前后不能有空格):
$ var="hello world"
然后你就可以使用這個變量了,比如:echo $var什么的。但是,我們要知道,這個變量只是一個當前shell的“局部變量”,只在當前的shell進程中可以訪問,這個shell進程fork出來的進程是訪問不到的。
你可以做這樣的測試:
$ var="hello coolshell" $ echo $var hello coolshell $ bash $ echo $var
上面的測試中,第三個命令執行了一個bash,也就是開了一個bash的子進程,你就會發現var不能訪問了。為了要讓shell的子進程可以訪問,我們需要export一下:
$ export var="hello coolshell"
這樣,這個環境變量就會在其子進程中可見了。如果你要查看一下有哪些環境變量可以在子進程中可見(也就是是否被export了),你可使用env命令。不過,env命令也可以用來定義export的環境變量。如下所示:
$ env var="hello haoel"
有了這些基礎知識還不夠,我們還要知道一個基礎知識——shell的函數。
bash的函數
在bash下定義一個函數很簡單,如下所示:
$ foo(){ echo "hello coolshell"; } $ foo hello coolshell
有了上面的環境變量的基礎知識后,你一定會想試試這個函數是否可以在子進程中調用,答案當然是不行的。
$ foo(){ echo "hello coolshell"; } $ foo hello coolshell $ bash $ foo bash: foo: command not found
你看,和環境變量是一樣的,如果要在子進程中可以訪問的話,那么,還是一樣的,需要export,export有個參數 -f,意思是export一個函數。如:
$ foo(){ echo "hello coolshell"; } $ foo hello coolshell $ export -f foo $ bash $ foo hello coolshell
好了,我講了這么半天的基礎知識,別煩,懂了這些,你才會很容易地理解這兩個漏洞是怎么回事。
好,現在要進入正題。
bash的bug
從上面我們可以看到,bash的變量和函數用了一模一樣的機制,如果你用env命令看一下export出來的東西,你會看到上面我們定義的變量和函數都在,如下所示(我省略了其它的環境變量):
$ env var=hello coolshell foo=() { echo "hello coolshell" }
原來,都用同樣的方式啊——無論是函數還是變量都是變量啊。于是,看都不用看bash的源代碼,聰明的黑客就能猜得到——bash判斷一個環境變量是不是一個函數,就看它的值是否以”()”開始。于是,一股邪念涌上心頭。
黑客定義了這樣的環境變量(注:() 和 { 間的空格不能少):
$ export X='() { echo "inside X"; }; echo "outside X";'
env一下,你會看到X已經在了:
$ env X=(){ echo "inside X"; }; echo "outside X";
然后,當我們在當前的bash shell進程下產生一個bash的子進程時,新的子進程會讀取父進程的所有export的環境變量,并復制到自己的進程空間中,很明顯,上面的X變量的函數的后面還注入了一條命令:echo “outside X”,這會在父進程向子進程復制的過程中被執行嗎?(關于fork相關的東西你可以看一下我以前寫的《fork的一個面試題》)
答案是肯定的。
$ export X='() { echo "inside X"; }; echo "outside X";' $ bash outside X
你看,一個代碼注入就這樣完成了。這就是bash的bug—— 函數體外面的代碼被默認地執行了。
我們并不一定非要像上面那樣創建另一個bash的子進程,我們可以使用bash -c的參數來執行一個bash子進程命令。就像這個安全漏洞的測試腳本一樣:
env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo Bash Test"
其中,() { :;} 中的冒號就相當于/bin/true,返回true并退出。而bash -c其實就是在spawn一個bash的echo的子進程,用于觸發函數體外的echo命令。所以,更為友好一點的測試腳本應該是:
env VAR='() { :;}; echo Bash is vulnerable!' bash -c "echo 如果你看到了vulnerable字樣說明你的bash有安全問題"
OK,你應該明白這個漏洞是怎么一回事了吧。
bash漏洞的影響有多大
在網上看到好多人說這個漏洞不大,還說這個事只有那些陳舊的執行CGI腳本的網站才會有,現在已經沒有網站用CGI了。我靠,這真是無知者無畏啊。
我舉個例子,如果你的網站中有調用操作系統的shell命令,比如你用PHP執行個exec之類的東西。這樣的需求是有的,特別是對于一些需要和操作系統交互的重要的后臺用于系統管理的程序。于是就會開一個bash的進程來執行。
我們還知道,現在的HTTP服務器基本上都是以子進程式的,所以,其中必然會存在export 一些環境變量的事,而有的環境變量的值是從用戶端來的,比如:HTTP_USER_AGENT這樣的環境變量,只由瀏覽器發出的。其實這個變量你想寫成什么就寫成什么。
于是,我可以把這個HTTP_USER_AGENT的環境變量設置成上述的測試腳本,只不過,我會把echo Bash is vulnerable!這個東西換成別的更為兇殘的命令。呵呵。
關于這個漏洞會影響哪些已有的系統,你可以自己Google,幾乎所有的報告這個漏洞的文章都說了(比如:這篇,這篇),我這里就不復述了。
注:如果你要看看你的網站有沒有這樣的問題,你可以用這個在線工具測試一下:‘ShellShock’ Bash Vulnerability CVE-2014-6271 Test Tool。
現在,你知道這事可能會很大了吧。還不趕快去打補丁。(注,yum update bash 把bash版本升級到 4.1.2-15.el6_5.2 , )
關于 AfterShock – CVE-2014-7169 測試腳本的解釋
很多同學沒有看懂下面這個測試腳本是什么意思,我這里解釋一下。
env X='() { (a)=>\' sh -c "echo date"; cat echo
X='() { (a)=>\’ 這個不用說了,定義一個X的環境變量。但是,這個函數不完整啊,是的,這是故意的。另外你一定要注意,\’不是為了單引號的轉義,X這個變量的值就是 () { (a)=>\
-
其中的 (a)=這個東西目的就是為了讓bash的解釋器出錯(語法錯誤)。
-
語法出錯后,在緩沖區中就會只剩下了 “>\”這兩個字符。
-
于是,這個神奇的bash會把后面的命令echo date換個行放到這個緩沖區中,然后執行。
相當于在shell 下執行了下面這個命令:
$ >\ echo date
如果你了解bash,你會知道 \ 是用于命令行上換行的,于是相當于執行了:這不就是一個重定向么?上述的命令相當于:
$ date > echo
于是,你的當前目錄下會出現一個echo的文件,這個文件的內容就是date命令的輸出。
能發現這個種玩法的人真是個變態,完全是為bash的源代碼量身定制的一個攻擊。
原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2034