bash代碼注入的安全漏洞

本文轉載於:http://www.kankanews.com/ICkengine/archives/182068.shtml


很多人或許對上半年發生的安全問題“心臟流血”(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)

也就是下面這段測試代碼(注意,其中的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,你應該明白這個漏洞是怎麼一回事了吧。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章