Shell脚本学习指南-第十五章-安全的Shell脚本起点
第15章 安全的Shell脚本:起点
背景 Shell脚本很容易引入安全漏洞;本章讲解写安全脚本的原则、限制性shell、特洛伊木马、setuid脚本风险。 注意:本章仅shell层面安全,不是完整系统安全教程。
15.1 安全Shell脚本编写提示
目标:抵御输入篡改、环境篡改、木马、竞争条件等攻击。下面是实操要点+示例。
1. PATH环境变量安全
- 不要把当前目录
.放入PATH,会触发特洛伊木马风险。 - PATH内的目录,权限必须严格:目录仅属主可写,其他用户不能写。
- 脚本开头强制重置PATH为可信系统路径,不要继承外部传入PATH。
安全初始化示例:
command 和 getconf 详解
command -p 与 getconf 拆解
目的:拿到系统出厂默认、干净可信的 PATH,完全不继承当前环境被篡改过的 PATH,安全脚本专用。
command内置命令
command 是 shell 内置命令。
语法:
command [-p] 命令 [参数]
两个核心作用:
- 绕开函数:即使你写了同名 shell 函数,
command xxx强制执行外部真实命令,不去调用函数,防止函数劫持。 ‑p选项(重点):使用系统默认的安全 PATH去查找程序,忽略你当前脚本环境里被修改、被污染的 PATH 变量。
command -p:不管现在$PATH被攻击者改成什么,使用系统内置原始搜索路径来找可执行程序。
示例区分:
getconf命令(外部工具,POSIX 标准)
getconf:查询系统配置参数,查询操作系统编译、环境的标准设置。
语法:
|
|
这里:
|
|
输出 POSIX 标准规定的系统默认 PATH 字符串,这是操作系统内置的干净 PATH,不受用户、脚本当前环境$PATH变量的影响。
举个输出例子:
为什么不用直接写
echo $PATH?$PATH是当前环境变量,攻击者可以恶意篡改;getconf PATH读取系统本身的标准配置,不是读环境变量。
但是!
有些老旧 UNIX 系统没有 getconf 这个工具,命令执行会报错,输出为空。
[[ -z "$SYSPATH" ]] 判断,如果拿不到结果(getconf 不存在),手动兜底写死默认值 /usr/bin:/bin。
整行完整拆解,一步一步执行
|
|
command -p:使用系统原始搜索路径,无视当前被污染的 PATH,去寻找getconf程序,防止被伪造的 getconf 木马劫持。- 执行
getconf PATH,读取操作系统标准默认 PATH。 2>/dev/null:屏蔽错误;没有 getconf 工具时,不会打印错误。- 命令替换
$(...),把输出赋值给变量SYSPATH。
- 如果执行结果为空(系统没有 getconf),手动设置兜底的安全 PATH。
|
|
脚本把自己的 PATH 替换为这套干净、可信的系统路径。
安全脚本的关键点:不再继承外部传入的 PATH 环境变量,杜绝木马攻击。
组合记忆表格
| 片段 | 作用 |
|---|---|
command |
绕开同名 shell 函数,执行真正外部命令 |
command -p |
使用系统原始 PATH 查找命令,忽略当前环境 PATH |
getconf PATH |
查询操作系统内置标准 PATH 配置,不是读取环境变量 $PATH |
2>/dev/null |
丢弃错误输出,兼容没有 getconf 的老旧系统 |
2. 校验所有输入
- 期待数字:必须校验输入是数字,并且在合法范围。
- 用户输入参数,全部务必加双引号包裹
"$1""$@",防止元字符注入。
3. 不信任外部传入环境变量
攻击者可以篡改环境变量:PATH、IFS、TZ。
脚本启动主动重置
PATH、IFS,不要继承外部环境。
4. 切换到已知工作目录
使用相对路径前,先cd到确定目录,并且判断cd是否执行成功。
|
|
5. 尽量使用命令完整绝对路径
不依赖PATH查找,避免被劫持。
|
|
6. 谨慎使用eval
尽量不要直接把用户输入交给eval,极易被注入攻击。
7. 命令出错要检查返回状态
外部命令有可能被恶意环境强迫失败,不要假设命令一定执行成功。
8. 日志审计,使用logger记录行为
记录执行时间、操作用户,方便事后排查。
$*:把全部位置参数,用 IFS 第一个字符(默认空格)拼接成单个字符串。这里只有 1 个参数,$*就等于这个完整字符串。
9. 警惕符号链接、竞争条件
- 处理文件前,判断是否为符号链接,
[ -L file ],防止指向系统关键文件。 - 两个命令之间存在时间窗口,攻击者可以修改文件,尽量避免这种窗口。
10. setuid/setgid 建议
- 优先
setgid,危害范围限制在组,而不是setuid。 - 尽量不要直接setuid到root;新建普通专用账号来完成权限操作。
- setuid代码尽量精简,独立拆分。
15.2 限制性Shell restricted shell
作用:限制普通用户操作,多用于guest访客账号,防止用户做危险操作。 支持:bash(rbash)、ksh93(rksh);POSIX没有标准化,设置环境非常麻烦,不推荐生产使用。
如何启用
- ksh93:程序链接为
rksh,或者使用ksh -r;passwd登录shell写rksh完整路径。 - bash:程序链接为
rbash,或者bash -r。
⚠限制生效时机:用户的.profile环境脚本执行完毕之后才开启限制。管理员在profile里面预置安全环境。
ksh93 rksh 禁止行为
- 不能
cd切换目录 - 禁止输出重定向:
> >> >| - 不能修改环境变量:
PATH ENV FPATH SHELL - 只能执行PATH内命令;不能写带
/的绝对路径命令。
bash rbash 额外禁止行为
- 不能执行
set +r关闭限制模式 - `. source 命令不能带带/路径的文件
- exec替换shell被禁止
重要坑
- 配置
.profile权限必须保护,用户不能修改profile文件,否则绕过限制。 - PATH里面不能放其他shell程序、不能放vi/ed/ex这类带shell逃逸的编辑器;用户调用编辑器内部shell即可突破限制。
现实:限制性shell很难配置完全安全,实际工作尽量寻找替代方案。
15.3 特洛伊木马 Trojan horse
背景:伪装成正常程序,偷偷执行恶意代码。最经典攻击手段:PATH顺序 + 可写目录。
攻击示例
用户jprog,个人bin目录放在PATH最前面,且这个bin目录其他用户可以写入。
攻击者放一个伪造grep脚本到该目录:
PATH 继承原理
继承 PATH 的原理
Linux/Unix:子进程默认继承父进程全部环境变量(PATH、HOME、TZ、IFS 等等)。
PATH 本质就是一个环境变量 (environment variable)。
父进程的环境变量,会复制一份,传给它创建出来的子进程。
关键点区分两个概念:
- Shell 内部变量(普通变量):仅当前 shell 内部,不会传给子进程,需要
export导出,才会进入环境变量。 - 环境变量:存放在进程的环境块,
fork()创建子进程时,操作系统自动完整复制一份给子进程 → 子进程继承。
PATH 几乎永远都是export导出的环境变量,所以子进程默认拿到父的 PATH。
什么时候会继承 PATH?(父进程创建子进程就继承)
只要是父进程 fork+exec 启动新程序,子进程默认继承父全部环境(包含 PATH):
- 在 shell 里面执行外部命令(ls、grep、cat、su、sudo 等)
- 执行脚本
./test.sh su root(不带-/-l登录参数)- 管道、
$(...)命令替换、()子 shell ssh user@host command非登录执行一条远程命令
以上全部:子进程复制父进程的环境变量,PATH 原样继承。
什么时候不会继承旧 PATH?(会重置 PATH)
触发「登录 shell」的时候,系统会重新初始化环境,读取目标用户的/etc/profile、~/.profile,丢弃继承过来的 PATH,重新设置成该用户的标准 PATH。
su 的两种模式(重点,对应书上木马案例)
① su root (没有横杠 -)
- 只是切换用户 ID,不启动登录 shell
- 继承原来用户全部环境变量(PATH 保留原来的值!!)
jprog 执行
su root,父 shell 的 PATH=~jprog/bin:/usr/bin:/bin,root 的 su 子进程直接继承这份 PATH,这就是木马攻击能成功的根源。
② su - root / su -l root(带横杠,登录模式)
- 启动登录 shell,模拟用户真正登录系统
- 丢弃继承来的旧环境;执行 root 的 profile,生成 root 自己干净的 PATH。
此时 PATH 不再包含
~jprog/bin,木马就不会触发。
其他会重置 PATH 的场景
ssh user@host不带命令,交互式登录:登录 shell,加载 profile,重置 PATH。- 直接本机控制台输入账号密码登录:登录 shell,重置 PATH。
sudo:默认行为要看 sudo 配置/etc/sudoers;- 默认很多系统
sudo会重置 PATH 为安全值,不会继承用户 PATH; - 如果 sudoers 设置
Defaults env_keep += "PATH",就会继承用户 PATH(危险配置)。
- 默认很多系统
脚本自身
脚本执行的时候,作为子 shell,继承调用者的 PATH 环境变量。
⚠这就是为什么安全脚本开头必须手动重置 PATH!因为调用脚本的外部环境 PATH 可能被攻击者篡改。
通俗流程演示木马案例
- jprog 登录,登录 shell:PATH=
~jprog/bin:/usr/bin:/bin(环境变量,已 export) - jprog 执行
su root- su 是子进程,继承 jprog 的 PATH:
~jprog/bin:/usr/bin:/bin - su 把 UID 切换成 root,但环境变量没有重置
- su 是子进程,继承 jprog 的 PATH:
- 在这个 su 出来的 root shell 里面执行
grep - shell 根据继承过来的 PATH 查找命令,优先找到木马脚本
~jprog/bin/grep - 木马以 root 权限运行。
如果是
su - root: su 启动 root 的登录 shell,加载 root 的 profile,抛弃继承过来的 PATH,重新设置 PATH=/usr/sbin:/usr/bin:/sbin:/bin,不再包含 jprog 的 bin 目录,攻击失效。
一张表总结
表格
| 操作 | 是否继承父进程 PATH | 原因 |
|---|---|---|
su root(无-) |
继承 | 非登录 shell,仅改 UID,保留全部环境变量 |
su - root / su -l root |
重置 PATH | 登录 shell,读取目标用户 profile |
./myscript.sh |
继承 | 子 shell,继承调用方环境变量 |
ssh root@ip 'grep xxx'(远程执行单条命令,非登录) |
继承 ssh 客户端环境(远程服务器这边一般不会继承客户端本地 PATH,远程是 sshd 设置环境) | 非登录 shell |
ssh root@ip(交互式登录) |
重置 PATH | 登录 shell,加载 profile |
攻击流程:
- jprog执行
su切换root,继承原来的PATH。 - PATH优先查找
~jprog/bin/grep,执行木马脚本。 - 脚本以root权限执行恶意代码,自我删除不留痕迹。
防御要点
- PATH不要把
.放在前面。 - PATH里面所有目录权限严格:只有属主可写,其他人禁止写。
- 脚本开头重置PATH为可信系统路径。
15.4 Shell脚本使用setuid:一个坏主意
概念回顾
- setuid(u+s):执行文件时,进程有效用户ID变成文件属主,而不是运行者的id。
- setgid(g+s):执行文件时,进程有效组ID变成文件属组。
- ls -l出现
s代替x代表开启setuid/setgid。
⚠现代Linux、BSD系统已经直接忽略shell脚本上的setuid位,就算chmod u+s,也不会生效。
漏洞示例
脚本/etc/setuid_script,属主root,设置setuid,开头:
|
|
攻击者操作:
会被解析为执行 /bin/sh -i,直接拿到setuid权限的交互式shell。
修复写法:脚本解释器参数末尾加横杠-
|
|
含义:后面全部参数当做文件名,不识别shell选项,规避该攻击。
重要区分:
- setuid shell脚本:大部分系统直接失效,漏洞巨大。
- setuid 二进制可执行程序:依旧生效,风险同样很高。
攻击者可以通过木马生成setuid的sh二进制文件,永久获取root权限。
15.5 ksh93 特权模式 privileged mode
作用:ksh93专门为setuid脚本设计的安全模式;当有效UID≠真实UID(开启setuid),自动开启
set -o privileged / set -p。
行为
- 自动读取
/etc/suid_profile,管理员在这里设置安全环境,把PATH设置只读。
- 如果手动关闭特权模式
set +p,有效UID会回退到真实UID,setuid权限丢失。
/etc/suid_exec 代理程序
ksh93配套工具:用来安全执行setuid脚本,校验脚本安全性。
现实工作:尽量不要写setuid脚本;优先使用
sudo完成权限提升。 示例:sudo /bin/ksh -p
15.6 本章小结
- 安全脚本基础原则:重置PATH/IFS;校验所有输入;双引号引用变量;不要信任外部环境;检查命令返回值;警惕符号链接与竞争条件。
- 限制性shell(rbash/rksh):限制用户行为,但配置极其复杂,容易被绕过,尽量寻找替代方案。
- 特洛伊木马:利用PATH顺序、目录权限漏洞伪装正常程序;防御:严格管控PATH和目录写权限。
- setuid shell脚本:强烈不推荐,现代UNIX直接忽略shell脚本setuid位;存在大量攻击面。
- ksh93特权模式
set -p是专门针对setuid脚本的保护机制,但依然需要专业知识;日常优先使用sudo代替setuid脚本。
高频速记卡片
| 要点 | 正确示例 | 错误示例 |
|---|---|---|
| 脚本初始化PATH | PATH="/bin:/usr/bin" |
继承外部PATH,PATH里面带. |
| 用户输入引用 | rm "$filename" |
rm $filename |
| cd跳转 | cd app || exit 1 |
cd app |
| setuid脚本开头 | #!/bin/sh - |
#!/bin/sh |
文章作者 会写代码的小郎中
上次更新 2011-07-28
许可协议 CC BY-NC-ND 4.0