Shellshock Attack Lab
前導知識
SHELL:指令行界面的解釋器
Linux下常見Shell:
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Bourne Again Shell(
)/bin/bash
檢視目前使用的shell是否是/bin/bash:
[email protected]:/usr/lib/cgi-bin$ echo $SHELL
/bin/bash
vim編輯shell腳本:
#!/bin/bash
echo "Hello World !"
可以使用 vi/vim 指令來建立檔案,擴充名為 sh(sh代表shell),#! 是一個約定的标記,它告訴系統這個腳本需要什麼解釋器來執行,即使用哪一種 Shell。
shellshock原理
[04/14/2021 00:28] [email protected]:~$ foo='() { echo "wdnmd"; } ;echo "8848";'
[04/14/2021 00:29] [email protected]:~$ echo $foo
() { echo "wdnmd"; } ;echo "8848";
[04/14/2021 00:29] [email protected]:~$ export foo
[04/14/2021 00:29] [email protected]:~$ bash
8848
[04/14/2021 00:29] [email protected]:~$ declare -f foo
foo ()
{
echo "wdnmd"
}
foo本來是一個shell變量,但是在export foo使其成為環境變量後,在子shell程序中,foo成為了一個函數,并且指令行自動執行了echo "8848"的指令
分析:
foo='() { echo "wdnmd"; } ;echo "8848";'
foo () { echo "wdnmd"; } ;echo "8848";
foo變量的字元串被解釋成了兩個指令,分别是聲明了一個函數foo 和 輸出字元串 8848
檢視variables.c的源碼:
可以看到,隻要子程序在傳遞父程序的環境變量的時候,比對到
() {
這四個字元,就會将其解釋為函數
此後,倘若該環境變量字元串包含多個用分号
;
隔開的shell指令,
parse_and_execute
函數會執行每一條指令

是以,對于滿足shellshock的情景,我們可以構造出一個特殊的字元串,使得字元串前面部分是空函數定義,後面是用分号
;
隔開的惡意指令
- 一個攻擊者能夠控制的環境變量,該變量以 () { 開始。
- 必須調用bash
- 系統存在bash漏洞
Task 1: Attack CGI programs
-
CGI(通用網關接口,Common Gateway Interface)
一個在Web伺服器中使用的技術,是最早的可以建立動态網頁内容的技術之一。它會把一個HTTP請求轉化為一次shell調用。
-
Apache HTTP Server
世界上最為廣泛使用的Web伺服器軟體,使用了UNIX shell 環境變量來儲存從Web伺服器傳遞出去的參數
(注:Apache不是指阿帕奇武裝直升機)
原理
當使用者将CGI URL發送到Apache Web伺服器時,Apache将檢查該請求
如果是CGI請求,Apache将使用fork()啟動新程序,然後使用exec())函數執行CGI程式
Shellshock的原理是利用了Bash在導入環境變量函數時候的漏洞,啟動Bash的時候,它不但會導入這個函數,而且也會把函數定義後面的指令執行。在有些CGI腳本的設計中,資料是通過環境變量來傳遞的,這樣就給了資料提供者利用Shellshock漏洞的機會。
實驗步驟
/usr/lib/cgi-bin
目錄下使用vim編輯檔案
myprog.cgi
,并設定755權限
#!/bin/bash
echo "Content-type: text/plain"
echo
echo
echo "wdnmd"
#!
告訴系統其後路徑所指定的程式即是解釋此腳本檔案的 Shell 程式
echo "Content-type: text/plain"
是告訴shell解析的時候保持為純文字,不用其他的解釋型語言來解析
使用
ifconfig
指令得到ip位址為
192.168.32.134
為了通路該cgi程式,在指令行中輸入:(curl 是發起http請求的指令行工具)
curl http://localhost/cgi-bin/myprog.cgi
用浏覽器打開也可以通路該cgi程式:
阿帕奇伺服器從http請求中獲得user_agent的資訊并将它指派給http_user_agent環境變量,而curl -A可以更改user_agent字段。是以,我們可以通過curl -A指令修改該字段進而發起攻擊
使用curl指令構造攻擊,檢視/
etc/passwd
目錄下的内容
task2 Attack Set-UID programs
task2A
#include <stdio.h>
void main()
{
setuid(geteuid()); // make real uid = effective uid.
system("/bin/ls -l");
}
讓
/bin/sh
指向
/bin/bash.
sudo ln -sf /bin/bash /bin/sh
編譯檔案,設為root所有,SUID權限
gcc task2.c -o task2
sudo chown root task2
sudo chmod 4755 task2
建立環境變量foo
輸入
./t
,system函數通過
fork()
函數建立子程序,并得到父程序的環境變量,bash被調用并執行放在末尾的
/bin/sh
指令
通過攻擊,得到一個所有者為root的
Set-UID
程式
task2B
移除
setuid(geteuid())
将無法擷取root權限
setuid(geteuid())
語句将真實的UID轉換為有效的UID,當UID和EUID相同的時候(詳見教材第一章,p20),privmod==0,才會執行環境變量中構造的函數,詳見源代碼中第342行的if語句
教材p20
當UID和EUID不相等時,privmode不為0,導緻不能執行環境變量中構造的函數
Task 2C
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
char ** environ;
int main()
{
char * argv[3];
argv[0] = "/bin/ls";
argv[1] = "-l";
argv[2] = NULL;
setuid(geteuid()); // make real uid = effective uid.
execve(argv[0], argv, environ);
return 0 ;
}
編譯代碼,并檔案成為root擁有的set-uid程式
輸出了ls -l的結果,但是沒有獲得root權限
這是因為,不同于
system()
函數調用子bash程式作為單獨的程序執行指令,
execve()
函數接受三個參數:1 運作的指令,2 指令用到的參數,3 傳入新程式的環境變量,該函數會直接請求作業系統(而不是shell程式執行指定的指令),是以不會執行foo後面的
/bin/sh
task3
受shellshock攻擊滿足的條件:
- 一個攻擊者能夠控制的環境變量,該變量以 () { 開始。
- 必須調用bash
- 系統存在bash漏洞
其他可能受到ShellShock攻擊的情景
-
DHCP用戶端
一些DHCP用戶端也可以将指令傳遞給Bash;連接配接到開放的Wi-Fi網絡時,易受攻擊的系統可能受到攻擊。DHCP用戶端通常從DHCP伺服器請求并擷取IP位址,但也可以提供一系列附加選項。惡意DHCP伺服器可以在這些選項之一中提供一個字元串,用于在易受攻擊的工作站或筆記本電腦上執行代碼
-
Qmail伺服器
當使用Bash處理電子郵件時,郵件伺服器通過一種可以利用脆弱版本的Bash的方式傳遞外部輸入。
ShellShock漏洞的根本問題是什麼?我們可以從這個漏洞中學到什麼?
- 根本問題在于在一定條件下,子程序會把父程序的環境變量解釋為一個函數,并且通過
函數執行用分号分割的指令,是以會受到被精心構造的指令的攻擊parse_and_execute()
附錄
shellshock 308~369行
initialize_shell_variables (env, privmode)
char **env;
int privmode;
{
char *name, *string, *temp_string;
int c, char_index, string_index, string_length;
SHELL_VAR *temp_var;
create_variable_tables ();
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) */
temp_var = (SHELL_VAR *)NULL;
/* 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);
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] = '('; /* ) */
}