根用户与不可变性:OpenBSD chflags 与日志篡改
When Root Meets Immutable: OpenBSD Chflags vs. Log Tampering

原始链接: https://rsadowski.de/posts/2025/openbsd-immutable-system-logs/

## OpenBSD上的不可变日志与ISO 27001合规性 ISO 27001要求保护日志数据免遭篡改,但并未指定*如何*实现。本文演示了一种使用OpenBSD内置文件系统标志——`sappnd`(仅追加)和`schg`(不可变)来实现此目的的解决方案。 标准的OpenBSD日志设置虽然可预测,但依赖于以root身份运行的每小时轮转,这会产生漏洞。拥有root权限的攻击者可以修改或删除日志。为了解决这个问题,作者建议将`sappnd`应用于活动日志文件,防止修改,并使用`schg`归档轮转的日志,使其永久不可变。 实施过程包括禁用自动`newsyslog`轮转,创建归档目录,应用标志,并利用`/etc/rc.securelevel`在启动期间临时降低系统安全性。这允许在恢复安全性之前进行日志轮转和重新应用不可变标志,从而防止即使是root用户进行篡改。 这种方法提供了一种强大、自包含的取证完整性解决方案,满足了ISO 27001的意图,且无需外部依赖。NetBSD和FreeBSD中存在类似标志,可以提供可比的安全优势。

## 黑客新闻讨论:OpenBSD 日志不可变性 一篇关于 OpenBSD 的 `chflags` 命令实现日志不可变性的文章,引发了黑客新闻的讨论,核心在于依赖文件系统层面的不可变性来保障安全存在的局限性。虽然 OpenBSD 允许即使是 root 用户也无法篡改日志(修改需要重启),但评论者普遍认为这不足以抵御决心强、拥有 root 权限或物理访问权限的攻击者。 大家的共识是,**将日志流式传输到独立的、安全管理的日志服务器是更可靠的解决方案**,这与 PCI 合规性等标准相符。这种方法解决了攻击者攻陷原始系统带来的风险,并涵盖了系统故障可能导致的数据丢失问题。 许多用户指出,即使具有本地不可变性,攻击者仍然可以掩盖他们的踪迹或用垃圾信息填充日志。还有人指出 Linux (`chattr`) 和 macOS 也存在类似的功能。这场讨论强调了便利性和安全性之间的权衡,本地不可变日志可能提供一层有用的、但有限的防御,尤其适用于小型设置。最终,采用纵深防御策略,并进行异地日志记录,被认为是最高效的方法。
相关文章

原文

Why ISO 27001 Demands Immutable Logs (Without Actually Saying So) #

ISO 27001 is like that careful lawyer who never says exactly what they mean – it tells you what needs to be achieved, not how to do it. When it comes to logging, this is particularly telling: Control A.12.4.2 simply states that “logging information and logging facilities shall be protected against tampering and unauthorized access.” Period. How? That’s your problem to solve.

But anyone who’s ever had to investigate a security incident knows the harsh reality: logs are only as trustworthy as their protection against post-incident tampering. An attacker who gains root access isn’t going to politely leave their tracks in the log files – unless they physically can’t alter them anymore.

Anyone who follows this blog knows I’ve been diving into VFS/FFS code lately, and during that exploration, I stumbled across SF_APPEND and SF_IMMUTABLE flags in the kernel source. “IMMUTABLE?” I thought. “What the hack, OpenBSD can do that? Must be dead code ;)”. Turns out these flags are very much alive and can be beautifully set using chflags. I started playing around with them in /tmp, and for days afterward, I’d get a bunch of error messages on every boot because OpenBSD, as you know, cleans /tmp on startup. I left it like that for a while – just another quirky reminder of my filesystem experiments.

Then a client I administrate several OpenBSD servers for asked me about ISO 27001 compliance. Suddenly, those “dead” immutable flags didn’t seem so useless anymore. In fact, they turned out to be exactly what the standard was asking for, even if it doesn’t explicitly say so.

