在 Shell 算术评估中使用未经处理的数据的安全影响

gar*_*Red 29 shell security arithmetic

在对最近问题评论中,Stéphane Chazelas 提到双括号算术存在安全隐患,例如:

x=$((1-$x))
Run Code Online (Sandbox Code Playgroud)

在大多数贝壳上。

我的谷歌技能似乎生疏了,我找不到任何东西。双括号算术的安全含义是什么?

Sté*_*las 32

问题在于 的内容$x尚未清理并且包含可能在攻击者控制之下的数据的情况下,如果 shell 代码可能最终被用于权限提升上下文(例如由 setuid 调用的脚本)应用程序、sudoers 脚本或用于直接或间接处理离线数据(CGI、DHCP 钩子...))。

如果:

x='(PATH=2)'
Run Code Online (Sandbox Code Playgroud)

然后:

x=$((1-$x)))
Run Code Online (Sandbox Code Playgroud)

具有设置PATH2(很可能受攻击者控制的相对路径)的副作用。您可以替换PATHLD_LIBRARY_PATHIFS...x=$((1-x))在 bash、zsh 或 ksh 中也会发生同样的情况(不是 dash 或 yash,它们只接受变量中的数字常量)。

注意:

x=$((1-$x))
Run Code Online (Sandbox Code Playgroud)

$x在某些实现(根据 POSIX 可选)--(递减)运算符的shell 中,对于负值将无法正常工作(与 一样x=-1,这意味着要求 shell 评估1--1算术表达式)。"$((1-x))"没有问题,因为x作为算术评估的一部分(不是之前)进行了扩展。

bash, zshand ksh(not dashor yash), ifx是:

x='a[0$(uname>&2)]'
Run Code Online (Sandbox Code Playgroud)

然后扩展$((1-$x))or$((1-x))导致该uname命令被执行(for zsh,a需要是一个数组变量,但可以使用psvar例如)。

总之,不应在 shell 的算术表达式中使用未初始化或未清理的外部数据。

请注意,算术计算可以通过$((...))(又名$[...]inbashzsh)完成,但也取决于let[/ testdeclare/typeset/export...returnbreakcontinueexitprintfprint内置函数、数组索引((..))[[...]]构造中的外壳,仅举几例)。

因为它适用于数组索引中ksh/ zsh/ bash,它也适用于需要变量名作为参数所有内建([/test-vreadunsetexport/ typeset/ readonlyprint/printf-vgetopts...)。

数字测试运算符的操作数被视为带有[[...]]和不带有[/test内置的算术表达式这一事实是为什么 inbashzsh,通常最好使用后者的原因之一。

相比:

$ a='x[1$(uname>&2)]' bash -c '[ "$a" -eq "$b" ]'
bash: line 0: [: x[1$(uname>&2)]: integer expression expected</pre>
Run Code Online (Sandbox Code Playgroud)

(安全)与:

$ a='x[1$(uname>&2)]' bash -c '[[ "$a" -eq "$b" ]]'
Linux
Run Code Online (Sandbox Code Playgroud)

uname被处决)。

(中ksh,双方[[[ ... ]]有问题)

要检查变量是否包含文字十进制整数,您可以使用 POSIXly:

case $var in
  ("" | - | *[!0123456789-]* | ?*-*) echo >&2 not a valid number; exit 1;;
esac
Run Code Online (Sandbox Code Playgroud)

要注意的是[0-9],并[[:digit:]]在某些地区超过0123456789比赛所以应该避免任何形式的输入验证/禁制的。

还要记住,在某些情况下,带前导零的数字被视为八进制(010有时是 10,有时是 8),并注意上面的检查将允许可能大于系统支持的最大整数的数字(或任何您将使用的应用程序)使用该整数;例如,bash 将 18446744073709551616 视为 0,因为它是 2 64)。因此,您可能希望在上面的 case 语句中添加额外的检查,例如:

(0?* | -0?*)
  echo >&2 'Only decimal numbers without leading 0 accepted'; exit 1;;
(-??????????* | [!-]?????????*)
  echo >&2 'Only numbers from -999999999 to 999999999 supported'; exit 1;;
Run Code Online (Sandbox Code Playgroud)

例子:

$ export 'x=psvar[0$(uname>&2)]'
$ ksh93 -c 'echo "$((x))"'
Linux
ksh93: psvar: parameter not set
$ ksh93 -c '[ x -lt 2 ]'
Linux
ksh93: [: psvar: parameter not set
$ bash -c 'echo "$((x))"'
Linux
0
$ bash -c '[[ $x -lt 2 ]]'
Linux
$ bash -c 'typeset -i a; export a="$x"'
Linux
$ bash -c 'typeset -a a=([x]=1)'
Linux
$ bash -c '[ -v "$x" ]'
Linux
$ bash -c 'read "$x"' < /dev/null
Linux
$ env psvar= bash -c 'unset "$x"'
Linux
$ mksh -c '[[ $x -lt 2 ]]'
Linux
$ zsh -c 'echo "$((x))"'
Linux
0
$ zsh -c 'printf %d $x'
Linux
0
$ zsh -c 'integer x'
Linux
$ zsh -c 'exit $x'
Linux
Run Code Online (Sandbox Code Playgroud)

更多阅读: