前言
首先,我想这不是解决问题的正确方法。这有点像说“ 您不应该谋杀人民,否则会入狱 ”。
同样,您不要引用变量,因为否则会引入安全漏洞。您引用变量是因为不这样做是错误的(但是如果担心入狱会有所帮助,为什么不这样做)。
刚上火车的人的摘要。
在大多数shell中,不加引号的可变扩展名(尽管(以及该答案的其余部分)也适用于命令替换(`...`
或$(...)
)和算术扩展($((...))
或$[...]
))具有非常特殊的含义。描述它的最好方法是就像调用某种隐式split + glob运算符¹。
cmd $var
用另一种语言写的像是:
cmd(glob(split($var)))
$var
首先根据涉及$IFS
特殊参数的复杂规则将其拆分为单词列表(拆分部分),然后将由此拆分产生的每个单词视为一种模式,然后扩展为与其匹配的文件列表(全局部分) 。
举个例子,如果$var
包含*.txt,/var/*.xml
与$IFS
包含,
,cmd
将与多个参数,第一个是所谓的cmd
,下的人是在txt
当前目录中的文件和xml
文件中/var
。
如果cmd
只想使用两个文字参数cmd
和进行调用*.txt,/var/*.xml
,则可以编写:
cmd "$var"
用您其他熟悉的语言:
cmd($var)
我们所说的外壳漏洞是什么意思?
毕竟,从很早就知道,不应在安全敏感的上下文中使用shell脚本。当然,好的,不加引号的变量是一个错误,但是不会造成太大的危害,可以吗?
好吧,尽管事实上有人会告诉您不要将Shell脚本用于Web CGI,或者值得庆幸的是,当今大多数系统都不允许使用setuid / setgid shell脚本,这是shellshock的一件事(可远程利用的bash错误导致2014年9月的头条新闻)揭示了shell仍在可能不应该使用的地方得到广泛使用:在CGI,DHCP客户端钩子脚本,sudoers命令中,由 setuid命令调用(如果不是as ...)
有时在不知不觉中。例如system('cmd $PATH_INFO')
在php
/ perl
/ python
CGI脚本执行调用一个shell来解释命令行(更不用说事实,cmd
本身可能是一个shell脚本,它的作者可能从未预料到从CGI调用)。
当存在特权升级的路径时,即有人(让他称为攻击者)能够执行他无意做的事情时,您就会遇到漏洞。
这总是意味着攻击者提供数据,而该数据正在由特权用户/进程处理,而该用户/进程无意中执行了某些错误,而该错误在大多数情况下是不应该做的。
基本上,当您的错误代码在攻击者的控制下处理数据时,您会遇到问题。
现在,数据的来源并不总是很明显,而且通常很难判断您的代码是否能够处理不受信任的数据。
就变量而言,就CGI脚本而言,很明显,数据是CGI GET / POST参数以及cookie,path,host ...等参数。
对于setuid脚本(在被另一个用户调用时以一个用户身份运行),它是参数或环境变量。
另一个非常常见的向量是文件名。如果您从目录中获取文件列表,则可能是攻击者将文件植入了该目录中。
就这一点而言,即使在交互式shell提示下,您也可能容易受到攻击(
例如,在其中/tmp
或其中处理文件时~/tmp
)。
甚至a ~/.bashrc
都可能是易受攻击的(例如,
在服务器部署中bash
被调用ssh
以在客户端控制下运行带有某些变量的ForcedCommand
类似对象时,将对其进行解释git
)。
现在,可能不会直接调用脚本来处理不受信任的数据,但是可能会被另一个执行该命令的命令调用。否则,您的错误代码可能会被复制粘贴到可以执行此操作的脚本中(由您自己决定三年或您的一位同事)。尤其重要的地方是问答网站中的答案,因为您永远不会知道代码的副本可能在哪里结束。
正事;有多糟
到目前为止,与外壳程序代码相关的安全漏洞的头号来源是不加引号的变量(或命令替换)未被引用。一方面是因为这些错误通常会转化为漏洞,另一方面是因为经常看到未引用的变量。
实际上,在寻找Shell代码中的漏洞时,要做的第一件事就是寻找未加引号的变量。很容易发现,通常是一个很好的候选人,通常很容易追溯到攻击者控制的数据。
不带引号的变量有多种方法可以转化为漏洞。我只在这里给出一些共同的趋势。
信息披露
大多数人会因为分割而碰到与未引用变量相关联的错误(例如,如今文件名中经常有空格,而空格是IFS的默认值)。许多人会忽略
球状部分。的水珠部分是至少与作为危险
分割部分。
在未经过滤的外部输入下进行通配,意味着攻击者可以使您读取任何目录的内容。
在:
echo You entered: $unsanitised_external_input
如果$unsanitised_external_input
包含/*
,则表示攻击者可以看到的内容/
。没什么大不了。但是,它变得更加有趣,/home/*
它为您提供了机器上的用户名列表/tmp/*
, /home/*/.forward
以提示其他危险行为,/etc/rc*/*
启用的服务……无需单独命名它们。值/*
/*/* /*/*/*...
将仅列出整个文件系统。
拒绝服务漏洞。
与前面的案例相距太远,我们有了DoS。
实际上,列表上下文中任何未引用输入的未引用变量至少是一个DoS漏洞。
即使是专业的Shell脚本编写者,也通常会忘记引用以下内容:
#! /bin/sh -
: ${QUERYSTRING=$1}
:
是no-op命令。可能出什么问题了?
这是要分配$1
给$QUERYSTRING
if $QUERYSTRING
未设置。这也是使CGI脚本也可以从命令行调用的快速方法。
这$QUERYSTRING
仍然是虽然扩大,因为它没有被引用,那么拆分+水珠操作符被调用。
现在,有一些全局扩展尤其昂贵。在/*/*/*/*
一个已经够糟糕了,因为这意味着上市目录,多达4倍的水平下降。除了磁盘和CPU活动外,这还意味着存储成千上万个文件路径(这里在最小的服务器VM上为40k,其中有10k为目录)。
现在/*/*/*/*/../../../../*/*/*/*
意味着40k x 10k,
/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*
足以让最强大的机器瘫痪。
自己尝试一下(尽管为机器崩溃或挂起做好准备):
a='/*/*/*/*/../../../../*/*/*/*/../../../../*/*/*/*' sh -c ': ${a=foo}'
当然,如果代码是:
echo $QUERYSTRING > /some/file
然后,您可以填满磁盘。
只需在shell cgi或bash cgi或ksh cgi上进行google搜索,就会发现一些页面,向您展示如何在shell中编写CGI。请注意,那些过程参数中有一半是易受攻击的。
甚至大卫·科恩(David Korn)自己的人也
很脆弱(请查看cookie处理)。
多达任意代码执行漏洞
任意代码执行是最严重的漏洞,因为如果攻击者可以运行任何命令,则对其执行的操作没有任何限制。
这一般是在分裂的部分,导致这些。这种拆分导致在仅需要一个参数时将几个参数传递给命令。虽然第一个将在预期的上下文中使用,但其他将在不同的上下文中,因此可能会有不同的解释。一个更好的例子:
awk -v foo=$external_input '$2 == foo'
在此,意图是将$external_input
shell变量的内容分配
给该foo
awk
变量。
现在:
$ external_input='x BEGIN{system("uname")}'
$ awk -v foo=$external_input '$2 == foo'
Linux
拆分后的第二个单词$external_input
未分配给您,foo
但被视为awk
代码(在此执行任意命令:)uname
。
这尤其是一个问题,可以执行其他命令的命令(awk
,env
,sed
(GNU一个)perl
,find
...),尤其是在GNU变种(其参数之后接受选项)。有时候,你不会怀疑的命令可以执行其他人一样ksh
,bash
或者zsh
的[
或printf
...
for file in *; do
[ -f $file ] || continue
something-that-would-be-dangerous-if-$file-were-a-directory
done
如果我们创建一个名为的目录x -o yes
,则测试将变为肯定,因为它是我们要评估的完全不同的条件表达式。
更糟糕的是,如果我们创建一个名为的文件x -a a[0$(uname>&2)] -gt 1
,且至少具有所有ksh实现(包括sh
大多数商业Unices和某些BSD的实现),则该文件会执行,uname
因为这些shell对[
命令的数字比较运算符执行算术求值。
$ touch x 'x -a a[0$(uname>&2)] -gt 1'
$ ksh -c 'for f in *; do [ -f $f ]; done'
Linux
与一样bash
的文件名x -a -v a[0$(uname>&2)]
。
当然,如果他们无法任意执行,则攻击者可能会接受较小的损害赔偿(这可能有助于获得任意执行)。任何可以写入文件或更改权限,所有权或具有任何主要或副作用的命令都可以被利用。
各种各样的事情都可以通过文件名来完成。
$ touch -- '-R ..'
$ for file in *; do [ -f "$file" ] && chmod +w $file; done
最终使您..
可写(使用GNU递归
chmod
)。
在公共可写区域(例如,可写区域)中自动处理文件的脚本/tmp
必须非常小心地编写。
关于什么 [ $# -gt 1 ]
我觉得这很生气。有些人不知所措,想知道某个特定的扩展是否可能会成为决定是否可以省略引号的问题。
这就像在说。嘿,看起来好像$#
不能使用split + glob运算符,让我们让shell对其进行split + glob。还是,嘿,仅仅因为错误不太可能被发现,让我们编写不正确的代码。
现在怎么可能呢?OK,$#
(或$!
,$?
或任何算术替代)只能包含数字(或-
某些数字),因此glob部分不可用。为了使拆分部分能够执行某些操作,我们所需要做的就是$IFS
包含数字(或-
)。
对于某些shell,$IFS
可能是从环境继承而来的,但是如果环境不安全,则无论如何都是游戏。
现在,如果您编写类似以下的函数:
my_function() {
[ $# -eq 2 ] || return
...
}
这意味着函数的行为取决于调用它的上下文。换句话说,$IFS
成为它的输入之一。严格来说,当您为函数编写API文档时,应该是这样的:
# my_function
# inputs:
# $1: source directory
# $2: destination directory
# $IFS: used to split $#, expected not to contain digits...
调用函数的代码需要确保其中$IFS
不包含数字。所有这些都是因为您不想键入这两个双引号字符。
现在,要使该[ $# -eq 2 ]
漏洞成为漏洞,您需要以某种方式使其价值$IFS
受到攻击者的控制。可以想象,除非攻击者设法利用另一个错误,否则通常不会发生这种情况。
但这并非闻所未闻。常见的情况是,人们忘记在算术表达式中使用数据之前先清理数据。上面我们已经看到,它可以允许在某些shell中执行任意代码,但是在所有这些shell中,它都允许
攻击者为任何变量提供整数值。
例如:
n=$(($1 + 1))
if [ $# -gt 2 ]; then
echo >&2 "Too many arguments"
exit 1
fi
并且使用$1
with值(IFS=-1234567890)
,该算术运算会影响设置IFS的副作用,并且下[
一条命令将失败,这意味着将绕过检查过多的args。
如果不调用split + glob运算符,该怎么办?
在另一种情况下,变量和其他扩展名需要加引号:用作模式时。
[[ $a = $b ]] # a `ksh` construct also supported by `bash`
case $a in ($b) ...; esac
不测试$a
和$b
是否相同(除非zsh
),但是否$a
匹配中的模式$b
。而你需要引用$b
如果要比较的字符串(同样的事情在"${a#$b}"
或"${a%$b}"
或"${a##*$b*}"
其中$b
应该被引用,如果它不被视为一种模式)。
这也就意味着,[[ $a = $b ]]
在可能的情况下返回true $a
从不同$b
(例如当$a
是anything
和$b
是*
),或当它们是相同的可能会返回错误(例如,当这两个$a
和$b
的[a]
)。
这可以造成安全漏洞吗?是的,像任何错误一样。在这里,攻击者可以更改脚本的逻辑代码流和/或破坏脚本所做的假设。例如,使用如下代码:
if [[ $1 = $2 ]]; then
echo >&2 '$1 and $2 cannot be the same or damage will incur'
exit 1
fi
攻击者可以通过绕过检查'[a]' '[a]'
。
现在,如果既不使用模式匹配也不使用split + glob运算符,则不给变量加引号的危险是什么?
我必须承认我确实写过:
a=$b
case $a in...
在那里,引用无害,但并非绝对必要。
但是,在这些情况下(例如,在问答中)省略引号的一个副作用是,它可能向初学者发送错误消息:不引用变量也可以。
例如,他们可能开始认为,如果a=$b
还可以,那么export a=$b
也可以(在很多shell中,因为它不在export
命令的参数中,所以在列表上下文中)或env a=$b
。
那zsh
呢
zsh
确实解决了大多数设计上的尴尬。在zsh
(至少在非sh / ksh仿真模式下),如果要splitting,globbing或pattern匹配,则必须明确请求:$=var
split和$~var
glob或将变量的内容视为一种模式。
但是,拆分(而不是遍历)仍然在无引号的命令替换后隐式完成(如中所述echo $(cmd)
)。
同样,有时不引用变量有时会产生副作用,就是清空容器。该zsh
行为类似于通过完全禁用glob(与set -f
)和拆分(with与IFS=''
)在其他Shell中可以实现的功能。还在:
cmd $var
没有split + glob,但是如果$var
为空,则cmd
不会接收任何参数,而不是接收一个空参数。
这可能会导致错误(如显而易见的[ -n $var ]
)。这可能会破坏脚本的预期和假设并导致漏洞,但是我暂时无法提供一个不太牵强的示例)。
什么,当你对做需要拆分+水珠操作?
是的,通常是在您确实希望不使用变量的情况下。但是随后您需要确保在使用前正确调整了split和glob运算符。如果您只希望分割部分而不是glob部分(大多数情况下是这种情况),那么您确实需要禁用globbing(set -o noglob
/ set -f
)并修复$IFS
。否则,您也会造成漏洞(例如上述的David Korn的CGI示例)。
结论
简而言之,在shell中不加引号的变量(或命令替换或算术扩展)确实非常危险,尤其是在错误的上下文中完成时,并且很难知道哪些是错误的上下文。
这就是为什么将其视为不良做法的原因之一。
感谢您到目前为止的阅读。如果它超过您的头,请不要担心。不能期望每个人都能以编写代码的方式理解编写代码的所有含义。这就是为什么我们有良好实践建议的原因
,因此不必理解为什么就可以遵循它们。
(以防万一,请避免在shell中编写安全敏感的代码)。
并且请在此站点的答案中引用您的变量!
¹在ksh93
and pdksh
及其衍生物中,除非禁用globlob(即使版本为ksh93u +以上,即使禁用了该选项),也都执行大括号扩展。ksh93
braceexpand