This is where immutability becomes not just a nice-to-have, but a forensic necessity. While ISO 27001 never mentions the word “immutable”, it’s essentially describing exactly that: logs that cannot be modified after they’re written. This isn’t security theater – it’s about maintaining the integrity of your audit trail when it matters most.

But there’s a deeper motivation here beyond just checking the compliance box. If an attacker gains root privileges on your system without you knowing, you have a massive problem. Your logs might be the only evidence of what happened, when it happened, and how extensive the breach was. If those logs can be silently modified or deleted, you’re essentially flying blind during the most critical moment of your security response.

The following blog post shows one approach to having secure, root-tamper-proof logs and archiving them IMMUTABLE without any external system.

OpenBSD’s Default Logging: Simple but Effective #

OpenBSD ships with a straightforward logging configuration that handles most system events out of the box. The default /etc/syslog.conf follows a clean separation of concerns:

*.notice;auth,authpriv,cron,ftp,kern,lpr,mail,user.none	/var/log/messages
kern.debug;syslog,user.info				/var/log/messages
auth.info						/var/log/authlog
authpriv.debug						/var/log/secure
cron.info						/var/cron/log
daemon.info						/var/log/daemon
ftp.info						/var/log/xferlog
lpr.debug						/var/log/lpd-errs
mail.info						/var/log/maillog

# ....
#!doas
#*.*							/var/log/doas

This configuration creates a logical hierarchy: general system messages land in /var/log/messages, while specific services get their own dedicated log files. Authentication attempts go to /var/log/authlog, privileged operations to /var/log/secure, and so on.

The beauty of this setup is its predictability. When you need to investigate a login issue, you know exactly where to look. Mail problems? Check /var/log/maillog. System crashes? /var/log/messages has your answers.

But here’s where it gets interesting from a security perspective: newsyslog runs as a root cron job every hour, rotating these logs automatically. A quick look at /etc/newsyslog.conf shows the rotation schedule:

/var/cron/log		root:wheel	600  3     10   *     Z
/var/log/authlog	root:wheel	640  7     *    168   Z
/var/log/daemon				640  5     300  *     Z
/var/log/lpd-errs			640  7     10   *     Z
/var/log/maillog			640  7     *    24    Z
/var/log/messages			644  5     300  *     Z
/var/log/secure				600  7     *    168   Z
/var/log/wtmp				644  7     *    $M1D4 B ""
/var/log/xferlog			640  7     250  *     Z
/var/log/pflog				600  3     250  *     ZB "pkill -HUP -u root -U root -t - -x pflogd"
/var/www/logs/access.log		644  4     *    $W0   Z "pkill -USR1 -u root -U root -x httpd"
/var/www/logs/error.log			644  7     250  *     Z "pkill -USR1 -u root -U root -x httpd"

While this prevents logs from consuming all available disk space, it also means that log files are regularly recreated, moved, and compressed. From an attacker’s perspective, this creates windows of opportunity – not just to modify logs, but to potentially interfere with the rotation process itself.

Even more concerning: any attacker with root access can simply open the log files directly and delete specific lines, modify timestamps, or insert false entries. There’s nothing stopping rm /var/log/authlog or a quick sed -i '/failed login/d' /var/log/secure to clean up those failed authentication attempts.

This is exactly where the default setup meets its limitations for ISO 27001 compliance. Those hourly rotations and the fact that everything runs as root means our logs are only as secure as our root access control. And if root is compromised, well… that’s precisely when we need our logs to be most trustworthy.

Understanding chflags: System-Level Immutability #

OpenBSD’s chflags command provides fine-grained control over file attributes at the filesystem level. According to the manpage, several flags are available, but for our log protection purposes, we’re particularly interested in:

  • sappnd - set the system append-only flag (superuser only)
  • schg - set the system immutable flag (superuser only)

Our strategy is simple: use sappnd for active log files that need to grow, and schg for archived logs that should never change.

Let’s see this in action. First, let’s check the current flags on a log file:

$ ls -lo /var/log/secure
-rw-------  1 root  wheel  - 748935 Jul 16 19:11 /var/log/secure
                          ^^^

