4. PASE 文件系统安全:根文件系统与 QOpenSys、文件权限与 ACL、符号链接攻击与防御

好,我们进入第四章。文件系统安全,说白了就是管好你的地盘。在 PASE 环境里,这个地盘有点特殊——它不是 AIX,也不是 Linux,但长得又很像。很多从 Linux 过来的朋友,第一次接触 PASE 文件系统时都会踩坑。我当年也一样,差点把生产环境搞崩。

这一章我们聚焦三个核心点:根文件系统与 QOpenSys 的区别、文件权限与 ACL 的正确用法、以及符号链接攻击的防御。嗯,每个点都跟安全直接挂钩。

4.1 根文件系统与 QOpenSys:两个世界的交汇

PASE 环境里有两个“根”,容易让人混淆。一个是传统的 IBM i 根文件系统('/'),另一个是 QOpenSys 文件系统('/QOpenSys')。

它们的关系,我画了张图,你看一眼就明白了。

PASE 文件系统结构 IBM i 根文件系统 (/) 传统 AS/400 文件系统 区分大小写?不区分 权限模型:IBM i 对象权限 路径示例:/home/user 符号链接:不支持 硬链接:不支持 QOpenSys 文件系统 类 UNIX 文件系统 区分大小写:区分 权限模型:POSIX 权限 + ACL 路径示例:/QOpenSys/home/user 符号链接:支持 硬链接:支持 关键区别:QOpenSys 是 PASE 的“原生”文件系统,支持 POSIX 语义

简单说,QOpenSys 是 PASE 的“亲儿子”。它支持大小写敏感、符号链接、硬链接、POSIX 权限和 ACL。而传统的 IBM i 根文件系统,虽然也能被 PASE 访问,但它的权限模型是 IBM i 那一套——对象权限,跟 UNIX 完全不同。

核心原则:所有 PASE 应用程序的数据文件、配置文件、日志文件,都应该放在 QOpenSys 下。根文件系统只用来放那些必须与 IBM i 原生环境交互的东西。

我在项目中遇到过一家客户,他们把 Apache 的配置文件放在了根文件系统下,结果符号链接用不了,权限也调不对。折腾了两天,最后全挪到 QOpenSys 下,十分钟搞定。

4.2 文件权限与 ACL:别只靠 chmod 777

很多人在 PASE 上配权限,习惯性地 chmod 777。嗯,我理解,图省事嘛。但你要知道,PASE 环境通常跑着关键业务——ERP、数据库、支付接口。777 等于把大门敞开,谁都能进。

PASE 支持标准的 POSIX 权限(rwxrwxrwx),也支持 ACL(访问控制列表)。ACL 比传统权限更灵活,可以针对单个用户或组设置精细权限。

4.2.1 传统 POSIX 权限

这个大家熟,我就不多讲了。只提醒一点:PASE 的 umask 默认值可能跟 Linux 不一样。我建议你在启动脚本里显式设置 umask,比如:

# 在 .profile 或启动脚本中设置
umask 027
# 效果:文件 640,目录 750

这样新创建的文件默认对同组用户只读,对其他用户完全关闭。

4.2.2 ACL:精细控制的利器

当传统权限不够用时,ACL 就派上用场了。比如,你希望某个文件对用户 A 可读写,对用户 B 只读,对组 C 可执行——传统权限做不到,ACL 可以。

PASE 上使用 ACL 的命令跟 Linux 一样:

# 查看 ACL
getfacl /QOpenSys/app/config/app.conf

# 设置 ACL:给用户 webadmin 读写权限
setfacl -m u:webadmin:rw /QOpenSys/app/config/app.conf

# 设置默认 ACL(对新文件生效)
setfacl -d -m u:webadmin:rw /QOpenSys/app/config/

我的习惯:对于多用户共享的目录,我总会在创建时就设好默认 ACL。这样新文件自动继承权限,不会出现“哎呀,我明明给了权限,怎么还报错”的情况。

4.2.3 权限检查顺序

PASE 检查权限时,顺序是这样的:

  1. 如果用户是文件所有者,使用所有者权限
  2. 否则,检查用户是否在 ACL 中有显式条目,有则使用 ACL 权限
  3. 否则,检查用户所属组,使用组权限
  4. 否则,使用其他用户权限

