使用函数输出中的带引号的值初始化 bash 数组的最安全方法是什么?

发布于 2024-08-03 09:08:42 字数 4374 浏览 3 评论 0原文

我更喜欢将 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

压抑⊿情绪 2024-08-10 09:08:42

以下正确处理数组元素中的空格:

#! /bin/bash

# $ ./return_an_array.sh 
# ./return_an_array.sh: line 9: declare: returned_array: not found
# declare -a returned_array='([0]="one" [1]="two three")'

return_an_array()
{
    local -a an_array=( 'one' 'two three' )
    declare -p an_array
}

declare -p returned_array
eval $(return_an_array | sed -e 's/^\(declare -a \)[^=]*=/\1 returned_array=/')
declare -p returned_array

The following handles spaces in array elements correctly:

#! /bin/bash

# $ ./return_an_array.sh 
# ./return_an_array.sh: line 9: declare: returned_array: not found
# declare -a returned_array='([0]="one" [1]="two three")'

return_an_array()
{
    local -a an_array=( 'one' 'two three' )
    declare -p an_array
}

declare -p returned_array
eval $(return_an_array | sed -e 's/^\(declare -a \)[^=]*=/\1 returned_array=/')
declare -p returned_array
遥远的绿洲 2024-08-10 09:08:42

对于数据函数,请使用 echo -e 并用换行符分隔数据:

getLines() { echo -e "\"Hello there\"\n\"loyal user\""; }

要读取数据,请使用进程替换和重定向:

i=0
while read -r
do
    arr[i++]=$REPLY
done < <(getLines)
# Line 0: '"Hello there"'
# Line 1: '"loyal user"'

不过,这会在字符串周围留下引号。

基于此处的技术。

For the data function use echo -e and separating data with newlines:

getLines() { echo -e "\"Hello there\"\n\"loyal user\""; }

To read the data, use process substitution and redirection:

i=0
while read -r
do
    arr[i++]=$REPLY
done < <(getLines)
# Line 0: '"Hello there"'
# Line 1: '"loyal user"'

This leaves the quotes around the strings, though.

Based on techniques from here.

你曾走过我的故事 2024-08-10 09:08:42

如果您担心来自函数的任意恶意数据,您应该简单地期望并使用 \0 (又名 NUL)分隔符解析字符串。这是唯一字符,不能成为任何变量的一部分,因此不可能与实际数据发生冲突。

haxxorz() {
    printf '%s\0' whoami
    printf '%s\0' '\`whoami\`'
    printf '%s\0' "\`whoami\`"
    printf '%s\0' "\"\`whoami\` is haxxor-proof"'!'"\""
    printf '%s' 'Trying to poison the $REPLY variable with a missing separator'
}

index=0
while IFS= read -r -d '' || [ -n "$REPLY" ]
do
    array[index++]="$REPLY"
done < <(haxxorz)

for element in "${array[@]}"
do
    echo "$element"
done

echo "${REPLY:-REPLY is empty}"

结果:

whoami
\`whoami\`
`whoami`
"`whoami` is haxxor-proof!"
Trying to poison the $REPLY variable with a missing separator
REPLY is empty

我喜欢制定计划!

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.

haxxorz() {
    printf '%s\0' whoami
    printf '%s\0' '\`whoami\`'
    printf '%s\0' "\`whoami\`"
    printf '%s\0' "\"\`whoami\` is haxxor-proof"'!'"\""
    printf '%s' 'Trying to poison the $REPLY variable with a missing separator'
}

index=0
while IFS= read -r -d '' || [ -n "$REPLY" ]
do
    array[index++]="$REPLY"
done < <(haxxorz)

for element in "${array[@]}"
do
    echo "$element"
done

echo "${REPLY:-REPLY is empty}"

Result:

whoami
\`whoami\`
`whoami`
"`whoami` is haxxor-proof!"
Trying to poison the $REPLY variable with a missing separator
REPLY is empty

I love it when a plan comes together!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文