The -o flag shows us file flags, and we see none are set (the dash after wheel). Now let’s set the append-only flag:

# chflags sappnd /var/log/secure
# ls -lo /var/log/secure  
-rw-------  1 root  wheel  uappnd 748935 Jul 16 19:11 /var/log/secure

Notice something interesting? We set sappnd but see uappnd. That’s because we’re running as root (the owner), so the system flag becomes a user flag from our perspective. We could have used uappnd directly with the same result. The behaviour makes the removal of flags somewhat error-prone.

Now comes the fascinating part. Let’s try to remove this flag as root:

# chflags nosappnd /var/log/secure
chflags: /var/log/secure: Operation not permitted

Even root can’t remove it! This is where OpenBSD’s security model shines. According to the manpage:

The superuser-settable sappnd and schg flags can be set at any time, but may only be cleared when the system is running at security level 0 or -1 (insecure or permanently insecure mode, respectively). For more information on setting the system security level, see securelevel(7).

This brings securelevel(7) into play – I think the only practical user-defined use case for OpenBSD’s securelevel(7) (All other use cases are not really useful for the end user). We can only modify these flags in 0 Insecure mode where “system file flags may be cleared with chflags(2)”.

Of course, we don’t want to manually boot into single-user mode every time we need to manage log rotation or we have to remove/modify chflags. Fortunately, OpenBSD provides /etc/rc.securelevel – commands that run before the security level changes. This is exactly what we need to automate flag management during the boot process.

Complete Setup: Immutable Logging Implementation #

Now let’s put it all together into a complete, production-ready setup.

Step 1: Disable the hourly newsyslog cron job #

Since we can’t rotate files with sappnd flags through the normal newsyslog(1) process (append-only means the file can’t be moved or renamed), we need to disable the automatic rotation:

# Comment out or remove newsyslog from root's crontab
# crontab -e

Step 2: Create the log archive directory #

We need a dedicated space for our immutable archived logs: mkdir /var/log/archive.

If you have existing rotated logs, move them to the archive and make them permanently immutable:

# mv /var/log/secure.0.* /var/log/archive/
# chflags -R schg /var/log/archive/

If schg is set on these archived files, they become completely immutable - perfect for long-term forensic preservation.

Step 3: Set append-only flag on active logs #

Now protect the active log files:

# chflags sappnd /var/log/secure
# chflags sappnd /var/log/authlog
# chflags sappnd /var/log/messages
# ....

Step 4: Create the securelevel script #

This is where the magic happens. Create /etc/rc.securelevel:

# touch /etc/rc.securelevel && chmod 700 /etc/rc.securelevel

Write your management script. Here is an example for /var/log/archive.

#!/bin/sh
# Remove append-only and immutable flags
chflags nosappnd /var/log/secure /var/log/authlog /var/log/messages
chflags -R noschg /var/log/archive

# Run newsyslog and force log rotation regardless of size/age
newsyslog -F -a /var/log/archive

# Restore append-only and immutable flags
chflags sappnd /var/log/secure /var/log/authlog /var/log/messages
chflags -R schg /var/log/archive

This script runs during boot before the security level increases, giving us a brief window to manage our immutable files. It rotates logs into the archive directory and immediately makes them immutable again.

The beauty of this setup? Once the system reaches normal security level, even root cannot tamper with these logs without rebooting into single-user mode – exactly the kind of forensic integrity ISO 27001 demands.

return 0; #

I hope this post was helpful for anyone facing similar challenges. These flags aren’t unique to OpenBSD – you’ll find these and additional options in NetBSD and FreeBSD as well. FreeBSD probably has even cooler solutions for this problem using ZFS features, and if not, maybe this could serve as inspiration for someone.

On that note, I’d like to thank the BSDNow podcast (which I discovered during workouts at the gym) for reading my last blog post on their show. Thank you!

This implementation provides filesystem-level log protection that satisfies ISO 27001 requirements while leveraging OpenBSD’s built-in security features. No additional software, no network dependencies – just the kernel enforcing immutability where it matters most.

联系我们 contact @ memedata.com