shell变量本身不执行代码,但不安全展开时,特殊符号会触发shell元字符解析或通配符扩展;根本原因是展开顺序与上下文差异——双引号内仍进行变量/命令/算术替换,而无引号则触发单词分割和globbing,导致命令注入或非预期行为。

Shell变量本身不会自动执行代码,但当它被不安全地展开并拼接到命令中时,特殊符号(如空格、换行符、;、&&、|、$()、`、*、?、[等)会触发Shell解释器的元字符解析或路径扩展,从而导致命令注入或非预期行为。
为什么变量里的特殊符号会出问题
根本原因在于Shell的展开顺序和上下文:变量值在双引号内仍会进行变量替换、命令替换和算术扩展,但不会做单词分割或通配符扩展;而一旦去掉引号直接使用,就可能触发单词分割(IFS作用)和Globbing(通配符展开),让攻击者控制命令结构。
例如:
危险写法:ls $USER_INPUT
若$USER_INPUT是"file.txt; rm -rf /",实际执行变成:ls file.txt; rm -rf /
若$USER_INPUT是"*.log",且当前目录有access.log error.log,则变成:ls access.log error.log——看似无害,但若换成rm -f $USER_INPUT,后果严重。
安全展开变量的4种关键方式
-
始终用双引号包裹变量:
"$VAR"。这能防止单词分割和通配符扩展,保留原始字符串结构。这是最基本、最有效的防护动作。 -
禁用命令替换和算术扩展:如果变量内容纯属数据(如文件名、用户名),避免在双引号内嵌套
$()或$[...]。不要写"$(echo $VAR)"这类嵌套,它重新引入执行风险。 -
显式禁用Globbing(通配符):在变量使用前临时关闭路径扩展:
set -f,用完再set +f。适用于必须裸写$VAR的极少数场景(不推荐)。 -
标准化输入后再赋值:对用户输入做白名单校验(如只允许字母、数字、下划线、短横线),或用
printf %q转义为Shell安全格式:SAFE_VAR=$(printf %q "$USER_INPUT"),之后可安全用于eval(仅限必要场景)。
哪些操作会放大风险
- 用
eval拼接命令:eval "ls $USER_INPUT"—— 它会二次解析,让所有元字符“活过来”,绝对禁止。 - 把变量放在反引号或
$()里:$(ls $USER_INPUT)—— 同样触发重解析,且可能执行任意命令。 - 未设
IFS就用for f in $VAR遍历 —— 空格或换行会被拆成多个词,破坏语义。 - 直接传给
find -name $VAR——$VAR中的*或?会被当作通配符匹配,不是字面量。
替代方案:绕过Shell解析更可靠
真正安全的做法是尽量避免让变量参与Shell命令构造:
- 用
find的-name参数时,改用find . -name "$VAR"(加引号)或更稳妥的find . -name "$VAR" -print0 | while IFS= read -r -d '' file; do ...; done。 - 执行外部命令时,优先使用数组传递参数:
cmd=(ls -l "$FILE_PATH"); "${cmd[@]}",这样完全跳过Shell解析阶段。 - 处理文件名含空格/换行等边界情况,用
while IFS= read -r -d ''配合find -print0,而非for循环。











