深入了解Shell的可移植性、安全性及相关特性
1. Shell会话与启动文件
1.1 交互式与非交互式会话
在Shell操作中,交互式会话和非交互式会话在启动文件的调用上有所不同。交互式会话通常只调用一个文件,例如使用
bash
启动交互式会话时:
$ bash
Start an interactive session
DEBUG: This is /home/bones/.bashrc
$ exit
Terminate the session
exit
而非交互式会话通常不调用任何文件,如:
$ echo pwd | bash
Run a command under bash
/home/bones
但如果
BASH_ENV
变量指向一个启动文件,非交互式会话也会调用该文件:
$ echo pwd | BASH_ENV=$HOME/.bashenv bash
Run a command under bash
DEBUG: This is /home/bones/.bashenv
/home/bones
1.2 Z-Shell的启动与终止
Z-Shell(
zsh
)具有复杂且灵活的定制过程。它可以伪装成Bourne shell或Korn shell。当以
sh
或
ksh
命名调用,或以
s
或
k
开头(可选地以单个
r
开头表示受限)时,它具有与这些shell相同的启动行为。
Z-Shell每次启动时,无论是登录shell、交互式shell还是非交互式shell,都会尝试读取两个初始化文件:
test -r /etc/zshenv && . /etc/zshenv
Read system-wide script
if test -n "$ZDOTDIR" && test -r $ZDOTDIR/.zshenv ; then
. $ZDOTDIR/.zshenv
Read this file
elif test -r $HOME/.zshenv ; then
. $HOME/.zshenv
Or else this file
fi
ZDOTDIR
变量可用于系统管理,阻止
zsh
自动读取用户主目录中的启动文件,而是从受管理控制的其他位置读取。如果需要该变量,通常会在
/etc/zshenv
中设置。若
ZDOTDIR
未设置,将个人定制放在
$HOME/.zshenv
文件中,可使其在每个Z-Shell会话中生效。
如果是登录shell,接下来会读取两个启动配置文件:
test -r /etc/zprofile && . /etc/zprofile
Read system-wide script
if test -n "$ZDOTDIR" && test -r $ZDOTDIR/.zprofile ; then
. $ZDOTDIR/.zprofile
Read this file
elif test -r $HOME/.zprofile ; then
. $HOME/.zprofile
Or else this file
fi
如果是登录shell或交互式shell,会尝试读取两个启动脚本:
test -r /etc/zshrc && . /etc/zshrc
Read system-wide script
if test -n "$ZDOTDIR" && test -r $ZDOTDIR/.zshrc ; then
. $ZDOTDIR/.zshrc
Read this file
elif test -r $HOME/.zshrc ; then
. $HOME/.zshrc
Or else this file
fi
最后,如果是登录shell,会尝试读取两个登录脚本:
test -r /etc/zlogin && . /etc/zlogin
Read system-wide script
if test -n "$ZDOTDIR" && test -r $ZDOTDIR/.zlogin ; then
. $ZDOTDIR/.zlogin
Read this file
elif test -r $HOME/.zlogin ; then
. $HOME/.zlogin
Or else this file
fi
当
zsh
退出时,如果是登录shell且不是因执行另一个进程而终止,会按顺序读取两个终止脚本:
if test -n "$ZDOTDIR" && test -r $ZDOTDIR/.zlogout ; then
Read this file
. $ZDOTDIR/.zlogout
elif test -r $HOME/.zlogout ; then
Or else this file
. $HOME/.zlogout
fi
test -r /etc/zlogout && . /etc/zlogout
Read system-wide script
以下是不同类型会话的示例:
-
登录会话
:
$ login
Start a new login session
login: zabriski
Password:
Echo suppressed to hide password
DEBUG: This is /etc/zshenv
DEBUG: This is /home/zabriski/.zshenv
DEBUG: This is /etc/zprofile
DEBUG: This is /home/zabriski/.zprofile
DEBUG: This is /etc/zshrc
DEBUG: This is /home/zabriski/.zshrc
DEBUG: This is /etc/zlogin
DEBUG: This is /home/zabriski/.zlogin
$ exit
Terminate the session
DEBUG: This is /home/zabriski/.zlogout
DEBUG: This is /etc/zlogout
- 交互式会话 :
$ zsh
Start a new interactive session
DEBUG: This is /etc/zshenv
DEBUG: This is /home/zabriski/.zshenv
DEBUG: This is /etc/zshrc
DEBUG: This is /home/zabriski/.zshrc
$ exit
Terminate the session
Silence: no termination files are read
- 非交互式会话 :
$ echo pwd | zsh
Run a command under zsh
DEBUG: This is /etc/zshenv
DEBUG: This is /home/zabriski/.zshenv
/home/zabriski
2. Shell脚本的可移植性与扩展
2.1 POSIX标准与Shell差异
POSIX标准为编写可移植的shell脚本做出了努力,但现实世界中情况复杂。
bash
和
ksh93
在POSIX基础上有许多扩展,但两者之间并非完全兼容,即使在设置选项或保存shell完整状态等简单领域也存在一些需要注意的小问题。
2.2 bash的shopt命令
shopt
命令可控制
bash
的行为,特别推荐在交互式使用时启用
extglob
选项。
2.3 常见扩展特性
bash
和
ksh93
有许多对shell编程非常有用的共同扩展,如
select
循环、
[[…]]
扩展测试工具、扩展模式匹配、花括号扩展、进程替换和索引数组等。还有一些小但有用的杂项扩展,其中算术
for
循环和
((…))
算术命令较为显著。
2.4 版本确定与其他Shell
可以从互联网下载
bash
和
ksh93
的源代码并进行编译。此外,还提到了另外两种流行的扩展Bourne风格的shell:
pdksh
和
zsh
。了解正在运行的shell版本很重要,因为不同的Bourne shell语言实现具有不同的启动和终止定制功能及文件。通用的shell脚本不应依赖于每个用户设置的功能或变量,而应自行完成所有必要的初始化。
3. 安全的Shell脚本编写
3.1 编写安全脚本的建议
为了编写更安全的shell脚本,可以参考以下建议:
1.
不将当前目录放入PATH
:可执行程序应仅来自标准系统目录,将当前目录(
.
)放入
PATH
会为“特洛伊木马”攻击打开大门。
2.
保护bin目录
:确保
$PATH
中的每个目录只能由其所有者写入,bin目录中的所有程序也应如此。
3.
设计后编码
:在编码前花时间思考要做的事情和实现方法,包含处理错误和失败的代码。
4.
检查输入参数的有效性
:验证输入参数是否符合预期类型和范围,shell的模式匹配功能对此很有用。
5.
检查命令的错误代码
:即使是看似不会失败的命令,也可能因恶意操作而失败,从而导致脚本行为异常。
6.
不信任传入的环境变量
:如果后续命令使用环境变量,应检查并将其重置为已知值。例如,显式设置
PATH
只包含系统bin目录,
IFS
设置为空格、制表符和换行符。
7.
从已知位置开始
:脚本启动时显式切换到已知目录,并确保切换成功,如
cd app-dir || exit 1
。
8.
使用完整路径名
:使用命令的完整路径名,以确保使用的是预期版本。
9.
使用syslog记录审计跟踪
:记录脚本调用的日期、时间和用户名等信息。如果没有
logger
,可以创建一个函数来记录日志:
logger( ) {
printf "%s\n" "$*" >> /var/adm/logsysfile
}
logger "Run by user " $(id -un) "($USER) at " $(/bin/date)
-
始终引用用户输入
:例如,使用
"$1"和"$*",防止恶意用户输入被进一步评估和执行。 -
不使用eval处理用户输入
:即使引用了用户输入,也不要使用
eval重新处理,否则用户容易破坏脚本。 - 引用通配符扩展结果 :创建包含特殊字符的文件名可能会对系统管理员造成危害,因此在脚本中应引用文件名参数。
-
检查用户输入中的元字符
:如果在
eval或$(…)中使用用户输入,要查找$或反引号等元字符。 - 测试和审查代码 :仔细检查代码中的假设和错误,尝试找出可能被利用的漏洞并修复。
- 注意竞态条件 :确保攻击者无法在脚本的两个命令之间执行任意命令而破坏安全性。
-
怀疑符号链接
:在更改文件权限或编辑文件时,检查文件是否为符号链接,可使用
[ -L file ]或[ -h file ]进行测试。 - 让他人审查代码 :新的视角可能会发现原作者遗漏的问题。
-
尽量使用setgid而非setuid
:使用
setgid可以限制受损组可能造成的损害。 -
使用新用户而非root
:如果必须使用
setuid访问一组文件,考虑创建一个非root用户并设置setuid。 -
尽量减少setuid代码
:将
setuid代码量减到最小,并将其移到一个单独的程序中,在必要时从较大的脚本中调用。
以下是
bash
维护者Chet Ramey提供的用于更安全脚本的代码:
# Reset IFS. Even though ksh doesn't import IFS from the environment,
# $ENV could set it. This uses special bash and ksh93 notation,
# not in POSIX.
IFS=$' \t\n'
# Make sure unalias is not a function, since it's a regular built-in.
# unset is a special built-in, so it will be found before functions.
unset -f unalias
# Unset all aliases and quote unalias so it's not alias-expanded.
\unalias -a
# Make sure command is not a function, since it's a regular built-in.
# unset is a special built-in, so it will be found before functions.
unset -f command
# Get a reliable path prefix, handling case where getconf is not
# available.
SYSPATH="$(command -p getconf PATH 2>/dev/null)"
if [[ -z "$SYSPATH" ]]; then
SYSPATH="/usr/bin:/bin" # pick your poison
fi
PATH="$SYSPATH:$PATH"
3.2 受限Shell
受限shell旨在限制用户的移动和文件写入能力,通常用于来宾账户。POSIX未规定环境必须提供受限shell,但
ksh93
和
bash
都提供了此功能。
3.2.1 ksh93受限shell
当以
rksh
调用(或使用
–r
选项)时,
ksh93
作为受限shell。可以通过在用户的
/etc/passwd
条目中设置
rksh
的完整路径名来使登录shell受限,前提是
ksh93
可执行文件有一个名为
rksh
的链接。受限
ksh93
对用户有以下限制:
- 无法更改工作目录,使用
cd
会报错
ksh: cd: restricted
。
- 不允许将输出重定向到文件,包括使用
exec
。
- 不能为环境变量
ENV
、
FPATH
、
PATH
或
SHELL
分配新值,也不能使用
typeset
更改其属性。
- 不能指定包含斜杠(
/
)的命令路径名,只能运行
$PATH
中的命令。
- 不能使用
builtin
命令添加新的内置命令。
3.2.2 bash受限shell
当以
rbash
调用时,
bash
作为受限shell,同样需要
bash
可执行文件有一个名为
rbash
的链接。
bash
的受限操作与
ksh93
类似,包括不能使用
cd
更改目录、设置或取消设置
SHELL
、
PATH
、
ENV
或
BASH_ENV
等。
这些限制在用户的
.profile
和环境文件运行后生效,这意味着受限shell用户的整个环境在
.profile
中设置,系统管理员可以根据需要配置环境。为防止用户覆盖
~/.profile
,仅将文件设置为只读是不够的,要么用户的主目录不可写,要么
.profile
中的命令应切换到不同的目录。常见的设置此类环境的方法是设置一个“安全”命令目录,并将其作为
PATH
中的唯一目录,或者设置一个特定的环境。
通过以上对shell会话、可移植性和安全性的介绍,我们可以更好地理解和使用shell,编写更健壮、安全的脚本。在实际应用中,应根据具体需求和场景选择合适的shell和编写方法,同时遵循安全建议,避免潜在的安全风险。
3.3 特洛伊木马与避免方法
“特洛伊木马”是一种恶意程序,它会伪装成正常的可执行文件,当用户在
PATH
中包含当前目录时,攻击者可以在当前目录放置一个与系统命令同名的恶意程序,当用户执行该命令时,就会执行恶意程序。
为了避免“特洛伊木马”攻击,关键在于不将当前目录(
.
)放入
PATH
中,确保可执行程序仅来自标准系统目录。例如,在编写脚本时,使用绝对路径调用命令,而不是依赖
PATH
来查找命令。
3.4 setuid shell脚本与Korn shell的特权模式
3.4.1 setuid和setgid的概念
- setuid :当一个程序被设置了setuid位后,用户执行该程序时,会以该程序所有者的身份运行。例如,如果一个root用户拥有的程序设置了setuid位,普通用户执行该程序时会拥有root权限。
- setgid :与setuid类似,但它是以程序所属组的身份运行。使用setgid可以限制可能造成的损害范围,因为它只提升组权限。
3.4.2 注意事项
- 尽量使用setgid而非setuid,以减少潜在的安全风险。
- 如果必须使用setuid来访问一组文件,考虑创建一个新的非root用户,并将setuid设置为该用户。
- 要尽量减少setuid代码的数量,将其移到一个单独的程序中,并在必要时从较大的脚本中调用。同时,要以防御性的方式编写代码,假设脚本可以被任何人从任何地方调用。
4. 总结与实际应用建议
4.1 知识总结
通过前面的介绍,我们了解了shell会话的不同类型(交互式、非交互式、登录会话)及其启动和终止时调用的文件。对于Z-Shell,其启动和终止过程较为复杂,涉及多个初始化文件和脚本。同时,我们也掌握了编写安全shell脚本的重要建议,包括避免“特洛伊木马”攻击、使用受限shell、正确处理setuid和setgid等。
4.2 实际应用建议
4.2.1 脚本编写流程
为了编写安全且可移植的shell脚本,可以遵循以下流程:
1.
规划阶段
:明确脚本的功能和需求,设计脚本的整体结构。考虑可能出现的错误情况,并计划如何处理。
2.
编码阶段
:根据设计编写代码,遵循安全脚本编写的建议。使用完整路径名调用命令,检查输入参数的有效性,处理命令的错误代码等。
3.
测试阶段
:对脚本进行全面测试,尝试从攻击者的角度思考,寻找可能的漏洞。可以使用不同的输入和场景进行测试,确保脚本在各种情况下都能正常工作。
4.
审查阶段
:让他人审查代码,以发现自己可能遗漏的问题。
5.
部署阶段
:在部署脚本时,确保脚本运行的环境安全,例如设置正确的文件权限,避免将敏感信息暴露。
4.2.2 不同shell的选择
-
如果需要兼容性和广泛的应用场景,
bash是一个不错的选择,它支持许多扩展功能,并且在大多数系统中都有预装。 -
如果对性能和特定功能有较高要求,
ksh93可能更适合,它也有丰富的扩展和强大的编程能力。 -
zsh具有复杂而灵活的定制功能,适合需要高度个性化配置的用户。
4.3 安全意识的重要性
在编写和使用shell脚本时,始终要保持安全意识。安全问题可能随时出现,即使是看似简单的脚本也可能存在潜在的漏洞。因此,要不断学习和更新安全知识,关注最新的安全威胁和防范方法。
5. 示例与实践
5.1 安全脚本示例
以下是一个简单的安全shell脚本示例,它遵循了前面提到的一些安全建议:
#!/bin/bash
# Reset IFS
IFS=$' \t\n'
# Unset all aliases
unset -f unalias
\unalias -a
# Set a reliable path
SYSPATH="$(command -p getconf PATH 2>/dev/null)"
if [[ -z "$SYSPATH" ]]; then
SYSPATH="/usr/bin:/bin"
fi
PATH="$SYSPATH:$PATH"
# Check input arguments
if [ $# -ne 1 ]; then
echo "Usage: $0 <number>"
exit 1
fi
if ! [[ $1 =~ ^[0-9]+$ ]]; then
echo "Error: Input must be a number."
exit 1
fi
# Do some operations
echo "The input number is: $1"
5.2 实践操作
为了更好地掌握这些知识,可以进行以下实践操作:
-
编写脚本
:根据自己的需求编写一些简单的shell脚本,并在编写过程中应用安全建议。
-
测试脚本
:使用不同的输入和场景测试脚本,检查脚本的安全性和稳定性。
-
使用受限shell
:创建一个受限shell环境,体验受限shell的功能和限制。
6. 总结
通过对shell会话、可移植性、安全性等方面的深入了解,我们可以编写更安全、更健壮的shell脚本。在实际应用中,要根据具体情况选择合适的shell和编写方法,同时始终保持安全意识,遵循安全建议,不断提高脚本的质量和安全性。
6.1 关键要点回顾
- 不同类型的shell会话(交互式、非交互式、登录会话)在启动和终止时调用不同的文件。
-
编写安全shell脚本的建议,如不将当前目录放入
PATH、检查输入参数、处理错误代码等。 - 受限shell可以限制用户的操作,提高系统的安全性。
- 避免“特洛伊木马”攻击,正确处理setuid和setgid。
6.2 未来展望
随着技术的不断发展,shell脚本的应用场景也在不断扩大。未来,我们可以期待更强大、更安全的shell工具和防护机制的出现。同时,我们也应该不断学习和更新知识,以适应新的安全挑战。
以下是一个简单的mermaid流程图,展示了安全脚本编写的基本流程:
graph LR
A[规划阶段] --> B[编码阶段]
B --> C[测试阶段]
C --> D[审查阶段]
D --> E[部署阶段]
表格:不同shell类型的特点对比
| Shell类型 | 特点 |
| ---- | ---- |
| bash | 兼容性好,支持许多扩展功能,大多数系统预装 |
| ksh93 | 性能高,有丰富的扩展和强大的编程能力 |
| zsh | 定制功能复杂灵活,适合高度个性化配置 |
通过以上内容,希望能帮助你更好地理解和应用shell脚本的相关知识,编写安全可靠的脚本。
超级会员免费看
11

被折叠的 条评论
为什么被折叠?



