Skip to content

Classic Shell Scripting 讀書筆記 (八)

  • Ops

ksh 與 bash 的擴展與相容性

模式匹配的擴展

bash 要先啟用 extglob 選項才支援此功能

正則的 ^$ 在 Shell 的模式匹配沒有等同物,我們可以想像,在 Shell 的模式匹配中, ^$ 已經自動帶在前後文了,若在模式前後加上 * 則可以停用此功能

括弧展開

源自於 csh 的功能,ksh 跟 bash 都支持此用法,可以巢狀化

進程替換 (Process Substitution)

可以讓使用者開啟多個進程數據流,再把它們餵給單一程序處理,程序會依序處理每個數據流,不管數據是否來自多個不同來源

範例:用於輸入的進程替換

範例:用於輸出的進程替換

須與 tee 命令配合

image

索引式陣列

Shell 會自動評估表達式產生索引,但只支援一維陣列

可以當成是一個整數輸入變量的數學函數,會回傳一個對應的值,屬於「number-dominate」的資料結構

有三種方式可以將值指定給陣列元素:

  1. 使用標準 Shell 變量指定,指定的值都會被視為字串: names[1]=bob
  2. 使用 set 命令: set -A names bob rob jacob (注意 bash 不支援此語法)
  3. (建議使用)複合指定:names=(bob rob jacob)

要取出索引陣列中的值,語法為 ${names[i]},索引 i 可以是算術表達式。如果在索引處使用 *@ 則會是以空格隔開所有元素,。省略索引的話,等同是指定索引為 0

可以透過 "${names[@]}" (使用雙引號)保留陣列元素內的任何空白字符,類似我們會用 "$@" 而不是 $*"$*"

可以透過 ${#names[*]} 求出陣列中的元素數量,* 是必須的,因為忽略索引會解釋成第 0 個元素(此用法有可移植性的問題,但 bash、ksh 皆支持)

算式展開的循環用法

POSIX 把表達式 $((...)) 定義作算術展開,且有離開狀態(0 或 1),因此能在 if 和 while 語句下使用

即席字符串(here strings)

即席字符串使用 <<< 再接上字符串,字符串即成為相關命令的標準輸入,不用再建立額外的進程

Shell 初始化與終止

因應客製化,Shell 會在啟動、終止時,讀取某些特定文件,每個 Shell 各自有不同的慣例模式

如果腳本是處理一般性的功能,且希望能相容其他的 Shell,就不應該依賴 Shell 客製化的功能,相反的,應該在腳本中設置它們自己的環境(例如 $PATH 的值)

Shell 的行為模式取決於它是否為 login Shell,可以檢查 $0 的值來判斷,以- 開頭即為 login Shell,連字號並不是暗示有個文件叫 /bin/-bash,而是當父進程執行 exec() 啟動 Shell 時,是用它來設置第 0 個參數

Bourne Shell (sh)

是登錄 Shell 時,起始會嘗試讀取 /etc/profile$HOME/.profile,這兩個檔案不一定要存在

在離開時,不管是否為登錄 Shell,皆不會讀取標準的終結文件(要達到此效果,可以在初始化的腳本額外設置 trap 指令如: trap '. $HOME/.logout' EXIT),但不保證終結的腳本一定會被執行

由於沒有終結文件、不支援 history 指令等限制,Bourne Shell 不常在交互模式下被使用

Korn Shell (ksh)

是登錄 Shell 時,起始會嘗試讀取 /etc/profile$HOME/.profile

是交互模式的話,在啟動時讀取 $ENV 定義的文件

Bourne-Again Shell (bash)

很多系統上的 /bin/sh 僅為 /bin/bash 的連結(尤其是 GNU/Linux)

是登錄 Shell 時,起始會依序嘗試讀取 /etc/profile$HOME/.bash_profile$HOME/.bash_login,最後才是 $HOME/.profile;離開時會讀取 $HOME/.bash_logout

不是登錄 Shell 且是交互模式時,起始會嘗試讀取 $HOME/.bashrc

不是登錄 Shell 且不是交互模式時,起始會嘗試讀取 $BASH_ENV 定義的文件

Shell 的安全性

  • 不要將當前目錄(.)置於 $PATH 中:可執行程序應該只放在標準的系統目錄,避免木馬攻擊
  • 確保 $PATH 的每個目錄都只有該目錄擁有者可以寫入
  • 不要信任傳進來的環境變量:在變量被命令使用前(例如 TZPATHIFS),請檢查或重設為已知的值
  • 保留紀錄:使用 logger 或建立函數來保存日誌
  • 不要對輸入的參數使用 eval
  • 使用輸入參數時,一定要以雙引號引用:避免執行超出範圍的計算
  • 文件可能是連結:在 chmod 文件或編輯文件時,檢查它是否真的是一個文件,而非連結到某個關鍵系統文件
  • setgid 而不要用 setuid:若非必要,兩者最好都不要在 Shell 中使用
  • 使用新的使用者而不是 root:如果必須使用 setuid 訪問文件,請考慮用新建的使用者而非 root

受限制的 Shell

嚴格限制文件寫入與移動的環境,使用的對象通常是訪客 (guest) 帳號,常見的有 rkshrbash

常見的限制有:

  • 不允許變更工作目錄
  • 不允許重定向輸出到文件
  • 不允許指定新值給特定的環境變量
  • 只能執行 $PATH 可以找到的命令

木馬的運作機制

在 bin 目錄疏忽保護的情況下,依芙在愛麗絲的 bin 目錄下建立一個 grep 腳本:

當愛麗絲以自己身份做事時,這個腳本不會有危險。一旦愛麗絲切換身份到 root(假設她有權限),su 命令會繼承當前 PATH 的設置,導致 PATH 感染了 ~/alice/bin/grep

當惡意腳本執行結束,腳本將自己刪除,不留下任何證據

setuid 的漏洞

進程牽涉到兩個 User ID:

  • real user ID: 表示進程由「誰」控制,也就是 ps 命令列出的 USER 欄位
  • effective user ID: 表示進程使用「誰」的權限

setuid 是文件的一個設置權限的位元:當一個可執行的文件啟用 setuid,在執行時,effective user ID 會轉換成文件擁有者的 user ID,也就是「以文件擁有者的權限來執行」

可以想見,如果程序的擁有者是 root 又啟用了setuid ,駭客就有機會以與 root 相同權限執行任何命令

以下範例的代碼可以搭配木馬來攻擊,當 root 感染了此木馬,當駭客就能以交互模式執行 affected_sh,並以 root 權限做任何事了:

我們常見到的 Shell 腳本,會在開頭(Shebang 加上 shell 文件)後方帶上 -,也是為了防止類似(將開啟 setuid 的 Shell 轉成交互式的 Shell)的攻擊:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *