如何在字符串中引用变量扩展名以避免单词拆分?


9
$ myvar="/path to/my directory"
$ sudo bash -c "cd $myvar"

在这种情况下,$myvar由于值中的空格,我如何引用以避免单词分裂myvar


4
无论您如何解决此问题,它都不会做得太多。该cd只具有内部效力bash -c外壳。
库萨兰达

我仅给出一个最小的示例,它不是解决我真正的问题的有效方法,它是unix.stackexchange.com/a/269080/674,另外在给sudo bash -c我的命令字符串中有一个变量扩展为可能包含空格的路径名。
蒂姆(Tim)

Answers:


13

有没有分词(如功能,不带引号时扩展分裂变量)在代码$myvar不带引号。

但是,存在一个命令注入漏洞,该漏洞$myvar会在传递给之前扩展bash。因此其内容被解释为bash代码!

那里的空格将导致几个参数传递给cd,不是因为单词拆分,而是因为它们将在shell语法中被解析为几个标记。值为bye;reboot,将重新启动!¹

在这里,您需要:

sudo bash -c 'cd -P -- "$1"' bash "$myvar"

(在其中将内容$myvar作为该内联脚本的第一个参数传递;请注意如何分别对$myvar$1进行引用,以防止它们各自的外壳被使用,以防止IFS单词拆分(和遍历))。

要么:

sudo MYVAR="$myvar" bash -c 'cd -P -- "$MYVAR"'

(您在其中传递$myvar环境变量的内容的位置)。

当然, cd在该内联脚本中运行(除了检查是否root可以cd插入其中),您将无法获得任何有用的东西。大概,您希望该脚本在cd那里,然后在其中执行其他操作:

sudo bash -c 'cd -P -- "$1" && do-something' bash "$myvar"

如果要使用sudo它来cd进入您否则无法访问的目录,那么这实际上是行不通的。

sudo sh -c 'cd -P -- "$1" && exec bash' sh "$myvar"

bash以其当前目录开始互动$myvar。但是该shell将以方式运行root

您可以这样做:

sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"

要获得bash当前目录为的非特权交互$myvar,但是如果您首先没有权限cd进入该目录,则即使该目录是您的当前工​​作目录,您也将无法在该目录中执行任何操作。

$ myvar=/var/spool/cron/crontabs
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ ls
ls: cannot open directory '.': Permission denied

如果您确实具有对目录本身的搜索权限,但对目录路径的目录组件之一没有搜索权限,则为例外:

$ myvar=1/2
$ mkdir -p "$myvar"
$ chmod 0 1
$ cd 1/2
cd: permission denied: 1/2
$ sudo sh -c 'cd -P -- "$1" && exec sudo -u "$SUDO_USER" bash' sh "$myvar"
bash-4.4$ pwd
/home/stephane/1/2
bash-4.4$ mkdir 3
bash-4.4$ ls
3
bash-4.4$ cd "$PWD"
bash: cd: /home/stephane/1/2: Permission denied

¹严格来说,对于$myvarlike $(seq 10)(字面值)的值,当然会在扩展该命令时被bash shell 替换为root


4

有一种新的(bash 4.4)引用魔术,使您可以更直接地执行所需的操作。根据更大的上下文,使用其他技术之一可能会更好,但是它确实可以在这种有限的上下文中工作。

sudo bash -c "cd ${myvar@Q}; pwd"

4

如果您不信任的内容$myvar,则唯一合理的方法是使用GNU printf创建它的转义版本:

#!/bin/bash

myvar=$(printf '%q' "$myvar")
bash -c "echo $myvar"

printf手册页所述:

%q

以可重用为shell输入的格式打印ARGUMENT,使用建议的POSIX $''语法转义不可打印的字符。


见我的答案对此便携式版本。
R .. GitHub STOP HELPING ICE

