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

jar*_*rno 3 security bash zsh sh dash-shell

我的意思是我想使用unset它本身不是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
Run Code Online (Sandbox Code Playgroud)

如果我使用的是Debian Almquist shell(破折号),我认为我可以依赖它\unset是纯粹的.至少我不能定义一个名为shell函数unsetdash.而在我bash或之中zsh我可以定义unset() { echo fake unset; },然后我无法取消该功能:\unset -f unset输出"假未设置".

与此相关,在bash脚本中,可以导出函数,export -f <function name>以便可以在bash脚本调用的脚本中使用它.但是,这在dash脚本中不起作用.我想知道,如果我不得不担心一个命令被定义为我写的脚本文件外的shell函数,如果我正在使用dash?其他POSIX兼容的shell怎么样?

mkl*_*nt0 9

注:以下内容适用于所有主要的POSIX兼容的炮弹,除非另有说明:bash,dash,ksh,和zsh.(dash,Debian Almquist Shell,是sh基于Debian的Linux发行版(如Ubuntu)的默认shell()).

  • unset具有其原始含义 - 一个可以通过其选项取消定义shell 函数的内置函数-f - 是确保任何其他shell关键字,命令或内置函数具有其原始含义的关键.

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

    • dash并且ksh是安全的,因为它们不允许定义一个名为的函数unset,正如您所发现的那样,并且可以通过调用as来绕过任何别名形式\unset.

    • bash,当处于POSIX兼容模式时,允许您定义一个名为的函数unset,但在调用时忽略unset,并始终执行内置函数,如您稍后发现的那样.

      • 鉴于POSIX兼容模式限制了Bash的功能集并修改了它的行为,因此通常不希望在其中运行Bash代码.在这篇文章底部是你建议的变通方法的实现,它暂时激活POSIX兼容模式,以确保没有定义任何unset 功能.
  • 遗憾的是,据我所知,在zsh- 以及在bash默认模式下 - 没有办法保证它unset本身没有被重新定义,并且可能有其他类似POSIX的shell表现相似.

    • 将其称为\unset(引用名称的任何部分)将绕过别名重定义,但不会重新定义函数 - 并撤消您需要原始unset本身:catch 22.
  • 因此,与在执行环境无法控制,你可以不写是完全不受篡改shell脚本,除非你知道你的代码会被执行dash,ksh或者bash(在地方解决方法).

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

      • 使用\unset -f以确保unaliascommand未修改(而不是由一个shell函数阴影:\unset -f unalias command)

        • 功能方面,不同于别名,必须是未定义明确,按名称,但不是所有的shell提供一种机制来枚举所有定义的功能,可惜的是(typeset -f工程bash,kshzsh,但dash似乎没有任何机制可言),使取消定义的所有功能,是并非总是可行.
      • 使用\unalias -a删除所有别名.

      • 然后调用一切command [-p],除了功能,已经定义.在调用外部实用程序时,尽可能使用显式路径,和/或在标准实用程序的情况下使用command -p,它使用$PATH限制在标准位置的最小定义(运行command -p getconf PATH以查看该定义).


