相信部分讀者應該用過 Metasploit 這套滲透測試框架。它整合了許多現成的漏洞利用與測試模組,讓我們可以選擇適合的模組、設定必要參數,嘗試驗證目標是否存在漏洞。不過,操作上的方便,也容易讓我們跳過一個重要問題:這個漏洞到底為什麼能成立?
就像昨天提到的,不能看到 CVSS 高分,就直接把它當成攻擊入口。當我們發現目標服務的版本可能受到某個 CVE 影響時,也應該先釐清:漏洞需要哪些觸發條件?攻擊者必須能控制什麼輸入?目前的環境是否真的符合這些條件?
今天,我們將以 Shellshock 的 CVE-2014-6271 為例,在容器 Lab 中重現漏洞。我們會先觀察 Bash 如何處理環境變數,再追查原本應該只是資料的內容,為什麼會變成被執行的指令,逐步理解漏洞的根本原因(Root Cause)。

簡單說一下 CVE-2014-6271 的原因,在 Bash 裡面可以用這種方式宣告函式:
'函式名稱'() { '函式功能'; }

上圖中,我們先定義了一個名為 foo 的函式,函式的內容是執行 echo hi。定義函式時,裡面的指令不會立即執行;直到我們在 Shell 中輸入 foo 呼叫這個函式,才會輸出 hi。
而 Bash 除了在互動介面裡定義函式,也能透過環境變數把函式傳給之後啟動的 Bash。問題就出在這個「匯入函式」的過程:GNU Bash 4.3 及更早的所有版本有一個邏輯錯誤——匯入時本該只處理到函式定義的 } 為止,有漏洞的版本卻會繼續往後解析、把夾帶在後面的指令也一起執行。
我們用 Docker Lab 來演示一遍:
docker exec day27-shellshock env 'x=() { :;}; echo VULNERABLE' bash -c 'echo hi'

這條指令用 env 設定一個環境變數 x,它的值是一段長得像函式的字串 () { :;}; echo VULNERABLE,再帶著這個環境啟動一個新的 Bash 執行 echo hi。值可以拆成兩部分:
() { :;}:一個什麼都不做的空函式(: 是不做事的指令); echo VULNERABLE:接在函式後面、額外夾帶的指令可以看到透過 bash -c 建立了一個新的 bash,而這個這個新的 bash 繼承到 env 所設定的環境變數 x 加上漏洞版本的邏輯錯誤導致 echo VULNERABLE 被解析成指令執行。
上圖可以看到 Docker 的環境是模擬出來有漏洞版本的 shell,跟已經修復版本的 shell 兩者執行的差別在哪裡。
參考來源:CVE-2014-6271 Detail
先看一下官網的說明內容:
官方文件說明到「Apache 的 mod_cgi 和 mod_cgid 也會變成這個 CVE 的攻擊管道」,因此我們在本機上面建立一個跟 CGI 串接的腳本,而它串接的就是我們的主角-bash。
我們先看一下正常的請求長什麼樣子:
curl -s -H 'User-Agent: normal-browser' http://127.0.0.1:8088/cgi-bin/vulnerable

我們透過利用修改 User-Agent 的方式將我們的惡意腳本傳送給 bash,由於在 Apache 的文件說明有記載「 HTTP 標頭會透過環境變數,傳給 CGI 腳本等程式」。 所以我們使用下面這個指令試探一下攻擊路徑是否成功?
curl -s -H 'User-Agent: () { :;}; echo; echo; /bin/bash -c "id"' \
http://127.0.0.1:8088/cgi-bin/vulnerable

參考來源:Environment Variables in Apache httpd
可以發現我們成功拿到了現在的使用者資訊,接著我們嘗試看看能不能閱讀到 /etc/passwd
curl -s -H 'User-Agent: () { :;}; echo; echo; /bin/bash -c "head -1 /etc/passwd"' \
http://127.0.0.1:8088/cgi-bin/vulnerable

可以看到我們已經讀到檔案了,這樣就是一個基本認識 CVE 重建漏洞與 Root Cause。
實作 CVE 重建漏洞與 Root Cause 其實是一個非常重要的一環,如果不理解 CVE 原理的話其實很難打在點上。一部分人會因為自動化工具有現成的可以使用,導致忽略掉這個最珍貴的學習階段久而久之便成了腳本小子。這點在紅隊的養成上完全沒有任何幫助,因為這意味著換一個條件跟環境你就沒有辦法進行滲透測試了。透過今天的主題其實也在勉勵我自己,學習的過程很辛苦,但就是因為這份辛苦才可以造就未來更強大的自己。
感謝大家的收看,我們明天見!