shell中实用eval命令和安全问题

 更新时间:2023年10月13日 08:30:19   作者:qq_21305943  
eval命令非常强大,但也非常容易被滥用,本文主要介绍了shell中实用eval命令和安全问题,具有一定的参考价值,感兴趣的可以了解一下

eval命令非常强大,但也非常容易被滥用。

它会导致代码被解析两次而不是一次。这意味着,如果你的代码中包含变量引用,shell解析器将评估该变量的内容。如果变量包含一个shell命令,shell可能会运行该命令,无论你是否希望运行它。这可能会导致意外的结果,特别是当变量可以从不受信任的来源(如用户或用户创建的文件)读取时。

请注意,eval命令在编程中被广泛认为是危险的。它可以执行任意的Shell代码,包括恶意代码,因此应该谨慎使用。

Bash的名称引用问题

Bash 4.3引入了declare -n("名称引用")来模仿Korn shell的nameref??特性,允许变量保存对其他变量的引用。然而,Bash中使用的实现存在一些问题。

首先,Bash的declare -n??实际上并没有避免名称冲突问题:

$ foo() { declare -n v=$1; }
$ bar() { declare -n v=$1; foo v; }
$ bar v
bash: warning: v: circular name reference

换句话说,我们无法给名称引用指定一个安全的名称。如果调用者的变量恰好具有相同的名称,那就麻烦了。

其次,Bash的名称引用实现仍然允许任意代码执行:

$ foo() { declare -n var=$1; echo "$var"; }
$ foo 'x[i=$(date)]'
bash: i=Thu Mar 27 16:34:09 EDT 2034: syntax error in expression (error token is "Mar 27 16:34:09 EDT 2023")

这个例子并不优雅,但你可以清楚地看到date??命令实际上被执行了。这绝不是我们想要的结果。

尽管存在这些缺点,declare -n??特性是朝着正确方向迈出的一步。但你必须小心选择一个调用者不会使用的名称(这意味着你需要对调用者有某种控制,即使只是告诉他们“不要使用以_my_pkg??开头的变量”),并且必须拒绝不安全的输入。

eval的良好使用示例

eval最常见的正确使用方式是从专门设计为以这种方式使用的程序输出中读取变量。例如,

# 在旧系统上,调整窗口大小后必须运行以下命令:
eval "`resize`"
# 更高级的用法:获取SSH私钥的密码短语。
# 这通常从.xsession或.profile类型的文件执行。
# ssh-agent生成的变量将被导出到用户会话中的所有进程,以便之后的ssh命令可以继承这些变量。
eval "`ssh-agent -s`"

eval还有其他用途,特别是在创建变量时(参考indirect variable references ↗)。以下是一种解析不带参数的命令行选项的示例:

# POSIX
#
# 动态创建选项变量。尝试调用:
#
#    sh -x example.sh --verbose --test --debug
for i; do
    case $i in
       --test|--verbose|--debug)
            shift                   # 从命令行中移除选项
            name=${i#--}            # 删除选项前缀
            eval "$name=\$name"    # 创建*新*变量
            ;;
    esac
done
echo "verbose: $verbose"
echo "test: $test"
echo "debug: $debug"

那么,为什么这个版本是可接受的呢?这是因为我们限制了eval命令的使用,只有在输入是一组有限的已知值之一时才会执行。因此,用户无法滥用它以导致任意命令执行——任何包含奇怪内容的输入都不会匹配三个预定的可能输入之一。

请注意,这仍然是不推荐的:这是一条很陡峭的道路,稍后的维护很容易将这段代码变成危险的内容。例如,你想要添加一个功能,允许传递一堆不同的--test-xyz选项。你将--test更改为--test-*,而不费力地检查脚本的其他部分的实现。你测试你的用例,一切正常。不幸的是,你刚刚引入了任意命令执行:

$ ./foo --test-'; ls -l /etc/passwd;x='
-rw-r--r-- 1 root root 943 2007-03-28 12:03 /etc/passwd

再次强调:允许eval命令在未经过滤的用户输入上使用会导致任意命令执行。

尽一切可能避免将数据传递给eval,即使你的代码似乎处理了所有边界情况。

如果你经过深思熟虑并向#bash寻求了替代方法,但没有找到任何方法,请跳到"Robust eval usage"部分。

使用declare的问题

使用declare能更好地完成这个任务吗?

for i in "$@"; do
    case "$i" in
        --test|--verbose|--debug)
            shift                   # 从命令行中移除选项
            name=${i#--}            # 删除选项前缀
            declare $name=Yes       # 设置默认值
            ;;
        --test=*|--verbose=*|--debug=*)
            shift
            name=${i#--}
            value=${name#*=}        # value是第一个单词后面的内容和=
            name=${name%%=*}        # 仅限于第一个单词(即使值中有另一个=)
            declare $name="$value"  # 创建*新*变量
            ;;
    esac
done

请注意,--name用于默认值,--name=value是必需的格式。

以下是eval的一个良好使用示例,用于从专门设计为以这种方式使用的程序输出中读取变量:

# 在旧系统上,调整窗口大小后必须运行以下命令:
eval "`resize`"
# 更高级的用法:获取SSH私钥的密码短语。
# 这通常从.xsession或.profile类型的文件执行。
# ssh-agent生成的变量将被导出到用户会话中的所有进程,以便之后的ssh命令可以继承这些变量。
eval "`ssh-agent -s`"

eval还可以用于创建变量时,尤其是在创建间接变量引用时。下面是一个解析不带参数的命令行选项的示例:

# POSIX
#
# 动态创建选项变量。尝试调用:
#
#    sh -x example.sh --verbose --test --debug
for i; do
    case $i in
       --test|--verbose|--debug)
            shift                   # 从命令行中移除选项
            name=${i#--}            # 删除选项前缀
            eval "$name=\$name"    # 创建*新*变量
            ;;
    esac
done
echo "verbose: $verbose"
echo "test: $test"
echo "debug: $debug"

尽管这个示例中的eval使用看起来安全,但仍然不推荐广泛使用eval命令,因为它需要非常小心的输入过滤和验证,以避免任意命令执行漏洞。尽量避免将数据传递给eval,并寻找替代方案,以增加脚本的安全性。

使用declare存在的问题

难道使用declare不能更好地解决这个问题吗?

for i in "$@"; do
    case "$i" in
        --test|--verbose|--debug)
            shift                   # 从命令行中移除选项
            name=${i#--}            # 删除选项前缀
            declare $name=Yes       # 设置默认值
            ;;
        --test=*|--verbose=*|--debug=*)
            shift
            name=${i#--}
            value=${name#*=}        # 值是等号后面的内容
            name=${name%%=*}        # 仅限于第一个单词的名称(即使值中还有另一个等号)
            declare $name="$value"  # 创建*新的*变量
            ;;
    esac
done

请注意,默认情况下,--name和--name=value是必需的格式。

对于某些输入,declare确实可以更好地工作:

griffon:~$ name='foo=x;date;x'
griffon:~$ declare $name=Yes
griffon:~$ echo $foo
x;date;x=Yes

但它仍然会导致数组变量中的任意代码执行:

attoparsec:~$ echo $BASH_VERSION
4.2.24(1)-release
attoparsec:~$ danger='( $(printf "%s!\n" DANGER >&2) )'
attoparsec:~$ declare safe=${danger}
attoparsec:~$ declare -a unsafe
attoparsec:~$ declare unsafe=${danger}
DANGER!

这段代码展示了使用declare可能引发的安全问题。在某些情况下,使用declare可能会导致任意代码执行,从而产生潜在的安全漏洞。在这个例子中,变量的值包含了一个命令,当使用declare声明变量时,该命令将被执行。这可能导致不受信任的代码执行,从而引发安全问题。

为了确保脚本的安全性,应该避免将不受信任的数据传递给declare命令。如果需要动态创建变量,可以考虑使用其他安全的方法或寻找替代方案,以避免潜在的安全风险。

强大的eval用法

几乎总是(至少在Bash中99%或更多的时间内,但也适用于更简洁的shell),正确地使用eval的方式是在库代码中生成隐藏在函数背后的抽象层。这允许函数具有以下功能:

  • 向函数的调用者呈现一个明确定义的接口,指定哪些输入必须由程序员严格控制,哪些可能是不可预测的,例如受用户输入影响的副作用。重要的是要记录哪些选项和参数在没有控制的情况下是不安全的。
  • 对某些类型的输入进行输入验证,如果可行,例如整数。在这种情况下,可以轻松地退出并返回一个错误状态,该错误状态可以由函数的调用者处理。
  • 创建隐藏使用eval的丑陋实现细节的抽象。

通常,当满足以下至少全部条件时,eval是正确的:

  • 可能的所有eval参数都保证不会在任何情况下产生有害的副作用或导致任意代码的执行。这些输入是静态编码的,不与不受控制的动态代码交互,并且/或经过彻底验证。这就是为什么函数很重要,因为你不一定需要自己保证这个保证。只要您的函数记录了哪些输入可能是危险的,您就可以将这个任务委托给函数的调用者。
  • eval用法向用户或程序员呈现了一个清晰的接口。
  • eval使得原本不可能的事情成为可能,而无需编写更大、更慢、更复杂、更危险、更丑陋、更不实用的代码。

如果出于某种原因仍然需要动态构建Bash代码并评估它,请确保采取以下预防措施:

  • 始终引用eval表达式:eval 'a=b'
  • 始终使用单引号引用代码,并使用printf的%q将数据扩展到其中:eval "$(printf 'myvar=%q' "$value")"
  • 不要使用动态变量名。即使使用了小心的%q用法,这也可能会被利用。

为什么要注意?如果未能遵循上述建议,以下是脚本可能会受到利用的示例:

  • 如果不对代码进行单引号引用,则存在将数据扩展到其中而没有进行%q处理的风险。这意味着该数据可以自由执行:
name='Bob; echo I am arbitrary code'; eval "user=$name"
  • 即使在对输入数据进行%q处理之后再将其视为变量名进行处理,如果赋值中存在非法变量名,Bash将会在PATH中搜索命令:
echo 'echo I am arbitrary code' > /usr/local/bin/a[1]=b; chmod +x /usr/local/bin/a[1]=b; var='a[1]' value=b; eval "$(printf '%q=%q' "$var" "$value")"

到此这篇关于shell中实用eval命令和安全问题的文章就介绍到这了,更多相关shell eval命令 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Shell命令之数组表示语法学习

    Shell命令之数组表示语法学习

    这篇文章主要为大家介绍了Shell命令之数组表示语法学习,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • win下调用putty执行命令脚本分享

    win下调用putty执行命令脚本分享

    这篇文章主要介绍了win下调用putty执行命令脚本,可以利用这个实现一些自动化的工作,需要的朋友可以参考下
    2014-03-03
  • Shell中重定向的深入讲解

    Shell中重定向的深入讲解

    这篇文章主要给大家介绍了关于Shell中重定向的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用shell具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-07-07
  • Linux 压缩某个文件夹的实现方法

    Linux 压缩某个文件夹的实现方法

    这篇文章主要介绍了Linux 压缩某个文件夹的实现方法的相关资料,希望通过本文能帮助到大家,让大家实现这样的功能,需要的朋友可以参考下
    2017-10-10
  • Linux 怎么实现添加FTP用户并设置权限的方法

    Linux 怎么实现添加FTP用户并设置权限的方法

    这篇文章主要介绍了Linux 怎么实现添加FTP用户并设置权限的方法的相关资料,这里对添加FTP用户进行了步骤详解,需要的朋友可以参考下
    2017-01-01
  • Shell脚本之文件批量创建与修改的简单方法

    Shell脚本之文件批量创建与修改的简单方法

    有时需要将文件内容进行修改,如果文件数量不多可以一个一个修改,那么如果文件数量很多一个一个修改很麻烦,这篇文章主要给大家介绍了关于Shell脚本之文件批量创建与修改的相关资料,需要的朋友可以参考下
    2021-06-06
  • Linux在shell中自动生成1到100的数组方法(两种方法)

    Linux在shell中自动生成1到100的数组方法(两种方法)

    之前自己在写shell脚本的时候,需要自动创建1-100的文本确不知道该如何去创建。今天小编给大家分享两种方法,需要的朋友参考下
    2017-02-02
  • Shell脚本配合iptables屏蔽来自某个国家的IP访问

    Shell脚本配合iptables屏蔽来自某个国家的IP访问

    这篇文章主要介绍了Shell脚本配合iptables屏蔽来自某个国家的IP访问,本文利用IPdeny的IP数据,然后用Shell脚本导入iptables实现屏蔽IP访问,需要的朋友可以参考下
    2015-04-04
  • Linux Shell脚本系列教程(五):数学运算

    Linux Shell脚本系列教程(五):数学运算

    这篇文章主要介绍了Linux Shell脚本系列教程(五):数学运算,本文讲解了使用let、(())和[]进行算术运算、使用expr进行算术运算、使用bc进行算术运算三种方法,需要的朋友可以参考下
    2015-06-06
  • linux下部署kodexplorer的方法

    linux下部署kodexplorer的方法

    下面小编就为大家带来一篇linux下部署kodexplorer的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05

最新评论