附加信息:

  • 每个POSIX,引用命令名称的任何部分(例如\unset),用该名称绕过任何别名形式或关键字形式(POSIX和用语中的保留字zsh) - 但不是 shell 函数.

  • 按POSIX,unalias -a取消定义所有别名.没有等效的POSIX兼容命令来取消定义所有功能.

    • 警告:旧zsh版本支持-a; v5.0.8但至少,他们确实如此.
  • 内建command可用于绕过关键字,别名,函数中bash,dashksh-换句话说:command只执行内建外部公用事业.相比之下,zsh默认情况下也会绕过内置组件 ; 要zsh使用内置函数,请使用options[POSIX_BUILTINS]=on.

  • 以下内容可用于在所有shell中执行仅命名的外部实用程序<name>:
    "$(command which <name>)" ...
    请注意,虽然which它不是POSIX实用程序,但它在现代类Unix平台上广泛使用.

  • 命令形式的优先顺序:

    • bash,zsh:别名> shell关键字> shell函数>内置>外部实用程序
    • ksh,dash:shell关键字>别名> shell函数>内置>外部实用程序
    • 即:In bashzsh别名可以覆盖shell关键字,而in kshdash它不能.
  • bash,kshzsh- 但不是dash- 都允许非标准函数签名function <name> { ...,作为POSIX兼容<name>() { ...表单的替代.

    • function语法是先决条件:
      • 确保定义函数之前,<name>它本身不受别名扩展的影响.
      • 能够选择一个<name>也是一个shell 关键字 ;
        请注意,此类函数只能引用形式调用; 例如,\while.
      • (在这种情况下ksh,使用function语法还意味着typeset语句创建局部变量.)
    • dash,kshbash 当在POSIX模式附加地防止命名功能特殊内置命令(例如,unset,break,set,shift); POSIX定义的特殊内置列表可以在这里找到; 双方dashksh添加更多一些不能被重新定义(例如,localdash; typesetunaliasksh),但两个壳具有附加的,非特殊的是内建被重新定义(如type).
      注意,在ksh上述规则的情况下,无论是否使用function语法,都适用.
  • 代码范围内的环境shell函数的潜在来源:

    • 注意:谨防这些最简单的方法是使用(未修改)command内置(在zshoptions[POSIX_BUILTINS]=on,以防止建宏旁路为好),只要你想调用一个内置或外部工具.

    • POSIX任务,通过在环境变量的绝对路径指定的脚本ENV采购互动壳(有一些限制-见规范); ksh并且dash总是尊重这一点,而bash只有sh在v4.2 +中调用时才会这样做--posix; 相比之下,zsh从不尊重这个变量.

      • 注意:您的代码作为脚本运行通常在非交互式 shell中运行,但这不能保证; 例如,您的代码可以来自交互式脚本,或者有人可以调用您的脚本,例如sh -i强制交互式实例.
    • bash2种机制:

      • 使用或导出单个函数(其他shell仅支持导出变量)export -fdeclare -fx
      • 在可选环境变量中启动非交互式 shell 时,指定要发送的脚本的完整路径BASH_ENV.
    • ksh支持自动装载的功能通过可选的FPATH环境变量:含有位于指定的任何目录函数定义文件FPATH隐式和自动加载.

      • (也zsh支持FPATH,但自动加载函数需要一个显式 autoload <name>语句,因此除非您特别要求给定名称的函数自动加载,否则不会向shell添加任何函数.)
    • zsh支持采购脚本任何 zsh情况下(无论是交互式或不能)通过其/etc/zshenv~/.zhsenv初始化文件.

    • (dash似乎不支持通过环境定义函数的任何机制.)


解决方法bash:确保unset具有其原始含义:

如果您知道bash将执行您的脚本,这种解决方法是安全的,遗憾的是,这本身不能保证.

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

如上所述,通常不希望在Bash的POSIX兼容模式下运行代码,但您可以暂时激活它以确保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
Run Code Online (Sandbox Code Playgroud)

测试命令:

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

下面的命令模拟篡改,与环境中,unset由两个别名和功能,以及文件阴影script来源,使其看到的功能和交互来源时,过于扩大别名:

$ (unset() { echo hi; }; alias unset='echo here'; . ./script)
unset is a shell builtin
Run Code Online (Sandbox Code Playgroud)

type unset输出unset is a shell builtin是证明影响内置函数的函数和别名unset都被取消激活的证明.


Mik*_*ger 1

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

$ var="FOO"
$ unset() { echo nope; }
$ echo "${var}"
FOO
$ unset var
nope
$ echo "${var}"
FOO
$ command unset var
$ echo "${var}"
<nothing!>
Run Code Online (Sandbox Code Playgroud)

如果您处于某人创建了command() { :; }函数的敌对环境中,这并没有帮助。但如果你处于敌对的环境中,你就已经失败了;)。

当涉及到将函数导出到环境中时,这是特定于 bash 的扩展,您不应该真正依赖它。POSIX shell(如 dash)在设计上不支持这一点。

  • 如果您担心“命令”被覆盖,那么您就已经失败了。每个 shell 内置函数都可以被覆盖。 (4认同)