注意:ACL 的优先级高于组权限。这意味着你可以通过 ACL 给某个用户单独授权,而不影响其他组员。

4.3 符号链接攻击与防御:看不见的陷阱

符号链接(symlink)是 PASE 的一大特色,也是安全风险的高发区。攻击者可以利用符号链接,让一个低权限进程去读写它本不该碰的文件。

4.3.1 经典攻击场景:TOCTOU 竞态条件

最常见的攻击是 TOCTOU(Time-of-Check Time-of-Use)。流程是这样的:

  1. 一个特权进程检查文件 /tmp/tmpfile 的权限,发现它属于普通用户,允许访问
  2. 在检查通过后、实际打开文件前,攻击者迅速把 /tmp/tmpfile 替换成一个指向 /etc/passwd 的符号链接
  3. 特权进程打开文件,实际上操作的是 /etc/passwd

为什么会这样?因为检查权限和打开文件是两个操作,中间有时间窗口。

我曾经在一个客户现场遇到过类似问题。他们的备份脚本会以 root 权限遍历 /tmp 目录,处理所有 .bak 文件。攻击者创建了一个符号链接 /tmp/evil.bak -> /QOpenSys/etc/security/passwd,结果备份脚本把密码文件当普通备份文件处理了……还好发现得早,没造成泄露。

4.3.2 防御策略

防御符号链接攻击,我总结了几个实用方法:

防御方法 说明 适用场景
使用 O_NOFOLLOW 标志 打开文件时指定 O_NOFOLLOW,如果路径最后一部分是符号链接,则打开失败 所有文件打开操作
使用 O_PATH + fstat 先以 O_PATH 打开路径,再用 fstat 检查文件类型,确认不是符号链接后再打开 需要兼容旧系统的场景
限制 /tmp 目录权限 使用 sticky bit(chmod +t),防止用户删除不属于自己的文件 共享临时目录
使用专用临时目录 为每个应用创建独立的临时目录,权限设为 700 高安全环境
使用 mkstemp/mkdtemp 创建临时文件时使用安全函数,避免可预测的文件名 代码开发

代码层面,我建议这样写:

// C 语言示例:安全打开文件
int fd = open("/tmp/datafile", O_RDONLY | O_NOFOLLOW);
if (fd == -1) {
    // 处理错误:可能是符号链接,或者文件不存在
    perror("open");
    return -1;
}

// 进一步检查:使用 fstat 确认文件类型
struct stat st;
if (fstat(fd, &st) == -1) {
    close(fd);
    return -1;
}
if (!S_ISREG(st.st_mode)) {
    // 不是普通文件,拒绝操作
    close(fd);
    errno = EACCES;
    return -1;
}

记住:检查文件类型时,一定要用 fstat(基于文件描述符),而不是 stat 或 lstat(基于路径名)。因为路径名可能在你检查之后又被替换了。fstat 操作的是已经打开的文件,攻击者无法中途替换。

4.3.3 审计与监控

除了代码层面的防御,你还可以通过审计来发现符号链接攻击的痕迹。PASE 的审计日志可以记录符号链接的创建和访问事件。

我建议开启以下审计事件:

  • 符号链接创建:记录谁在什么时候创建了符号链接,指向哪里
  • 跨目录符号链接:记录指向其他目录的符号链接,特别是从 /tmp 指向系统目录的
  • 特权进程的符号链接访问:记录以 root 或高权限用户身份访问符号链接的行为

你可以用 IBM i 的审计功能,或者自己写脚本定期扫描关键目录下的符号链接:

# 扫描 /tmp 下指向敏感文件的符号链接
find /tmp -type l -exec ls -la {} \; 2>/dev/null | \
  grep -E "(/etc|/QOpenSys/etc|/QOpenSys/usr/lib)"

这个脚本虽然简单,但很实用。我把它放在 cron 里,每天跑一次,有异常就发告警。

4.4 本章小结

文件系统安全,说白了就是管好三件事:放对地方、设对权限、防住链接。

  • 应用程序数据放 QOpenSys,别放根文件系统
  • 权限用 ACL,别偷懒用 777
  • 打开文件时用 O_NOFOLLOW,别给攻击者留窗口

嗯,这些原则听起来简单,但我在实际项目中见过太多因为忽视它们而导致的安全事件。希望你能把这些方法用起来,别走我踩过的坑。