printf只有在GNU系统上没有内置$(printf '%q' "$myvar")的Shell中运行GNU 时,才会在该处调用GNU printfprintf如今,大多数炮弹都已内置。并非所有人都支持,%q但是当它们支持时,它们会以对应的shell支持的格式引用事物,这不一定与所理解的相同bash。事实上,bashprintf无法正确引用的东西甚至本身的一些值$myvar在某些地区。
斯特凡Chazelas

随着bash-4.3至少,这仍然是一个命令注入漏洞与myvar=$'\xa3``reboot`\xa3`' 在zh_HK.big5hkscs区域设置实例。
斯特凡·查泽拉斯

3

首先,正如其他人指出的那样,您的cd命令是无用的,因为它发生在立即退出的shell上下文中。但总的来说,这个问题是有道理的。您需要一种用shell引用任意字符串的方法,以便可以在将其解释为shell引用的字符串的上下文中使用它。我的sh技巧页面中有一个示例:

quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }

使用此功能,您可以执行以下操作:

myvar="/path to/my directory"
sudo bash -c "cd $(quote "$myvar")"

3

斯特凡·查泽拉斯(StéphaneChazelas)提出的建议无疑是做到这一点的最佳方法。我将提供一个关于如何使用参数扩展语法安全引用的答案,而不是sed像在R ..的答案中那样使用子流程。

myvar='foo \'\''"bar'

quote() {
  printf "'%s'" "${1//\'/\'\\\'\'}"
}

printf "value: %s\n" "$myvar"
printf "quoted: %s\n" "$(quote "$myvar")"
bash -c "printf 'interpolated in subshell script: %s\n' $(quote "$myvar")"

该脚本的输出为:

value: foo \'"bar
quoted: 'foo \'\''"bar'
interpolated in subshell script: foo \'"bar

1

是的,有分词。
好吧,从技术上讲,bash在解析命令行时使用了命令行拆分。

首先让我们引用正确:

我相信您会希望使命令正常工作的最简单(且不安全)的解决方案是:

bash -c "cd \"$myvar\""

让我们确认发生了什么(假设myvar="/path to/my directory"):

$ bash -c "printf '<%s>\n' $myvar"
<./path>
<to/my>
<directory>

如您所见,路径字符串在空格处分割。和:

$ bash -c "printf '<%s>\n' \"$myvar\""
<./path to/my directory>

不分裂。

但是,命令(如所写)不安全。更好地使用:

$ bash -c "cd -P -- \"$myvar\"; pwd"
/home/user/temp/path to/my directory

这样可以保护cd免受以破折号(-)开头或可能导致问题的链接开头的文件的侵害。

如果您可以相信其中的字符串$myvar是路径,那将起作用。
但是,由于您正在“执行字符串”,因此“代码注入”仍然可能:

$ myvar='./path to/my directory";date;"true'
$ bash -c "printf '<%s> ' \"$myvar\"; echo"
<./path to/my directory> Tue May  8 12:25:51 UTC 2018

您需要对字符串进行更强的引用,例如:

$ bash -c 'printf "<%s> " "$1"; echo' _ "$myvar"
<./path to/my directory";date -u;"true>

正如您在上面看到的那样,该值未被解释,而只是用作"$1'

现在我们可以(安全地)使用sudo了:

$ sudo bash -c 'cd -P -- "$1"; pwd' _ "$myvar"
_: line 0: cd: ./path to/my directory";date -u;"true: No such file or directory

如果有多个参数,则可以使用:

$ sudo bash -c 'printf "<%s>" "$@"' _ "$val1" "$val2" "$val3"

-2

单引号在这里不起作用,因为那样您的变量将不会被扩展。如果您确定路径的变量已被清除,最简单的解决方案是将扩展名放在新的bash shell中,然后在其周围添加引号:

sudo bash -c "cd \"$myvar\""

2
仍然是一个命令注入漏洞,喜欢的值$myvar$(reboot)"; reboot; #
斯特凡Chazelas

1
使用sudo bash -c "cd -P -- '$myvar'"会将问题限制为仅$myvar 包含单引号字符的值,这样更易于清理。
斯蒂芬·查泽拉斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.