我有以下片段:
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- \
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"
其灵感来自以下(有效)片段:
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < \
"./server-root-setup.sh" "${BB_USER}" "${APP_USER}"
两个文件本地文件。
但是,我找不到引用或转义 top 命令的方法,以使其成功运行。上面的命令抛出了${value of BB_USER}": ./server-user-setup.sh: Permission denied
。我尝试了许多变体:
有人能向我解释如何正确地引用/转义这个命令吗?
更新:非常接近了——这会触发脚本,但不会传递参数!
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- su www -c < \
"./server-user-setup.sh" "${BB_USER}" "${APP_USER}"
返回
server-user-setup.sh: Wrong number of arguments. # Part of the script - no args passed
su www -c ${value of BB_USER} ${value of APP_USER}
答案1
总结
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" \
'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
可以通过BB_USER
和APP_USER
变量注入代码。
长答案
让我们分析一下发生了什么。这很复杂。为了清楚起见,我们假设:
DO_DROPLET_IP=ip
SSH_PRIVKEY_PATH=key
BB_USER=b-user
APP_USER=a-user
原始(有效)片段
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" sh -s -- < \ "./server-root-setup.sh" "${BB_USER}" "${APP_USER}"
所有变量都在本地扩展。重定向是本地的,没关系它在中间. 使用我们假设的变量值,这是等效命令:
<"./server-root-setup.sh" ssh root@"ip" -i "key" sh -s -- "b-user" "a-user"
ssh
获取以下参数:root@ip
,-i
,key
,sh
,-s
,--
,b-user
。a-user
由于root@ip
看起来像user@hostname
,第一个以下参数未被识别为选项(如-i
)或选项参数(如key
的选项参数-i
)被视为启动构建远程命令的操作数数组。所述参数是,这是尝试在远程端运行的sh
命令:ssh
sh -s -- b-user a-user
这将启动它sh
,它需要从其 stdin 中输入命令(由于-s
)。--
这是一个 POSIX 约定,它告诉工具任何后续操作都不是选项(请参阅这,准则 10)。这样,即使我们的${BB_USER}
扩展到-f
左右,sh
也不会考虑它是一种选择。以下操作数设置为 shell 的位置参数($1
,$2
)。stdin 由 提供ssh
,本地文件./server-root-setup.sh
通过流式传输。
如果该文件和相关的两个变量在远程端可用,则可以使用以下命令获得(几乎)相同的结果:
sh "./server-root-setup.sh" ${BB_USER} ${APP_USER}
或者如果脚本中有一个合适的内容:
./server-root-setup.sh ${BB_USER} ${APP_USER}
因此,该代码片段确实是一种在远程端运行本地脚本的方法。但请注意,我故意没有引用${BB_USER}
或${APP_USER}
。在原始代码片段中,它们在本地端被引用,但这并不重要,因为ssh
构建并传递命令作为细绳在远程端进行解析。如果两个变量中的任何一个包含内部空格,远程端sh
将获得比您预期更多的参数;前导或尾随空格将丢失。除非变量值是故意设计为可解析的(但这样它们就不能在本地使用以产生等效结果,这就是我写“几乎”的原因),否则像$
、"
或'
这样\
的字符会引起麻烦。
${APP_USER}
如果 eg扩展到 ,情况会更糟a-user& rogue_command
。在远端,你会得到
sh -s -- b-user a-user& rogue_command
请注意,当您在本地运行时(带或不带引号)不会发生此类代码注入
./server-root-setup.sh ${BB_USER} ${APP_USER}
因为分隔符(如&
和 );
是在变量扩展之前识别的,而不是之后。但如果您在本地扩展变量,然后在远程端再次解析生成的字符串,情况就完全不同了。就像远程eval
。
我猜${BB_USER}
和${APP_USER}
是用户名,非常安全的字符串,所以尽管可能存在一般问题,但整个过程仍然有效。但如果这些变量不完全由您控制,则该方法并不安全。
你的(失败的)尝试
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" su www -c sh -c -- \ "./server-user-setup.sh" "${BB_USER}" "${APP_USER}"
这次没有重定向。变量再次在本地扩展,等效命令如下所示:
ssh root@"ip" -i "key" su www -c sh -c -- "./server-user-setup.sh" "b-user" "a-user"
ssh
尝试在远程端运行此操作:
su www -c sh -c -- ./server-user-setup.sh b-user a-user
注意,所有参数现在都是 的参数su
。此时,sh
只是 option 的选项参数-c
,同样, 也是--
option 的选项参数其他 -c
选项(因此这里并不表示选项结束)。我的测试表明,从多个-c
选项到su
只有最后一个选项生效。这意味着-c sh
被丢弃,使用用户su
在(远程)中指定的任何 shell 。这可能是也可能不是。假设它是。这是接下来运行的内容(作为用户):/etc/passwd
www
sh
some-shell
www
some-shell -c -- # but wait, it's not all
其余操作数为su
,所以
some-shell -c -- ./server-user-setup.sh b-user a-user
如果是类似或 的some-shell
通用 shell ,则其行为类似。这里不接受选项参数(请参阅sh
bash
-c
规范),因此--
表示选项结束。在这种情况下,不能将以下参数作为选项,因此我们可以省略--
:
some-shell -c ./server-user-setup.sh b-user a-user
这就像
sh -c [-abCefhimnuvx] [-o option]... [+abCefhimnuvx] [+o option]... command_string [command_name [argument...]]
或者(丢弃所有我们不使用的东西)
some-shell -c command_string command_name argument
所以./server-user-setup.sh
命令字符串,b-user
是命令名称并且a-user
是争论。
-c
从操作数读取命令。根据操作数的值和位置参数(、等等)按顺序从剩余的参数操作数中command_string
设置特殊参数 […] 的值。[…]0
command_name
$1
$2
实际上,远程some-shell
尝试读取 (source) ./server-user-setup.sh
,字符串a-user
可用为$1
。特殊参数0
(现在值为b-user
)是 shell 认为是其自身名称的名称。该名称用于错误消息中,如下所示:
b-user: ./server-user-setup.sh: Permission denied
./server-user-setup.sh
显然远程端有此信息,但www
用户无法读取。
我的方法(仍然不安全):
ssh root@"${DO_DROPLET_IP}" -i "${SSH_PRIVKEY_PATH}" \
'su www -c "exec sh -s -- '"'${BB_USER}' '${APP_USER}'"'"' <"./server-user-setup.sh"
# 1 12 23 3
最后一行(注释)只是列举了上一行中的引号。要了解该命令的工作原理,我们需要“解密”这种引用狂潮。重要的是:
- 里面的所有内容
1
以及3
单引号都应按字面意思理解。 ${BB_USER}
并且${APP_USER}
用双引号引起来2
;“内部”单引号属于引用的字符串,它们不会阻止变量扩展。- 无论何时,结束引号紧接着开始引号(例如上面的引号
12
),引用的字符串都会被连接起来。
了解了这一点,我们可以知道带有扩展变量的命令看起来有点像这样:
<"./server-user-setup.sh" ssh root@"ip" -i "key" 'su www -c "exec sh -s -- Xb-userX Xa-userX"'
在哪里X
表示(仅对我们而言,此时此地)一个单引号字符,它属于以su
最后一个 开头和结尾的字符串"
。抱歉,我无法更清楚地表达这一点。希望当我们看到结果时,它会变得不那么神秘ssh
:
root@ip
,,,,-i
key
su www -c "exec sh -s -- 'b-user' 'a-user'"
最后一个参数包含双引号和单引号。这正是ssh
将在远程端运行的字符串。注意,我小心地将整个字符串作为一个参数传递给ssh
,因此该工具不会从多个参数和空格构建字符串;这样我就可以完全控制字符串。一般来说,这很重要,例如如果${BB_USER}
扩展为带有尾随空格的内容(在这种情况下,原始代码片段将失败)。
远端运行的命令:
su www -c "exec sh -s -- 'b-user' 'a-user'"
的行为su
已经讨论过了。请注意,整个带引号的字符串是 的选项参数-c
。如果不是引号,-s
则将是 的选项su
。以下是以www
用户身份运行的:
some-shell -c "exec sh -s -- 'b-user' 'a-user'"
注意,调用中的引号su
也会导致没有command_name
和没有argument
(s)(比较some-shell -c command_string command_name argument
上面的某处)。然后some-shell
运行:
exec sh -s -- 'b-user' 'a-user'
这可以在没有 的情况下运行exec
,但由于我们不再需要some-shell
,exec
这可能是一个好主意。它替换了sh
,some-shell
因此代替some-shell
和sh
作为其子项,只剩sh
下,就好像我们运行时some-shell
我们有
sh -s -- 'b-user' 'a-user'
现在,这与原始代码片段运行的内容非常相似(sh -s -- b-user a-user
)。由于额外的引号、可能的空格或来自${BB_USER}
或的其他一些麻烦字符,${APP_USER}
可能不会破坏任何内容。但是'
或"
会。代码注入仍然是可能的。考虑${APP_USER}
扩展到"& rogue_command;"
。在远程端运行的第一件事将是:
su www -c "exec sh -s -- 'b-user' '"& rogue_command;"'"
不管会不会some-shell
出错,rogue_command
都会运行。确保你的变量扩展为安全字符串。