使用函数输出中的带引号的值初始化 bash 数组的最安全方法是什么?
我更喜欢将 bash 脚本编程得尽可能程序化。我在尝试这样做时遇到的一个困难发生在函数之间传递数组数据时,bash 中对此任务没有很好的支持。
举个例子,在 bash 中使用多个硬编码的带引号的值来初始化一个数组是很简单的,每个值可能包含多个单词:
declare -a LINES=( "Hello there" "loyal user" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'Loyal user'
但是,用函数的输出替换这样的硬编码值似乎效果不太好:
getLines() {
echo "\"Hello there\" \"loyal user\""
}
local LINE_STR=$( getLines )
declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
我已经尝试了几乎所有允许的 bash 语句的排列来解决这个问题。似乎效果很好的一种方法是“eval”:
local LINE_STR=$( getLines )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
但是,这种方法存在安全问题,如下所示:
emulateUnsafeInput() {
echo "\"\`whoami\` just got haxxored\" \"Hahaha!\""
}
local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'
“read -a”似乎是一种可能的解决方案,尽管这是一个有问题的解决方案,因为“read”将在当数据通过管道传输到子 shell 时,有效地将其变量堆栈与调用脚本的堆栈分开。
我应该考虑哪些解决方案来减轻“评估”方法的安全问题?我包含了以下脚本,它演示了我尝试过的多种方法:
#!/bin/bash
getLines() {
echo "\"Hello there\" \"loyal user\""
}
emulateUnsafeInput() {
echo "\"\`whoami\` just got haxxored\" \"Hahaha!\""
}
execute() {
(
echo Test 01
declare -a LINES=( "Hello there" "loyal user" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
);(
echo Test 02
local LINE_STR=$( getLines )
declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
);(
echo Test 03
local LINE_STR=$( getLines )
declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello there" "loyal user"'
# Line 1: ''
);(
echo Test 04
local LINE_STR=$( getLines )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
);(
echo Test 05
local LINE_STR=$( getLines )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
);(
echo Test 06
local LINE_STR=$( getLines )
declare -a LINES=( $( echo ${LINE_STR} ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
);(
echo Test 07
local LINE_STR=$( getLines )
declare -a LINES=( $( echo "${LINE_STR}" ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
);(
echo Test 08
local LINE_STR=$( getLines )
declare -a LINES=( $( eval echo ${LINE_STR} ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello'
# Line 1: 'there'
);(
echo Test 09
local LINE_STR=$( getLines )
declare -a LINES=( $( eval echo "${LINE_STR}" ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello'
# Line 1: 'there'
);(
echo Test 10
local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'
);(
echo Test 11
local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'
);(
echo Test 12
local LINE_STR=$( emulateUnsafeInput )
declare -a LINES=( $( eval echo ${LINE_STR} ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root'
# Line 1: 'just'
);(
echo Test 13
local LINE_STR=$( emulateUnsafeInput )
declare -a LINES=( $( eval echo "${LINE_STR}" ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root'
# Line 1: 'just'
)
}
execute
I prefer to program my bash scripts to be as procedural as possible. One difficulty I've encountered in trying to do so happens when passing array data between functions, a task that's not well supported in bash.
As an example, it's trivial to initial an array in bash with multiple hard-coded, quoted values, each of which may contain multiple words:
declare -a LINES=( "Hello there" "loyal user" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'Loyal user'
However, replacing such hard-coded values with the output of a function seems to not work so well:
getLines() {
echo "\"Hello there\" \"loyal user\""
}
local LINE_STR=$( getLines )
declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
I've tried almost every permutation of allowed bash statements to overcome this problem. The one approach that seems to work well is 'eval':
local LINE_STR=$( getLines )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
However, this approach is wrought with security concerns, as demonstrated here:
emulateUnsafeInput() {
echo "\"\`whoami\` just got haxxored\" \"Hahaha!\""
}
local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'
'read -a' seems like a possible solution, although a problematic one because 'read' will operate in a sub-shell when data is piped into it, effectively separating its variable stack from the one of the calling script.
What solutions should I consider to mitigate the security concerns of the 'eval' approach? I've included the following script which demonstrates the myriad of approaches I've tried:
#!/bin/bash
getLines() {
echo "\"Hello there\" \"loyal user\""
}
emulateUnsafeInput() {
echo "\"\`whoami\` just got haxxored\" \"Hahaha!\""
}
execute() {
(
echo Test 01
declare -a LINES=( "Hello there" "loyal user" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
);(
echo Test 02
local LINE_STR=$( getLines )
declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
);(
echo Test 03
local LINE_STR=$( getLines )
declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello there" "loyal user"'
# Line 1: ''
);(
echo Test 04
local LINE_STR=$( getLines )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
);(
echo Test 05
local LINE_STR=$( getLines )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'
);(
echo Test 06
local LINE_STR=$( getLines )
declare -a LINES=( $( echo ${LINE_STR} ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
);(
echo Test 07
local LINE_STR=$( getLines )
declare -a LINES=( $( echo "${LINE_STR}" ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'
);(
echo Test 08
local LINE_STR=$( getLines )
declare -a LINES=( $( eval echo ${LINE_STR} ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello'
# Line 1: 'there'
);(
echo Test 09
local LINE_STR=$( getLines )
declare -a LINES=( $( eval echo "${LINE_STR}" ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello'
# Line 1: 'there'
);(
echo Test 10
local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'
);(
echo Test 11
local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'
);(
echo Test 12
local LINE_STR=$( emulateUnsafeInput )
declare -a LINES=( $( eval echo ${LINE_STR} ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root'
# Line 1: 'just'
);(
echo Test 13
local LINE_STR=$( emulateUnsafeInput )
declare -a LINES=( $( eval echo "${LINE_STR}" ) )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root'
# Line 1: 'just'
)
}
execute
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
以下正确处理数组元素中的空格:
The following handles spaces in array elements correctly:
对于数据函数,请使用
echo -e
并用换行符分隔数据:要读取数据,请使用进程替换和重定向:
不过,这会在字符串周围留下引号。
基于此处的技术。
For the data function use
echo -e
and separating data with newlines:To read the data, use process substitution and redirection:
This leaves the quotes around the strings, though.
Based on techniques from here.
如果您担心来自函数的任意恶意数据,您应该简单地期望并使用
\0
(又名NUL
)分隔符解析字符串。这是唯一字符,不能成为任何变量的一部分,因此不可能与实际数据发生冲突。结果:
我喜欢制定计划!
If you're worried about arbitrary, malicious data from functions, you should simply expect and parse strings with the
\0
(aka.NUL
) separator. It's the only character which can not be part of any variables, so there's no way there will be collisions with actual data.Result:
I love it when a plan comes together!