你怎么能使用纯粹的unset shell内置?你能编写一些免于篡改的shell脚本吗?

时间:2016-03-10 12:49:14

标签: bash security sh zsh dash-shell

我的意思是我想使用不是shell函数本身的command。如果我能做到这一点,我可以通过运行

确保#!/bin/sh { \unset -f unalias command [; \unalias unset command [ } 2>/dev/null; # make zsh find *builtins* with `command` too: [ -n "$ZSH_VERSION" ] && options[POSIX_BUILTINS]=on 是纯粹的
\unset

如果我使用的是Debian Almquist shell(破折号),我认为我可以依赖unset是纯粹的。至少我无法在dash中定义名为bash的shell函数。在zshunset() { echo fake unset; }中,我可以定义\unset -f unset,之后我无法取消设置功能:bash输出&#34;假未设置&#34;。< / p>

与此相关,在export -f <function name>脚本中,可以通过bash导出函数,以便可以在脚本调用的dash脚本中使用它。但是,这在dash脚本中不起作用。我想知道,如果我要使用Create Procedure [dbo].[Sp_User] ( @Id int=null, @FullName varchar(50)=null, @Password varchar(50)=null, @Address varchar(100)=null, @Email varchar(50)=null, @Imgurl varchar(50)=null, @Activity varchar(50)=null ) As Begin if(@Activity='Add') insert into registration (FullName,[Password],[Address],Email,Imgurl) values (@FullName,@Password,@Address,@Email,@Imgurl) if(@Activity = 'All') select top 1 FullName,Email,Password,imgurl from registration order by Id desc End ,我是否必须担心在我编写的脚本文件之外将命令定义为shell函数?其他POSIX兼容的shell怎么样?

3 个答案:

答案 0 :(得分:8)

