手机网站建设制作教程视频,wordpress网格化插件,seo做的最好的网站排行,上海建筑网站建设eval命令非常强大#xff0c;但也非常容易被滥用。
它会导致代码被解析两次而不是一次。这意味着#xff0c;如果你的代码中包含变量引用#xff0c;shell解析器将评估该变量的内容。如果变量包含一个shell命令#xff0c;shell可能会运行该命令#xff0c;无论你是否希望…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: iThu 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 --debugfor i; docase $i in--test|--verbose|--debug)shift # 从命令行中移除选项name${i#--} # 删除选项前缀eval $name\$name # 创建*新*变量;;esac
doneecho 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 $; docase $i in--test|--verbose|--debug)shift # 从命令行中移除选项name${i#--} # 删除选项前缀declare $nameYes # 设置默认值;;--test*|--verbose*|--debug*)shiftname${i#--}value${name#*} # value是第一个单词后面的内容和name${name%%*} # 仅限于第一个单词即使值中有另一个declare $name$value # 创建*新*变量;;esac
done
请注意--name用于默认值--namevalue是必需的格式。
以下是eval的一个良好使用示例用于从专门设计为以这种方式使用的程序输出中读取变量
# 在旧系统上调整窗口大小后必须运行以下命令eval resize# 更高级的用法获取SSH私钥的密码短语。# 这通常从.xsession或.profile类型的文件执行。# ssh-agent生成的变量将被导出到用户会话中的所有进程以便之后的ssh命令可以继承这些变量。eval ssh-agent -s
eval还可以用于创建变量时尤其是在创建间接变量引用时。下面是一个解析不带参数的命令行选项的示例
# POSIX
#
# 动态创建选项变量。尝试调用
#
# sh -x example.sh --verbose --test --debugfor i; docase $i in--test|--verbose|--debug)shift # 从命令行中移除选项name${i#--} # 删除选项前缀eval $name\$name # 创建*新*变量;;esac
doneecho verbose: $verbose
echo test: $test
echo debug: $debug尽管这个示例中的eval使用看起来安全但仍然不推荐广泛使用eval命令因为它需要非常小心的输入过滤和验证以避免任意命令执行漏洞。尽量避免将数据传递给eval并寻找替代方案以增加脚本的安全性。
使用declare存在的问题
难道使用declare不能更好地解决这个问题吗
for i in $; docase $i in--test|--verbose|--debug)shift # 从命令行中移除选项name${i#--} # 删除选项前缀declare $nameYes # 设置默认值;;--test*|--verbose*|--debug*)shiftname${i#--}value${name#*} # 值是等号后面的内容name${name%%*} # 仅限于第一个单词的名称即使值中还有另一个等号declare $name$value # 创建*新的*变量;;esac
done请注意默认情况下--name和--namevalue是必需的格式。
对于某些输入declare确实可以更好地工作
griffon:~$ namefoox;date;x
griffon:~$ declare $nameYes
griffon:~$ echo $foo
x;date;xYes
但它仍然会导致数组变量中的任意代码执行
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 ab始终使用单引号引用代码并使用printf的%q将数据扩展到其中eval $(printf myvar%q $value)不要使用动态变量名。即使使用了小心的%q用法这也可能会被利用。
为什么要注意如果未能遵循上述建议以下是脚本可能会受到利用的示例
如果不对代码进行单引号引用则存在将数据扩展到其中而没有进行%q处理的风险。这意味着该数据可以自由执行
nameBob; 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; vara[1] valueb; eval $(printf %q%q $var $value)