Bash

如何在算術表達式中安全地使用關聯數組?

  • January 5, 2021

一些類似 Bourne 的 shell 支持關聯數組:(ksh93自 1993 年以來)、zsh(自 1998 年以來)、bash(自 2009 年以來),儘管 3.

一個常見的用途是計算某些字元串的出現次數。

但是,我發現這樣的事情:

排版 -A 計數
(( count[$var]++ ))

不要為某些值工作,我聽說如果 的內容是或可能在攻擊者的控制之下,$var它甚至構成任意命令執行漏洞。$var

這是為什麼?有問題的價值觀是什麼?我該如何解決它?

問題在於,在 shell 算術表達式中,例如 inside $((...))(POSIX) 或((...))(ksh/bash/zsh),或某些 shell 內置函式或操作數的數組索引或參數,首先[[...]]執行單詞擴展 ( ${param}, $((...)), $[...], $(...), ..., ${ ...; }) ,然後將生成的文本解釋為算術表達式。

在 的情況下$((...)),這甚至是 POSIX 要求。

這允許事情像op=+; echo "$(( 1 $op 2 ))"工作,這解釋了為什麼a=1+1; echo "$(($a * 2))"輸出3而不是4,因為它1+1 * 2是被評估的表達式。

這也是為什麼在算術表達式中使用未經處理的數據通常是一個安全漏洞的部分原因。

容易被忽視的是它也適用於諸如

(( assoc[$var]++ ))

上面,除 in 外ksh93$var首先展開,並解釋結果。

這意味著如果$var包含@or *,則assoc[@]++orassoc[*]++表達式被評估,並且@/*在那裡具有特殊含義。如果$varx] + 2 + assoc[y,那就變成了assoc[x] + 2 + assoc[y]

現在通常,在 中$(( $var )),即使$var包含類似的內容$(reboot),也不會發生第二輪擴展,reboot也不會執行。但是正如在 Shell Arithmetic evaluation 中使用未經處理的數據的安全影響中已經看到的那樣,如果內部出現word[...]允許遞歸擴展,則會出現異常。問題的根源在於Korn shell 的一個不幸特性var,即 if包含算術表達式,然後在$((var))算術表達式中$var計算,甚至遞歸地(如 when var2='var3 + 1' var='var2 + 1'),這是允許的,但 POSIX 不需要。

當它擴展到數組成員時,這意味著數組索引的內容最終會被遞歸評估。因此,如果$varis $(reboot),則(( assoc[$var]++ ))最終呼叫reboot.

ksh93似乎有一定程度的解決方法,但只有在$var不包含$它的情況下。因此,雖然 ksh93 可以與var=']',var='@'或一起使用var='reboot',但它不適用於$(reboot).

例如,如果我們reboot用無害的替換uname>&2

$ var='1$(uname>&2)' ksh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
typeset -A a=([1]=1)
$ var='1$(uname>&2)' bash -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
declare -A a=([1]="1" )
$ var='1$(uname>&2)' zsh -c 'typeset -A a; (( a[$var]++ )); typeset -p a'
Linux
Linux
typeset -A a=( [1]=1 )

uname命令最終確實會執行(兩次在bash和中zsh,我想一次用於獲取目前值,第二次用於執行分配)。

在 5.0 版中,bash 添加了一個assoc_expand_once改變行為的選項:

$ var='1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
declare -A a=(["1\$(uname>&2)"]="1" )

現在可以了,但它沒有解決 , 或 字元的問題@*因此]它沒有解決任意命令執行漏洞:

$ var='x]+b[1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
Linux
declare -A a

(這一次,uname作為普通數組 ( b) 索引評估的一部分執行)。

有問題的字元列表因外殼而異。$是所有三個問題\,```,[]bashzsh"'對於的問題bash@*空值相同。另請注意,在某些語言環境中,某些字元的編碼確實包含\[或者]至少可能會導致問題。如何逃避這些必須在所有三個 shell 中以不同的方式完成。

要解決它,可以執行以下操作:

assoc[$var]=$(( ${assoc[$var]} + 1 ))

反而。那是:

  1. 不要將關聯數組成員的賦值作為算術表達式的一部分,而只執行裸關聯數組成員賦值。換句話說,不要使用帶有關聯數組成員的=, ++, --, +=, … 算術運算符作為目標。/=
  2. 在算術表達式中引用關聯數組時,不要使用assoc[$var], but ${assoc[$var]}(或$assoc[$var]in zsh),或者(${assoc[$var]})如果它意味著包含算術表達式而不僅僅是數字。

但是,與往常一樣,該關聯數組成員的必須在您的控制之下,最好是純數字,並且與任何其他參數擴展一樣,最好在周圍放置空格。例如,後者((1 - $var))更可取,((1-$var))因為後者會導致負值問題 (((1--1))在某些 shell 中導致語法錯誤,因為這是--運算符應用於1.

另一個需要注意的是 when$var是空的, in (( 1 + var )),那var仍然是算術表達式語法中的一個標記,以及對應的值 if 0。但是在 中(( 1 + $var )),算術表達式變成1 +了語法錯誤((( $var + 1 ))雖然可以,因為它變成+ 1了,呼叫一元運算+符)。

bash(當assoc_expand_once選項啟用時)或zsh(但不是ksh93仍然存在問題的]``\其他方法and characters)是將擴展延遲到上面提到的第二個遞歸解釋。

  • (( assoc[\$var]++ ))
  • let 'assoc[$var]++'(確保在此處使用引號)
  • incr='assoc[$var]++'; (($incr))(甚至((incr))
  • ((' assoc[$var]++ '))(( assoc['$var']++ ))bash僅)。

那些具有優勢或保留算術評估產生的退出狀態(如果非零則成功),因此可以執行以下操作:

if (( assoc[\$var]++ )); then
 printf '%s\n' "$var was already seen"
fi

bash現在,這留下了shell特有的一個問題:bash關聯數組不支持空鍵。雖然在and (not )assoc[]=x中都失敗了, when is empty 在or但不是。即使’s現在受 bash-5.1 支持,也不能在.bash``zsh``ksh93``assoc[$var]``$var``zsh``ksh93``bash``zsh``assoc+=('' value)``bash

因此,如果bash專門使用並且空鍵是可能的值之一,則唯一的選擇是添加固定的前綴/後綴。所以使案例如:

assoc[.$var]=$(( ${assoc[.$var]} + 1 ))

或者:

let 'assoc[.$var]++'
(( assoc[.\$var]++ ))
...

引用自:https://unix.stackexchange.com/questions/627474