注意:除非另有说明,否则以下内容适用于所有与POSIX兼容的主要shell:bashdashkshzsh。 (dash,Debian Almquist Shell,是基于Debian的Linux发行版(如Ubuntu)的默认shell(sh)。

  • unset具有原始含义 - a builtin that can undefine shell functions with its -f option - 是键,用于确保任何其他shell关键字,命令或内置文件具有其原始内容意

    • 从未经修改的unset开始,您可以确保未经修改的shopt和/或command,它们可以一起用于绕过或取消定义可能影子的任何别名或shell函数shell关键字,内置函数和外部实用程序。
    • 作为取消定义功能的替代方法,command可用于绕过它们,包括那些可能已在外部定义的 >你的代码,通过环境;
      只有bash支持的导出功能才是这些机制的一个;不同的壳有不同的壳可能支持几个 - 见下文。
  • 在POSIX兼容模式下,只有dashkshbash 才能保证unset不是&#39 ; t重新定义:

    • dashksh是安全的,因为他们不允许定义名为{{1}的函数 },正如您所发现的那样,任何别名形式都可以通过调用unset来绕过。

    • \unset在POSIX兼容模式时,允许您定义名为bash的函数,但忽略当你调用unset时,它总是会执行内置的,就像你后来发现的一样。

      • 鉴于 POSIX兼容模式限制了Bash的功能集并修改了其行为,通常不希望在其中运行Bash代码。在这篇文章的最后部分是您建议的变通办法的实现,临时激活POSIX兼容模式,以确保没有{{ 1}} 功能已定义。
  • 可悲的是,据我所知,在 unset - 以及unset的默认模式中 - 有否保证zsh本身没有被重新定义的方法,并且可能存在其他类似POSIX的shell。

    • 将其称为bash(引用名称的任何部分)将绕过别名重新定义,但不会绕过功能重新定义 - 并撤消您需要原始的unset本身:赶上22。
  • 因此,无法控制执行环境,您无法编写完全免于篡改的shell脚本 除非您知道您的代码将由\unsetunsetdash执行(具有适当的解决方法)

    • 如果您愿意假设ksh未被篡改,最强大的方法是:

      • 使用bash确保unset\unset -f未被修改(未被shell函数遮蔽:unalias

        • 与别名不同,函数必须按名称未明确显式,但不是所有shell都提供枚举所有已定义函数的机制,不幸的是command\unset -f unalias command中有效,typeset -fbash,但dash appears to have no mechanism at all),以便始终无法定义所有功能。
      • 使用ksh删除所有别名。

      • 然后使用zsh调用所有,但定义的函数除外。在调用外部实用程序时,尽可能使用显式路径,和/或在标准实用程序的情况下,使用\unalias -a,它使用限制在标准位置的最小command [-p]定义(运行command -p来查看该定义)。

其他信息:

  • 根据POSIX,引用命令名称的任何部分(例如$PATH)会绕过任何别名表单或关键字表单(该名称中的保留字在POSIX和command -p getconf PATH用语中 - 但 shell 函数

  • 根据POSIX,\unset取消定义所有别名。没有等效的POSIX兼容命令可以取消定义所有函数

    • 警告:较旧的zsh版本支持unalias -a;但至少zsh,他们确实如此。
  • 内置-a可用于绕过v5.0.8commandbash中的关键字,别名,功能 - 换句话说:{{1} }只执行 builtins 外部实用程序。相比之下,dash默认绕过 builtins ;要使ksh执行内置函数,请使用command

  • 以下内容可用于在所有shell中执行名为zsh外部实用程序:
    zsh
    请注意,尽管options[POSIX_BUILTINS]=on不是POSIX实用程序,但它在现代类Unix平台上广泛使用。

  • 命令表格的优先顺序:

    • <name>"$(command which <name>)" ...:别名&gt; shell关键字&gt; shell函数&gt; builtin&gt;外部效用
    • whichbash:shell关键字&gt;别名&gt; shell函数&gt; builtin&gt;外部效用
    • I.e。:在zshksh中,别名可以覆盖shell关键字,而dashbash则不能覆盖。
  • zshkshdash - 但不是bash - 所有允许非标准功能签名ksh作为替代与POSIX兼容的zsh表单。

    • dash语法是以下先决条件:
      • 确保{<1}}在定义函数之前,本身不受别名扩展的影响。
      • 能够选择也是shell 关键字的function <name> { ... ;
        请注意,此类函数只能引用的形式调用;例如,<name>() { ...
      • (在function的情况下,使用<name>语法还意味着<name>语句创建本地变量。)
    • 在处于POSIX模式时,
    • \whilekshfunction 还会阻止特殊内置命名函数(例如{ {1}},typesetdashksh);可以找到POSIX定义的特殊内置列表here; bashunset添加了一些无法重新定义的内容(例如,break中的set; shift中的dashksh但是,两个shell都有额外的非特殊内置,可以重新定义(例如,local)。
      请注意,在dash的情况下,无论是否使用typeset语法,上述规则都适用。
  • 代码范围内环境shell函数的潜在来源:

    • 注意:防范这些的最简单方法是使用(未修改的)unalias内置(在ksh中使用type,以防止绕过内置函数)无论何时你想调用内置或外部实用程序。

    • POSIX要求由环境变量ksh中的绝对路径指定的脚本为交互式 shell (有一些限制 - 请参阅{ {3}}); functioncommand始终尊重这一点,而zsh仅在options[POSIX_BUILTINS]=on或v4.2 +中使用ENV时才会这样做;相比之下,ksh从不尊重这个变量。

      • 注意:您的代码作为脚本运行通常会在非交互式 shell中运行,但这并不能保证;例如,您的代码可以来自交互式脚本 sourced ,或者有人可以使用例如dash调用您的脚本来强制交互式实例。
    • bash 2 机制:

      • 使用sh--posix导出单个函数(其他shell仅支持导出变量
      • 指定在可选环境变量zsh中启动非交互式 shell时要发送的脚本的完整路径。
    • sh -i通过可选的bash环境变量支持自动加载:包含位于export -f中指定的任何目录中的函数定义的文件是隐式自动加载。

      • declare -fx也支持BASH_ENV,但自动加载功能需要显式 ksh语句,因此除非您特别要求给定函数要自动加载的名称,不会将任何函数添加到shell中。)
    • FPATH支持通过其FPATHzsh初始化文件为任何 FPATH实例(无论是否具有交互性)获取脚本

    • autoload <name>似乎不支持通过环境定义功能的任何机制。)

zsh的解决方法:确保zsh具有原始含义:

如果你知道/etc/zshenv将执行你的脚本,这种解决方法是安全的,遗憾的是,这本身不能保证。

此外,因为它修改了shell环境(删除了别名和函数),所以它不适合设计为源的脚本。

如上所述,在Bash的POSIX兼容模式下运行代码通常不合适,但您可以暂时激活它以确保{{1} }没有被函数遮蔽:

~/.zhsenv

测试命令:

假设上面的代码已保存到当前目录中的文件dash

以下命令模拟篡改环境,其中bash被别名和函数遮蔽,文件unset 来源,导致其看到函数和交互式源代码,也可以扩展别名:

bash

unset输出#!/bin/bash # *Temporarily* force Bash into POSIX compatibility mode, where `unset` cannot # be shadowed, which allows us to undefine any `unset` *function* as well # as other functions that may shadow crucial commands. # Note: Fortunately, POSIXLY_CORRECT= works even without `export`, because # use of `export` is not safe at this point. # By contrast, a simple assignment cannot be tampered with. POSIXLY_CORRECT= # If defined, unset unset() and other functions that may shadow crucial commands. # Note the \ prefix to ensure that aliases are bypassed. \unset -f unset unalias read declare # Remove all aliases. # (Note that while alias expansion is off by default in scripts, it may # have been turned on explicitly in a tampered-with environment.) \unalias -a # Note: After this, \ to bypass aliases is no longer needed. # Now it is safe to turn POSIX mode back off, so as to reenable all Bash # features. unset POSIXLY_CORRECT # Now UNDEFINE ALL REMAINING FUNCTIONS: # Note that we do this AFTER switching back from POSIX mode, because # Bash in its default mode allows defining functions with nonstandard names # such as `[` or `z?`, and such functions can also only be *unset* while # in default mode. # Also note that we needn't worry about keywords `while`, `do` and `done` # being shadowed by functions, because the only way to invoke such functions # (which you can only define with the nonstandard `function` keyword) would # be with `\` (e.g., `\while`). while read _ _ n; do unset -f "$n"; done < <(declare -F) # IN THE REST OF THE SCRIPT: # - It is now safe to call *builtins* as-is. # - *External utilities* should be invoked: # - by full path, if feasible # - and/or, in the case of *standard utilities*, with # command -p, which uses a minimal $PATH definition that only # comprises the locations of standard utilities. # - alternatively, as @jarno suggests, you can redefine your $PATH # to contain standard locations only, after which you can invoke # standard utilities by name only, as usual: # PATH=$(command -p getconf PATH) # Example command: # Verify that `unset` now refers to the *builtin*: type unset 证明影响内置script的函数和别名都已停用。

答案 1 :(得分:1)

有趣的是,你已经说过了内置名称 - command

$ var="FOO"
$ unset() { echo nope; }
$ echo "${var}"
FOO
$ unset var
nope
$ echo "${var}"
FOO
$ command unset var
$ echo "${var}"
<nothing!>
如果您处于有人创建command() { :; }功能的恶劣环境中,这无济于事。但如果你处于敌对的环境中,你已经输了;)。

在将功能导出到环境中时,这是一个特定于bash的扩展,你不应该真的依赖它。 POSIX shell(如破折号)不支持设计。

答案 2 :(得分:1)

这就是我知道可以做些什么......

#!/bin/bash --posix

# if e.g. BASH_FUNC_unset() env variable is set, script execution cannot
# get this far (provided that it is run as is, not as `bash script ...`)

unset -f builtin command declare ...

saved_IFS=$IFS; readonly saved_IFS
# remove all functions (shell builtin declare executed in subshell)
IFS=$'\n'; for f in `declare -Fx`; do unset -f ${f##* }; done; IFS=$saved_IFS