bash shell 函数导入特性是否必然会引发提权问题?

信息安全 炮击 重击 特权升级
2021-08-11 13:11:43

即使没有Shellshock尾随命令的直接代码执行问题,bash 从环境中导入函数的特性在多大程度上会导致全面的提权漏洞?

需要明确的是,setuid 程序有很多,其中一些会直接或间接调用脚本,或者使用system(3)调用,这在大多数 Linux 上都会通过bash. 问题是,假设系统安装了这样的setuid二进制文件,这个特性在多大程度上bash允许本地权限提升。

  • 例如,如果调用脚本是否允许覆盖内置函数?如果是这样,这是否会导致权限升级与setuid可执行文件和调用相结合system(3)通过覆盖 eg或一些无害的东西?lscd
  • 这样的升级向量是否需要调用脚本(在这种情况下,可能会覆盖ls或脚本使用的任何未使用完整路径的命令)?
  • /bin/sh -c ARGUMENTS即使第一个参数不是脚本,正常执行路径是否可能在任何时候使用导入的函数?
  • 你能简单地给一个与第一个参数同名的环境变量system吗?
  • 如果是这样,这是否意味着system(3)setuid 可执行文件在使用/bin/shbash 的系统上的每次调用都是权限提升向量?
4个回答

这些测试在未打补丁的系统上运行:

# ls='() { echo NO; }' bash -c 'ls'
NO

* 通配符是必需的,否则 perl 将用 fork/exec 组合替换系统调用

# ls='() { echo NO; }' perl -e 'system("ls *");'
NO

但是ls不是内置的:

# type ls
ls is hashed (/bin/ls)
# type echo
echo is a shell builtin

因此,让我们尝试覆盖内置 echo (“命令”覆盖内置以避免递归):

# echo='() { command echo NO; }' bash -c 'echo YES'
NO

到目前为止,我们展示了:

  1. 它可以覆盖一个内置的
  2. 并且不需要调用脚本
  3. 即使第一个参数不是脚本
  4. 对“系统”的每次调用都是一个向量,赋予调用者特权

我认为赤裸裸的“系统”和“popen”已经烂透了,我认为这些的大多数用途都不会费心去逃避用户数据中的 shell 元字符。

当我想让 shell 生成管道或使用 shell 内置函数时,我有这个调用,但它仍然需要 fork/exec:

exec("sh", "-c", "\"$0\" \"$1\" | tee -a \"$2\"", ...);

该技术使用 bash 来引用 -c 命令的参数,$0 是第一个这样的参数,或者“--”可以用作填充符:

exec("sh", "-c", "\"$1\" \"$2\" | tee -a \"$3\"", "--", ...);

此测试在已修补的系统上运行:

$ env "BASH_FUNC_ls()=() { echo NO ; }" bash -c 'ls'
NO

清楚地表明这个问题与炮击无关。

虽然脚本或脚本使用工具的作者应该知道验证用户输入,并且应该知道验证/清理环境(因为它包含用户输入),但环境的某些部分可能需要保留,并且不能总是保留很容易看出它们可能是哪个;

XAUTHORITY、DBUS_SESSION_BUS_ADDRESS、XDG_SESSION_DESKTOP、SSH_AUTH_SOCK 是少数,其他重要的环境变量可能尚未发明

它被称为“环境”是有原因的。PATH 可能会被清理,但发出给 system() 的第一个命令可以被取代

sudoedit 手册页写道:

Running shell scripts via sudo can expose the same kernel bugs that make
setuid shell scripts unsafe on some operating systems (if your OS has a
/dev/fd/ directory, setuid shell scripts are generally safe).

但是,除非它们拒绝导入所有 bash 函数,否则对于 setuid bash shell 脚本而言,情况并非如此

“这是否会导致权限升级与 setuid 可执行文件结合使用......?”

您不能在 GNU 上拥有 setuid 脚本,因此让我们假设 setuid 二进制文件直接调用 Bash 脚本。在开始之前请注意,这是非常糟糕的设计, GNU手册中都明确建议不要这样做另请注意,这将剥离最邪恶的环境变量,并且在 GNU/Linux 中不可能将代码注入 setuid 进程或获取它的转储(除非您是 root)。因此,基于环境的攻击确实需要编写糟糕的 setuid 二进制文件。setuidld

Shellshock 在利用此类 setuid 二进制文件方面绝对没有任何作用。如果二进制文件确实遵循健全的建议,它不会调用任何 shell 脚本,不会调用系统,并确保exec*正在执行的所有调用都不受不受信任的环境变量的影响,它不会受到设计上的任何特权升级的影响。如果 setuid 二进制文件不验证不受信任的输入,它无论如何都会受到攻击。

你不应该系统或启动 shell 的原因不是因为不受信任的被调用者选择的环境将被转移到新的 shell(这完全取决于编程实践),而是因为如果你犯了任何错误,你会允许一个具有无限范围的混淆代理攻击,而不仅仅是允许特定操作:系统将受到比任何其他情况更广泛的危害。

从 Bash 调用具有类似 Shellshock 的环境的 setuid 二进制文件将导致您的 Bash 实例以您的权限处理额外的 Bash 原语,而不是稍后调用的 setuid 二进制文件的权限,后者无论如何都应该假设一个敌对环境。

“这样的升级向量是否需要调用脚本?”

Shellshock 意味着 Bash 是在一个受委托人控制的环境中调用的,该委托人的权限低于最初调用 Bash 的人。

如果没有人调用 Bash,那么您的 Shellshock 有效负载将毫无用处,因为它不会被处理(就像 OpenSSL 漏洞在 Microsoft Word 上不起作用一样)。

“即使第一个参数不是脚本,/bin/sh -c ARGUMENTS 的正常执行路径是否可能在任何时候使用导入的函数?”

如果参数是一个 shell 脚本并/bin/sh指向 Bash,则可以利用 Bash。但是,由于您使用自己的权限来运行该命令,因此对此完全没有兴趣。您不会获得任何特权(除非假设您在 SELinux 或 AppArmor 设置中通过使用/bin/sh -c而不是直接调用某些东西拥有更多特权;这样的设置会被破坏并且不是故意的)。

如果参数是二进制文件,则调用二进制文件。Microsoft Word 是否使用同时运行的 Firefox 中的功能?不。这里也是如此:加载器将加载二进制文件的代码并运行二进制文件,它不会运行来自另一个二进制文件的错误代码。您可以更改二进制文件行为方式的唯一情况是通过代码注入,并且您不能对 setuid 进程进行代码注入。

“你能简单地给一个与系统的第一个参数同名的环境变量吗?”

是的,但系统不读取环境变量,它执行命令。环境变量只是名称:字符串对的字典。无论语义如何,它们都不会替换将来使用的每一个文本。程序必须通过读取环境变量并决定关心它来显式使用它。

“如果是这样,这是否意味着 setuid 可执行文件对 system(3) 的每次调用,在 /bin/sh 是 bash 的系统上,都是权限提升向量?”

绝对不。systemsetuid 二进制文件的调用只是一个糟糕的主意,因为如果存在漏洞,后果将是荒谬的灾难性。system对使用 Bash 的操作系统的调用不会改变特权升级需要两个主体这一事实:只有当更高特权的主体决定使用由更低特权的主体提供的未经验证的环境调用 Bash 时,Shellshock 才会导致特权升级。

在大多数情况下,这样的系统调用已经是不可取的,因为有几种非 Shellshock 方式可能会导致这种设置出错,并且为避免现有的混淆代理攻击而采取的预防措施也可以防止 Shellshock。

如果您的 set(u|g)id 二进制文件保留其真实的 [ug]id,则答案是否定的。

当 e[ug]id != real [ug]id 并且不在特权模式 ( -p) 中调用时,bash 会放弃其特权:

重击 shell.c:main()

  if (running_setuid && privileged_mode == 0)
    disable_priv_mode ();

在特权模式下运行时,或者当 bash 被修补以在调用 /bin/sh 时不会放弃特权(例如:Debian 有这样一个补丁,以避免破坏旧的 set[ug]id 程序,例如 UUCP),bash 不会t 从环境中导入函数。Bash 检查它是否正在运行 set[ug]id 或特权模式 ( -p),如果是,则不会从环境中导入函数。

重击 shell.c:main()

  /* Initialize internal and environment variables.  Don't import shell
     functions from the environment if we are running in privileged or
     restricted mode or if the shell is running setuid. */
#if defined (RESTRICTED_SHELL)
  initialize_shell_variables (shell_environment, privileged_mode||restricted||running_setuid);
#else
  initialize_shell_variables (shell_environment, privileged_mode||running_setuid);
#endif

重击 variables.c:initialize_shell_variables()

  /* If exported function, define it now.  Don't import functions from
 the environment in privileged mode. */
  if (privmode == 0 && read_but_dont_execute == 0 && STREQN ("() {", string, 4))

是的!

bash函数导出本身就是一种风险的证明非常简单:

/bin/bash
SHELL=/bin/bash
export SHELL
ls() { echo executing /tmp/booby_trapped_ls ; }
declare -fx ls
perl -e 'system ("ls *") ;'