Sté*_*las 17 security bash ksh zsh associative-array
一些类似 Bourne 的 shell 支持关联数组:(ksh93自 1993 年以来)、zsh(自 1998 年以来)、bash(自 2009 年以来),尽管 3 之间的行为存在一些差异。
一个常见的用途是计算某些字符串的出现次数。
但是,我发现以下内容:
typeset -A count(( count[$var]++ ))
对某些值不起作用$var,我听说如果 的内容处于或可能处于攻击者的控制之下,它甚至会构成任意命令执行漏洞$var。
这是为什么?有问题的价值观是什么?我该如何解决它?
Sté*_*las 19
问题在于,在 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]++ ))
Run Code Online (Sandbox Code Playgroud)
以上,除了 in ksh93,$var首先展开,然后解释结果。
这意味着如果$var包含@or *,则assoc[@]++orassoc[*]++表达式被评估,并且@/*在那里具有特殊含义。如果$var是x] + 2 + assoc[y,那就变成assoc[x] + 2 + assoc[y]。
现在通常情况下$(( $var )),即使$var包含类似 的内容$(reboot),也不会发生第二轮扩展,reboot也不会运行。但是,正如在 Shell 算术评估中使用未经处理的数据的安全性影响中已经看到的那样,如果出现在内部word[...]以允许递归扩展,则存在一个例外。问题的根源在于Korn shell 的一个不幸的特性,即如果var包含一个算术表达式,那么在$((var))算术表达式中 in$var正在被评估,甚至是递归的(比如 when var2='var3 + 1' var='var2 + 1'),这是允许的,但不是 POSIX 要求的。
由于这扩展到数组成员,这意味着数组索引的内容最终会被递归评估。因此,如果$var是$(reboot),则(( assoc[$var]++ ))最终调用reboot。
ksh93似乎有一定程度的解决方法,但只有当它似乎$var不包含$时。所以,虽然ksh93的是OK 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 )
Run Code Online (Sandbox Code Playgroud)
的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" )
Run Code Online (Sandbox Code Playgroud)
现在好了,但它并没有解决与问题@,*或]字符,所以它并没有解决任意命令执行漏洞:
$ var='x]+b[1$(uname>&2)' bash -O assoc_expand_once -c 'typeset -A a; ((a[$var]++)); typeset -p a'
Linux
declare -A a
Run Code Online (Sandbox Code Playgroud)
(这一次,uname作为普通数组 ( b) 索引评估的一部分运行)。
有问题的字符列表因外壳而异。$对于所有三个问题\,`,[并]是一个问题bash和zsh,",'的bash。同为@和*空值。另请注意,在某些语言环境中,某些字符的编码确实包含\,[或者]至少可能会导致问题。如何逃脱这些必须在所有三个 shell 中以不同的方式完成。
要解决它,可以这样做:
assoc[$var]=$(( ${assoc[$var]} + 1 ))
Run Code Online (Sandbox Code Playgroud)
反而。那是:
=, ++, --, +=, /=... 算术运算符与关联数组成员一起用作目标。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,调用了一元运算+符)。
其他方法 with bash(当该assoc_expand_once选项未启用时) or 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
Run Code Online (Sandbox Code Playgroud)
现在,这留下了bashshell特有的一个问题:bash关联数组不支持空键。虽然assoc[]=x在bashand zsh(not ksh93)中都失败了,但assoc[$var]when $varis empty 在zshorksh93但不是 中有效bash。即使是zsh的assoc+=('' value)现在的bash-5.1支持不工作,bash。
因此,如果bash专门使用并且如果空键是可能的值之一,则唯一的选择是添加固定的前缀/后缀。所以使用例如:
assoc[.$var]=$(( ${assoc[.$var]} + 1 ))
Run Code Online (Sandbox Code Playgroud)
或者:
let 'assoc[.$var]++'
(( assoc[.\$var]++ ))
...
Run Code Online (Sandbox Code Playgroud)