shell中實用eval命令和安全問題
eval命令非常強(qiáng)大,但也非常容易被濫用。
它會導(dǎo)致代碼被解析兩次而不是一次。這意味著,如果你的代碼中包含變量引用,shell解析器將評估該變量的內(nèi)容。如果變量包含一個shell命令,shell可能會運行該命令,無論你是否希望運行它。這可能會導(dǎo)致意外的結(jié)果,特別是當(dāng)變量可以從不受信任的來源(如用戶或用戶創(chuàng)建的文件)讀取時。
請注意,eval命令在編程中被廣泛認(rèn)為是危險的。它可以執(zhí)行任意的Shell代碼,包括惡意代碼,因此應(yīng)該謹(jǐn)慎使用。
Bash的名稱引用問題
Bash 4.3引入了declare -n("名稱引用")來模仿Korn shell的nameref??特性,允許變量保存對其他變量的引用。然而,Bash中使用的實現(xiàn)存在一些問題。
首先,Bash的declare -n??實際上并沒有避免名稱沖突問題:
$ foo() { declare -n v=$1; } $ bar() { declare -n v=$1; foo v; } $ bar v bash: warning: v: circular name reference
換句話說,我們無法給名稱引用指定一個安全的名稱。如果調(diào)用者的變量恰好具有相同的名稱,那就麻煩了。
其次,Bash的名稱引用實現(xiàn)仍然允許任意代碼執(zhí)行:
$ foo() { declare -n var=$1; echo "$var"; } $ foo 'x[i=$(date)]' bash: i=Thu Mar 27 16:34:09 EDT 2034: syntax error in expression (error token is "Mar 27 16:34:09 EDT 2023")
這個例子并不優(yōu)雅,但你可以清楚地看到date??命令實際上被執(zhí)行了。這絕不是我們想要的結(jié)果。
盡管存在這些缺點,declare -n??特性是朝著正確方向邁出的一步。但你必須小心選擇一個調(diào)用者不會使用的名稱(這意味著你需要對調(diào)用者有某種控制,即使只是告訴他們“不要使用以_my_pkg??開頭的變量”),并且必須拒絕不安全的輸入。
eval的良好使用示例
eval最常見的正確使用方式是從專門設(shè)計為以這種方式使用的程序輸出中讀取變量。例如,
# 在舊系統(tǒng)上,調(diào)整窗口大小后必須運行以下命令: eval "`resize`" # 更高級的用法:獲取SSH私鑰的密碼短語。 # 這通常從.xsession或.profile類型的文件執(zhí)行。 # ssh-agent生成的變量將被導(dǎo)出到用戶會話中的所有進(jìn)程,以便之后的ssh命令可以繼承這些變量。 eval "`ssh-agent -s`"
eval還有其他用途,特別是在創(chuàng)建變量時(參考indirect variable references ↗)。以下是一種解析不帶參數(shù)的命令行選項的示例:
# POSIX # # 動態(tài)創(chuàng)建選項變量。嘗試調(diào)用: # # sh -x example.sh --verbose --test --debug for i; do case $i in --test|--verbose|--debug) shift # 從命令行中移除選項 name=${i#--} # 刪除選項前綴 eval "$name=\$name" # 創(chuàng)建*新*變量 ;; esac done echo "verbose: $verbose" echo "test: $test" echo "debug: $debug"
那么,為什么這個版本是可接受的呢?這是因為我們限制了eval命令的使用,只有在輸入是一組有限的已知值之一時才會執(zhí)行。因此,用戶無法濫用它以導(dǎo)致任意命令執(zhí)行——任何包含奇怪內(nèi)容的輸入都不會匹配三個預(yù)定的可能輸入之一。
請注意,這仍然是不推薦的:這是一條很陡峭的道路,稍后的維護(hù)很容易將這段代碼變成危險的內(nèi)容。例如,你想要添加一個功能,允許傳遞一堆不同的--test-xyz選項。你將--test更改為--test-*,而不費力地檢查腳本的其他部分的實現(xiàn)。你測試你的用例,一切正常。不幸的是,你剛剛引入了任意命令執(zhí)行:
$ ./foo --test-'; ls -l /etc/passwd;x=' -rw-r--r-- 1 root root 943 2007-03-28 12:03 /etc/passwd
再次強(qiáng)調(diào):允許eval命令在未經(jīng)過濾的用戶輸入上使用會導(dǎo)致任意命令執(zhí)行。
盡一切可能避免將數(shù)據(jù)傳遞給eval,即使你的代碼似乎處理了所有邊界情況。
如果你經(jīng)過深思熟慮并向#bash尋求了替代方法,但沒有找到任何方法,請?zhí)?quot;Robust eval usage"部分。
使用declare的問題
使用declare能更好地完成這個任務(wù)嗎?
for i in "$@"; do case "$i" in --test|--verbose|--debug) shift # 從命令行中移除選項 name=${i#--} # 刪除選項前綴 declare $name=Yes # 設(shè)置默認(rèn)值 ;; --test=*|--verbose=*|--debug=*) shift name=${i#--} value=${name#*=} # value是第一個單詞后面的內(nèi)容和= name=${name%%=*} # 僅限于第一個單詞(即使值中有另一個=) declare $name="$value" # 創(chuàng)建*新*變量 ;; esac done
請注意,--name用于默認(rèn)值,--name=value是必需的格式。
以下是eval的一個良好使用示例,用于從專門設(shè)計為以這種方式使用的程序輸出中讀取變量:
# 在舊系統(tǒng)上,調(diào)整窗口大小后必須運行以下命令: eval "`resize`" # 更高級的用法:獲取SSH私鑰的密碼短語。 # 這通常從.xsession或.profile類型的文件執(zhí)行。 # ssh-agent生成的變量將被導(dǎo)出到用戶會話中的所有進(jìn)程,以便之后的ssh命令可以繼承這些變量。 eval "`ssh-agent -s`"
eval還可以用于創(chuàng)建變量時,尤其是在創(chuàng)建間接變量引用時。下面是一個解析不帶參數(shù)的命令行選項的示例:
# POSIX # # 動態(tài)創(chuàng)建選項變量。嘗試調(diào)用: # # sh -x example.sh --verbose --test --debug for i; do case $i in --test|--verbose|--debug) shift # 從命令行中移除選項 name=${i#--} # 刪除選項前綴 eval "$name=\$name" # 創(chuàng)建*新*變量 ;; esac done echo "verbose: $verbose" echo "test: $test" echo "debug: $debug"
盡管這個示例中的eval使用看起來安全,但仍然不推薦廣泛使用eval命令,因為它需要非常小心的輸入過濾和驗證,以避免任意命令執(zhí)行漏洞。盡量避免將數(shù)據(jù)傳遞給eval,并尋找替代方案,以增加腳本的安全性。
使用declare存在的問題
難道使用declare不能更好地解決這個問題嗎?
for i in "$@"; do case "$i" in --test|--verbose|--debug) shift # 從命令行中移除選項 name=${i#--} # 刪除選項前綴 declare $name=Yes # 設(shè)置默認(rèn)值 ;; --test=*|--verbose=*|--debug=*) shift name=${i#--} value=${name#*=} # 值是等號后面的內(nèi)容 name=${name%%=*} # 僅限于第一個單詞的名稱(即使值中還有另一個等號) declare $name="$value" # 創(chuàng)建*新的*變量 ;; esac done
請注意,默認(rèn)情況下,--name和--name=value是必需的格式。
對于某些輸入,declare確實可以更好地工作:
griffon:~$ name='foo=x;date;x' griffon:~$ declare $name=Yes griffon:~$ echo $foo x;date;x=Yes
但它仍然會導(dǎo)致數(shù)組變量中的任意代碼執(zhí)行:
attoparsec:~$ echo $BASH_VERSION 4.2.24(1)-release attoparsec:~$ danger='( $(printf "%s!\n" DANGER >&2) )' attoparsec:~$ declare safe=${danger} attoparsec:~$ declare -a unsafe attoparsec:~$ declare unsafe=${danger} DANGER!
這段代碼展示了使用declare可能引發(fā)的安全問題。在某些情況下,使用declare可能會導(dǎo)致任意代碼執(zhí)行,從而產(chǎn)生潛在的安全漏洞。在這個例子中,變量的值包含了一個命令,當(dāng)使用declare聲明變量時,該命令將被執(zhí)行。這可能導(dǎo)致不受信任的代碼執(zhí)行,從而引發(fā)安全問題。
為了確保腳本的安全性,應(yīng)該避免將不受信任的數(shù)據(jù)傳遞給declare命令。如果需要動態(tài)創(chuàng)建變量,可以考慮使用其他安全的方法或?qū)ふ姨娲桨?,以避免潛在的安全風(fēng)險。
強(qiáng)大的eval用法
幾乎總是(至少在Bash中99%或更多的時間內(nèi),但也適用于更簡潔的shell),正確地使用eval的方式是在庫代碼中生成隱藏在函數(shù)背后的抽象層。這允許函數(shù)具有以下功能:
- 向函數(shù)的調(diào)用者呈現(xiàn)一個明確定義的接口,指定哪些輸入必須由程序員嚴(yán)格控制,哪些可能是不可預(yù)測的,例如受用戶輸入影響的副作用。重要的是要記錄哪些選項和參數(shù)在沒有控制的情況下是不安全的。
- 對某些類型的輸入進(jìn)行輸入驗證,如果可行,例如整數(shù)。在這種情況下,可以輕松地退出并返回一個錯誤狀態(tài),該錯誤狀態(tài)可以由函數(shù)的調(diào)用者處理。
- 創(chuàng)建隱藏使用eval的丑陋實現(xiàn)細(xì)節(jié)的抽象。
通常,當(dāng)滿足以下至少全部條件時,eval是正確的:
- 可能的所有eval參數(shù)都保證不會在任何情況下產(chǎn)生有害的副作用或?qū)е氯我獯a的執(zhí)行。這些輸入是靜態(tài)編碼的,不與不受控制的動態(tài)代碼交互,并且/或經(jīng)過徹底驗證。這就是為什么函數(shù)很重要,因為你不一定需要自己保證這個保證。只要您的函數(shù)記錄了哪些輸入可能是危險的,您就可以將這個任務(wù)委托給函數(shù)的調(diào)用者。
- eval用法向用戶或程序員呈現(xiàn)了一個清晰的接口。
- eval使得原本不可能的事情成為可能,而無需編寫更大、更慢、更復(fù)雜、更危險、更丑陋、更不實用的代碼。
如果出于某種原因仍然需要動態(tài)構(gòu)建Bash代碼并評估它,請確保采取以下預(yù)防措施:
- 始終引用eval表達(dá)式:eval 'a=b'
- 始終使用單引號引用代碼,并使用printf的%q將數(shù)據(jù)擴(kuò)展到其中:eval "$(printf 'myvar=%q' "$value")"
- 不要使用動態(tài)變量名。即使使用了小心的%q用法,這也可能會被利用。
為什么要注意?如果未能遵循上述建議,以下是腳本可能會受到利用的示例:
- 如果不對代碼進(jìn)行單引號引用,則存在將數(shù)據(jù)擴(kuò)展到其中而沒有進(jìn)行%q處理的風(fēng)險。這意味著該數(shù)據(jù)可以自由執(zhí)行:
name='Bob; echo I am arbitrary code'; eval "user=$name"
- 即使在對輸入數(shù)據(jù)進(jìn)行%q處理之后再將其視為變量名進(jìn)行處理,如果賦值中存在非法變量名,Bash將會在PATH中搜索命令:
echo 'echo I am arbitrary code' > /usr/local/bin/a[1]=b; chmod +x /usr/local/bin/a[1]=b; var='a[1]' value=b; eval "$(printf '%q=%q' "$var" "$value")"
到此這篇關(guān)于shell中實用eval命令和安全問題的文章就介紹到這了,更多相關(guān)shell eval命令 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Shell腳本變量的只讀?刪除?類型及注釋語法基礎(chǔ)
這篇文章主要介紹了Shell腳本變量的只讀刪除類型及注釋語法基礎(chǔ)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05Shell腳本中的特殊字符(美元符、反斜杠、引號等)作用介紹
這篇文章主要介紹了Shell腳本中的特殊字符(美元符、反斜杠、引號等)作用介紹,還包括其它特殊字符,需要的朋友可以參考下2015-05-05解析Linux?xfs文件系統(tǒng)stat命令Birth字段為空的原因
這篇文章主要介紹了Linux?xfs文件系統(tǒng)stat命令Birth字段為空的原因探究,stat命令在一些平臺下Birth字段有值,而在一些平臺則為空值,這是什么原因呢,下面小編給大家詳細(xì)講解,需要的朋友可以參考下2023-05-05shell腳本編寫ping包及arp的監(jiān)控并發(fā)送短信功能
這篇文章主要介紹了shell腳本編寫ping包及arp的監(jiān)控并發(fā)送短信功能,需要的朋友可以參考下2017-03-03