资讯详情

资讯详情

嵌入式Linux安全加固实战:从最小化裁剪到防火墙策略

1. 安全加固的整体设计思路先弄清楚你在防谁嵌入式 Linux 和服务器 Linux 有个本质区别服务器的安全威胁大多来自外部网络你防的是几百公里外不知名的攻击者而嵌入式设备往往就暴露在物理可接触的场景里——机房里的一台边缘网关、路侧的一台智能终端、工厂产线上一块工控板谁都能靠近它、插个 U 盘、按一下复位键、甚至直接拆开外壳把 Flash 焊下来。所以嵌入式安全加固的第一课不是“装个杀毒软件”而是先建立一个完整的威胁模型。我在做这块的时候习惯把嵌入式设备的威胁来源分成四类第一类是远程网络攻击比如弱口令爆破、未授权访问、漏洞利用这类威胁和服务器类似第二类是本地提权攻击攻击者通过 Web 漏洞或者应用层漏洞先拿到一个普通用户权限再通过内核漏洞、配置缺陷、SUID 文件等方式提权到 root第三类是物理攻击包括串口登录、JTAG 调试接口、Live 启动、拆片读 Flash第四类是供应链攻击固件在编译、打包、分发过程中被植入后门。这个第 17 讲的核心思路就是针对这四类威胁分别部署防御层最小化裁剪用来缩小攻击面权限硬化用来提高提权门槛日志审计用来解决“出了事不知道”的问题轻量防火墙用来做网络边界管控。四者不是孤立的功能叠加而是层层递进的关系——裁剪减少了可以被利用的组件数量权限硬化让即使被利用也难以扩大战果日志审计保证你能发现入侵痕迹并追溯攻击路径防火墙则在最外层把大部分自动化扫描直接挡掉。这套方案适合谁呢适合那些已经开始做嵌入式 Linux 产品、但是安全加固还停留在“改个默认密码”阶段的人。不管你是做路由器、工业网关、边缘计算盒子还是智能终端这套思路都可以直接套用。当然如果你的产品拿到了严格的安全合规需求比如等保、PCI、车规级的 EVITA 等等那这讲的内容可以作为地基在上面再叠加对应的合规要求。2. 系统最小化裁剪从源头缩小攻击面2.1 内核裁剪关掉那些你根本用不到的功能内核裁剪是最容易出效果、也最容易翻车的一步。嵌入式设备功能相对固定内核里大部分模块其实根本不会被用到。我见过不少团队图省事直接拿厂商 BSP 的内核配置来用一个 4MB 的 Flash 里塞了一个功能齐全的发行版内核里面支持十几种文件系统、几十种网络协议、各种多媒体驱动一半以上永远跑不起来。这些用不到的模块不只是浪费存储空间更是实打实的安全风险。内核里每一个协议栈、每一个驱动、每一个文件系统实现都可能是潜在的漏洞入口。举个现实的例子如果你裁剪掉了CONFIG_BLK_DEV_LOOP那攻击者就没法轻易挂载一个恶意镜像文件如果你关掉了CONFIG_BINFMT_MISC很多基于 magic 值的提权利用就没法落地。攻击面就是通过这样一个个看似不起眼的开关缩小下去的。我这边裁剪内核的基本步骤如下先用make menuconfig基于产品实际用到的硬件接口和功能列表把用不到的驱动全部关掉从设备的 BSP 默认配置开始逐步“做减法”。网络协议这一块要特别留意除了 TCP/IP 和必要的协议之外像 DCCP、SCTP、RDS、TIP 这类协议该关就关。文件系统保留实际用的那一种或两种就够了CONFIG_FUSE、CONFIG_VFAT这些如果不涉及外部存储就别开。还有一个容易被忽略的CONFIG_KEXEC、CONFIG_HIBERNATION、CONFIG_DEVMEM、CONFIG_DEVKMEM这些属于典型的危险开关能关则关。内核模块加载功能如果产品确实不需要动态加载驱动建议直接关掉CONFIG_MODULES彻底断了注入内核模块这条路。如果因为硬件初始化必须要保留模块加载能力那就用内核模块签名机制只信任带合法签名的模块。裁剪完成后用size命令确认一下 vmlinux 的体积再用产品实际要跑的应用做一轮完整的功能回归不要只测启动。裁剪内核最怕的就是“启动看起来正常某路 SPI 设备初始化到一半挂了”这类问题。2.2 RootFS 裁剪BusyBox、musl 与依赖清理用户空间裁剪的核心工具基本绕不开 BusyBox。BusyBox 一个二进制就集成了两百多个常用命令对嵌入式设备来说非常实用。但要注意的是BusyBox 里的每个 applet 也不是越多越好。编译 BusyBox 的时候make menuconfig里同样可以逐个勾选需要保留的命令。一个最小化的系统保留sh、mount、ifconfig、ping、cat、ls、cp、mv、rm、ps、kill、syslogd、logrotate如果集成的话这些就基本够了。像telnetd、ftpd、httpd这类网络服务如果产品没用上就千万别勾勾上等于白送攻击者一个入口。C 库的选择对体积影响也很明显。glibc 功能全但体积大依赖多musl 在体积上优势明显静态链接时更突出。如果你的应用不依赖 glibc 特有的 GNU 扩展迁移到 musl 是一个很值得考虑的选项。我实测过一个基于 ARM Cortex-A7 的项目换到 musl 之后整个 rootfs 的压缩镜像从 8MB 降到了 5MB 左右启动时内存占用也少了约 15%。当然迁移的成本在于一些动态库的 ABI 差异和个别 API 行为差异但总体来说性价比很高。还有一个必须做的动作所有不需要的 setuid/setgid 文件统统处理掉。BusyBox 默认编译如果不带单独的 applet那su、mount这些需要特权的操作要怎么实现常见做法是启用 BusyBox 的 setuid 机制给busybox二进制本身加上 setuid root由它内部根据调用的 applet 决定是否降权。但这个机制也是双刃剑——一旦 BusyBox 的某个 applet 有漏洞setuid root 的 BusyBox 就成了提权放大器。所以在产品最终形态里如果不需要普通用户执行 mount 之类的操作建议把 BusyBox 的CONFIG_FEATURE_SUID关掉让所有命令都以普通权限运行。实在需要特权操作的场景用单独的、去掉 setuid 位的小工具来实现别让整个 BusyBox 都带 setuid 位。2.3 裁剪时的“功能与安全”平衡决策讲到这里必须提醒一句最小化裁剪不是砍得越狠越好而是要在“够用”的基础上再砍一刀。砍得太狠的后果是——你连排查问题的工具都没了设备出问题只能拆机接串口而产品经理还在一旁催着“远程解决一下”。我说一个比较实用的平衡思路把系统分区和运行分区分开。只读的系统分区放内核和最精简的 BusyBox 工具链保证基础启动和网络连通可写的 overlay 分区放需要更新的应用和临时数据。这样即使应用区被写坏系统区还能启动恢复机制可以正常工作。不要把整个 rootfs 都做成可写的那是给攻击者改你系统文件的便利条件。3. 权限硬化把攻击者挡在 root 之外3.1 用户与权限体系设计删除、隔离、最小化嵌入式 Linux 默认跑起来是 root 用户这是很多设备出厂时最明显的问题。产品在研发阶段用 root 图省事可以理解但正式发布的固件里必须建立一套最小化的用户权限体系。具体做法是删除不需要的系统账户比如games、news、uucp这些发行版默认创建的账户在嵌入式环境里完全没有存在的意义。正常需要保留的大概就是root、daemon、bin、sys、nobody以及运行特定服务时创建的专用账户。如果你的产品里跑了一个 Web 服务、一个 MQTT 客户端、一个日志收集进程那就分别创建www、mqtt、logd这样的独立账户每个账户只赋予自己业务目录的读写权限绝不互相交叉。进程以最小权限运行即使被攻破也只是拿到这个账户的权限而不是整个系统。用户密码策略也不能忽视。嵌入式设备的 root 密码如果再使用出厂默认值就是给攻击者送分。更合理的方式是首次开机强制修改密码或者干脆不设密码只允许通过证书密钥登录适用于 SSH 管理场景同时把串口登录限制在 uboot 阶段内核启动之后串口终端尽快关闭或者也要求认证。如果产品没有本地运维需求串口直接关闭也可以。3.2 文件系统挂载选项noexec、nosuid、nodev 与只读挂载文件系统挂载选项的安全价值我怎么说都不为过。这是性价比最高的权限硬化手段之一几乎没有成本却能让一大堆攻击手法直接失效。关键挂载选项有这么几个noexec禁止在该文件系统上执行任何可执行文件。比如/tmp、/var、/home这些目录业务上不应该有可执行文件存在的需求统统加上noexec。这样攻击者即使通过漏洞把恶意二进制写进了 /tmp也无法直接执行。nosuid忽略该文件系统上的 setuid/setgid 位。再配合上面说的 BusyBox setuid 机制能很大程度上杜绝提权。nodev忽略该文件系统上的设备文件。攻击者在可写目录里创建一个/dev/sda1之类的设备节点再直接访问底层存储这个小技巧在一些场景下能用来绕过访问控制加上nodev就能堵住。ro只读挂载。系统分区、内核分区、只读 rootfs 都建议直接只读挂载需要改配置的少量文件单独用 bind mount 到可写分区上。在一个实际项目里我拿到一个新的 BSP 之后第一件事就是改/etc/fstab把能加安全选项的分区全加上。我一个做物联网网关的朋友有一次设备被入侵了攻击者留下的后门脚本就是放在/tmp下执行的。当时/tmp没有加noexec导致攻击者轻松拿到了稳定的执行权限。后来他在所有线上设备上加了这个挂载选项之后同类攻击就没有再得逞过。3.3 内核安全参数sysctl 与内核防护机制内核自身也有一些安全开关通过/etc/sysctl.conf配置。说几个嵌入式场景下比较值得开的kernel.kptr_restrict1可以限制非特权用户读取内核符号地址。攻击者利用内核漏洞时通常需要知道一些符号的地址这个参数会显著提高利用难度。kernel.dmesg_restrict1限制非特权用户查看内核日志防止攻击者从 dmesg 里收集内核版本、内存布局等敏感信息。net.ipv4.tcp_syncookies1开启 SYN cookies能在一定程度上缓解 SYN Flood 攻击。对暴露在公网的设备来说这算是基础防护。net.ipv4.conf.all.rp_filter1开启反向路径过滤能防一部分 IP 欺骗攻击。不过要注意如果网络环境里有不对称路由比如多 WAN 口负载均衡这个选项可能导致正常流量被丢弃开之前要测试线上业务。内核安全模块方面SELinux 和 AppArmor 是两个选项。SELinux 功能最强但配置复杂度高对嵌入式设备来说学习成本和维护成本都很高AppArmor 相对轻量配置基于路径更直观一些。如果你做的是安全等级要求比较高的产品比如金融终端或者军工相关设备那 SELinux 值得投入如果只是普通的 IoT 网关、工业盒子AppArmor 或者一些轻量级的策略约束就够了。但这不是说不用安全模块就完全不行——前面做的用户隔离和文件系统选项已经把大部分攻击路径都堵掉了安全模块是在这之上再加一道保险。4. 日志审计的完整落地出了事你得知道4.1 日志采集链路从应用到落盘的完整路径安全加固做到前面那几步之后剩下一个关键问题如果设备真的被入侵了你怎么知道怎么追踪攻击者做了什么这就是日志审计存在的意义。嵌入式设备的日志采集链路通常是这样的应用程序产生日志 - syslog 接口或者直接写文件- syslogd 进程接收 - 写入本地缓冲区或落盘 - 日志轮转工具按策略清理或归档 - 远程 Syslog 转发到日志服务器。BusyBox 内置的syslogd默认行为是把日志写到/var/log/messages日志量大的时候很快就把 Flash 写满了所以必须配合日志轮转。BusyBox 里自带了一个精简的logread和对应的循环缓冲机制如果你的系统可用内存允许建议把 syslogd 的输出配置成使用内存环形缓冲区然后定期将有用的日志刷到外部存储。这样既保证了日志采集能力又减少了对 Flash 的频繁写入。在 syslogd 的配置里有一个参数值得特别注意-b参数可以设置环形缓冲区的大小-s参数可以设置单条日志最大长度。做审计日志时日志内容宁可多一些冗余也不能丢关键信息建议把单条日志长度设置足够大免得日志被截断后关键信息丢失。4.2 日志内容抓什么who、what、when、where、how日志审计的价值取决于你采集了什么。安全日志至少要覆盖这几个维度谁who、做了什么what、什么时候when、在哪台设备上where、怎么做的how。具体来说需要重点采集的日志类型包括登录事件本地和控制台的每次成功与失败登录失败登录尤其要记录这是暴力破解的直接证据。提权操作任何 su、sudo 操作都要记录。如果系统里没有正常的 sudo 需求那就更简单——如果日志里出现了 sudo 记录直接判定异常。用户和组变更创建新用户、修改 UID、添加用户到 sudo 组这些都是攻击者常做的事一步步记录下来。服务启停网络服务、定时任务的变化特别是新增了监听端口或者新增了 crontab 条目。内核关键事件内核 panic、模块加载、错误消息。系统重启记录攻击者可能通过重启来激活某些恶意配置比如修改了启动脚本。4.3 日志轮转与远程转发防篡改的最后一公里日志最大的问题是攻击者拿到 root 权限之后第一件事往往就是清理日志。所以日志审计不能只落在本地远程日志服务器是必须的。BusyBox 的 syslogd 支持通过网络把日志转发到远程 Syslog 服务器配置方法是在启动参数里加上-R 日志服务器IP:端口。但这里有个坑默认情况下的 Syslog 协议是明文 UDP 传输攻击者如果控制了局域网内的任何一台设备可以抓包看到所有日志内容甚至伪造日志包干扰审计。如果安全等级要求高建议走带加密的日志通道。标准的 syslog 协议族里有 TLS 加密方案比如 syslog-ng 或 rsyslog 都支持tls嵌入式场景如果不想引入太大的依赖也可以用轻量级的方案比如用一个简单的脚本定期把日志通过 SSH 通道传输到日志服务器。日志轮转配置方面logrotate是标准工具。嵌入式系统里一般用 BusyBox 自带的logrotateapplet。我通常的配置策略是日志按天轮转保留最近 7 天的本地日志超过 7 天的自动清理。如果产品对审计要求更高可以保留更长时间或者直接全部远程存储。日志轮转本身也要注意轮转脚本如果写得不对可能会产生新的安全风险——比如变量没加引号导致路径注入、轮转后的日志文件权限过宽导致其他用户可读等。写完轮转配置之后建议实际跑一轮确认没有问题再发布。5. 轻量防火墙实战iptables 规则与性能取舍5.1 先搞清楚你的设备需不需要防火墙不是所有嵌入式设备都需要防火墙。如果设备部署在可信内网并且没有任何对外监听的端口那防火墙的实际防御价值其实不大反而会增加 CPU 开销和规则维护成本。但如果是下面这几种场景防火墙就是必需品设备有外部网络访问能力比如 4G/5G 上网并且监听了某些服务端口设备是网关类产品需要做端口转发和访问控制设备会被放置在不同客户的网络环境里你无法保证客户的内网足够安全产品需要满足某些安全合规要求比如等保 2.0 的边界防护要求判断标准其实很简单设备上有没有对外监听的 TCP/UDP 端口如果有那就要用防火墙明确“谁能访问这些端口”。如果设备完全是被动连接出去没有监听任何端口那防火墙的作用相对有限但依然可以通过 egress 限制来防止设备被当作跳板去攻击其他主机。5.2 最小规则集默认 DROP 策略与白名单思维我见过很多嵌入式开发人员对防火墙的态度是“配一下 iptables 规则绑一下端口就完事了”结果规则链用的是默认 ACCEPT 策略只加了少量 DROP 规则。这个思路本质上还是“黑名单”思维——你只能防御你已知的攻击未知攻击直接绕过去了。正确的做法是默认 DROP。INPUT 链默认策略设置为 DROP然后逐条放开必须的入站流量OUTPUT 链默认策略设置为 DROP然后放开必须的出站流量。这个配置逻辑上很简单但实践中很多人的反应是“这样我的设备是不是什么都干不了了”——没错而且这恰恰是目标。你需要在本地把设备的网络通信行为完整梳理一遍列出一份“必须允许”的清单允许 DHCP 请求出站、允许 DNS 查询出站、允许 NTP 时间同步出站、允许 MQTT 到指定服务器出站、允许 SSH 从管理网段入站然后把这清单翻译成防火墙规则。这里有一个典型场景设备需要从远程服务器下载升级包那就要允许到升级服务器的 443 端口出站设备必须响应某个网段的 ping 请求那就单独允许 ICMP echo-request 入站。注意别为了省事把整段 ICMP 都放开ICMP 重定向、时间戳请求这些类型都有被利用的可能。一个完整的 iptables 最小规则集大概长这样# 清除现有规则 iptables -F iptables -X iptables -Z # 默认策略 iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT DROP # 允许回环接口 iptables -A INPUT -i lo -j ACCEPT iptables -A OUTPUT -o lo -j ACCEPT # 允许已建立的连接及其相关流量 iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT # 允许 DHCP 出站 iptables -A OUTPUT -p udp --dport 67:68 -j ACCEPT # 允许 DNS 出站 iptables -A OUTPUT -p udp --dport 53 -j ACCEPT iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT # 允许 NTP 出站 iptables -A OUTPUT -p udp --dport 123 -j ACCEPT # 允许 MQTT 到指定云平台替换为实际地址 iptables -A OUTPUT -d 203.0.113.10 -p tcp --dport 8883 -j ACCEPT # 允许 SSH 从管理网段入站 iptables -A INPUT -s 192.168.10.0/24 -p tcp --dport 22 -j ACCEPT # 允许 ICMP echo-request 入站可选 iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT这个规则集放在设备上能挡住绝大部分的端口扫描和未授权访问尝试。5.3 iptables 还是 nftables嵌入式场景怎么选现在新版本的内核里 nftables 已经在逐渐替代 iptables。但对嵌入式场景来说选哪个其实首先要看内核版本和既有代码的维护成本。如果 BSP 里已经集成了 iptables且你的规则都是 iptables 语法写的那就继续用 iptables没必要为了“新”而折腾。nftables 的优势主要是规则集更紧凑、性能更好、语法上更统一但在低性能的 ARM 设备上这套优势的感知并不明显。还有一个更轻量的思路如果设备性能极其有限比如单核 Cortex-M 级别跑 Linux跑完整的 iptables/netfilter 栈都可能成为负担。这种情况下可以考虑在应用层做访问控制比如在 TCP 协议栈的 socket 层做 bind/connect 限制或者用更轻量的工具像busybox里自带的防火墙配置界面其实还是走 iptables。我个人建议是Cortex-A 级别以上的设备直接用 iptables/nftables性能完全够用再往下的 MCU 级别设备先考虑业务上的安全设计不要硬上完整防火墙。5.4 防火墙规则别忘了持久化很多嵌入式工程师在调试时手动敲了一堆 iptables 规则一切正常然后重启设备规则全没了。这并不是脚本错误而是 iptables 规则本质上是运行时的配置不会自动保存。你需要把最终的规则集保存下来并在系统启动时自动加载。常见的做法有两种一是使用iptables-save导出规则文件然后在/etc/init.d/下的启动脚本里用iptables-restore导入二是把规则直接写进一个 shell 脚本比如/etc/firewall.sh在网络服务启动之后调用。第二种做法的好处是规则脚本里可以写注释和条件判断方便适配不同产品形态。不管用哪种方式都要记住一个关键点防火墙规则的加载时机很重要。如果规则加载太早网络接口还没就绪规则可能因为找不到匹配的接口而报错加载太晚则会留下一个没有防火墙保护的窗口期。我一般建议在网络服务全部启动之前、接口配置完成之后加载。6. 全流程实践从零加固一台嵌入式 Linux 设备6.1 阶段一梳理业务与确定资产清单在前面几节讲完了各项技术细节之后我把一次完整的加固流程串起来。这个流程我称之为“先理后治”不管你是用 Buildroot、Yocto 还是传统的手工交叉编译环境大致路径都差不多。第一步是梳理业务设备上跑哪些服务监听哪些端口需要网络访问哪些外部服务SSH 远程管理是否需要串口调试在生产阶段还需不需要把这些问题列成一个表这是后续所有裁剪和规则配置的依据。这一步不能省略因为如果业务梳理不清楚后面的裁剪和防火墙配置就是空中楼阁。6.2 阶段二构建最小化系统并加固配置接下来按照第 2 节的方法做内核裁剪和 rootfs 裁剪。这里我建议用构建脚本把整个过程固化下来不要每次手工操作。Buildroot 是一个比较顺手的选择配置好后可以一键生成内核、rootfs、工具链并且可以重复构建。如果你用的是 Yocto那裁剪的思路也是类似的只是配置方式变成了 recipe 和 image feature。系统构建完成之后进入配置加固阶段修改/etc/inittab把不需要的 tty 终端关掉特别是ttyS0如果产品不需要本地登录直接注释掉修改/etc/fstab按第 3.2 节的方法给各个分区加上安全挂载选项修改/etc/sysctl.conf启用内核安全参数创建最小化的用户账户体系删除默认账户配置 BusyBox 的 syslogd 和 logrotate最后把防火墙规则写进启动脚本。6.3 阶段三安全功能验证与回归测试加固完成之后不要急着发布先做一轮验证功能验证所有业务功能是否正常特别是网络相关功能防火墙规则不能影响正常业务。权限验证普通用户能不能 sudo能不能直接读写系统分区能不能修改/etc/passwd安全验证从另一台机器扫描设备的所有端口确认只有预期的端口在监听尝试通过弱口令、默认口令、关闭的服务入口去连接确认全部失败。稳定性验证长时间运行至少 24 小时后检查内存泄漏、日志文件大小、Flash 剩余空间是否在预期范围内。恢复验证模拟一次固件异常升级、一次配置写坏确认设备的恢复机制可以正常把系统拉回来。其中端口扫描这一步我建议在每次加固之后都跑一遍用nmap从攻击者的视角看一下设备暴露了哪些端口。很多时候你以为关闭了某个服务实际还在监听这种问题只有扫描才能发现。6.4 阶段四文档化与持续维护安全加固不是一个一次性的动作而是一个持续的过程。固件每次升级之后攻击面都可能发生变化所以你需要把加固的每一个决策记录下来——为什么裁剪这个驱动为什么放开这个端口为什么给这个分区加只读生成一份加固基线文档后续每次版本迭代都拿基线文档来对比。我自己的习惯是把加固过程做成一个 checklist每个版本发版前跑一遍 checklist。这个 checklist 大概包括几十项内容是否还有默认密码是否还有没用的监听端口/tmp 是否加上了 noexec内核模块加载是否关闭日志是否正常轮转和转发这比每次发版前临时想“我这次改了哪些东西会不会引入安全问题”要可靠得多。7. 常见问题与排查技巧实录7.1 加固后业务异常防火墙静默丢包这是最常遇到的问题。现象是设备加固之后某项业务不通了但完全找不到原因。比如设备上的应用原本可以正常连接云端加完防火墙规则之后就一直连不上。问题往往出在 OUTPUT 链的默认 DROP 策略上——设备的业务进程要访问某个端口而你忘了在规则里放开。排查方法其实不复杂先看日志dmesg里开启 iptables 的日志记录加一条-j LOG规则这样被丢弃的包会打印在内核日志里一看就知道是谁访问什么端口被挡了。然后再对照业务梳理清单把遗漏的端口补上。需要注意的是LOG规则在生产环境不要长时间开启会产生大量内核日志影响性能定位完成后就删掉。7.2 日志文件把 Flash 写爆了嵌入式设备的 Flash 写入次数和容量都非常有限。有些产品跑了一阵之后内存卡或者 Flash 被日志写满了导致系统无法正常工作。这个问题通常是两个原因造成的一是日志采集过于激进应用把调试日志输出到了正式环境二是日志轮转策略没有生效。排查时先检查/var/log下各个文件的大小再用du -sh /var/log/*看一下分布确认哪类日志最多。优化手段包括把日志级别从 DEBUG 改为 INFO 或者 WARNING缩短日志轮转周期限制单条日志的最大长度最有效的还是加上前面说的-b环形缓冲区限制把日志放在内存里。日志本来就只是用于审计和排障不需要永久保留特别是调试日志。7.3 文件系统变成只读配置无法持久化有些产品把 rootfs 做成了只读结果应用运行时发现配置文件写不进去进程直接启动失败。这个问题的根源在于只读和可写分区没有做好规划。解决思路是把需要持久化的数据目录单独放在一个可写的 overlay 分区比如/data、/var/lib在应用启动时通过 bind mount 或者符号链接把可写目录映射到系统的预期路径下。比如/etc下的业务配置文件可以先复制一份放到/data/etc/再用 bind mount 把/etc下的某个具体文件挂载成/data/etc/下的文件这样既能保持系统分区只读又能正常持久化配置。7.4 加固后的性能下降netfilter 与日志开销有些工程师担心防火墙规则会影响设备性能。实测下来在 Cortex-A7 级别的设备上几十条 iptables 规则对网络吞吐的影响其实很小可以忽略不计。真正影响性能的往往不是规则匹配而是日志记录。如果在规则里加了很多-j LOG且系统开启了内核打印那大量日志输出会拖慢系统。另一个容易被忽略的性能问题是连接跟踪conntrack表溢出。如果设备处于高并发网络环境中conntrack 表项满了之后新连接会被丢弃表现为网络时断时续或者特定业务不可达。解决方法是调大 conntrack 表的最大值或者对某些场景禁用连接跟踪。我曾经在一台设备上遇到过这种事情端口扫描工具一跑设备直接卡死。后来发现是攻击者发了大量伪造的 TCP SYN 包把 conntrack 表灌满了。这个时候net.ipv4.tcp_max_syn_backlog和net.netfilter.nf_conntrack_max就是核心参数调优之后这个现象基本消失。8. 第 16 讲课后思考题解析绕过“信息差”的实操问答8.1 思考题一为什么最小化裁剪能提高安全性裁剪到什么程度合适最小化裁剪提高安全性的核心原理是“攻击面缩小”。一个系统里存在的代码越多、功能越复杂潜在漏洞数量就越多攻击者可以利用的路径也就越多。裁剪掉一个用不到的协议栈等于直接关闭了一整条攻击路径比单纯靠打补丁更可靠——因为你不再需要为一个永远不会使用的功能维护安全性。裁剪到什么程度合适这个没有统一答案但有个基本判断标准删掉一个组件后产品的所有功能依然能正常通过测试如果删掉之后某个功能挂了那么这个组件就是需要的。实际操作中“保留最小集加白名单”比“先完整集再逐个删”更省事、更安全因为你不需要每次对删掉的东西做风险评估你只需要对保留的少量模块做重点维护。但我们前面也说了别把整个 rootfs 砍到连排查问题的工具都没有了那属于“为了安全而牺牲了可维护性”在产品实践里得不偿失。8.2 思考题二SELinux 和 AppArmor 在嵌入式场景下应该选哪个这个问题在社区里争议很大。我的看法是如果你能接受 CAP 策略的编写成本SELinux 的安全强度确实更高因为它可以对进程的每一个文件访问、网络访问、系统调用进行细粒度控制但问题在于它的学习曲线非常陡峭策略编写和调试成本很高在嵌入式设备上维护一套完整的 SELinux 策略工作量不亚于维护一个 BSP。AppArmor 的优势是配置基于路径写起来直观普通工程师半天就能上手。对于大多数嵌入式产品来说AppArmor 提供的隔离能力已经够用了。如果产品说明确要求了 SELinux那就直接选 SELinux如果没有先上 AppArmor等产品安全团队成长起来后再考虑更重的方案。另外有个补充思路很多嵌入式产品既没上 SELinux 也没上 AppArmor光靠用户隔离和文件系统权限就已经过了等保检查。这不是说安全模块没用而是说安全加固要从投入产出比的角度考虑先把便宜好用的手段用足再考虑重型武器。8.3 思考题三日志审计最重要的是什么是不是日志越多越好日志审计的目标不是收集尽量多的日志而是让安全团队在事件发生后可以回答“发生了什么、为什么发生、影响范围多大”。所以日志审计的第一原则是“关键事件不漏”而不是“所有事件都收”。如果日志量大到无法审查和存储真正关键的事件反而会被淹没。嵌入式设备的日志审计要把握好这几点采集登录、提权、用户变更、服务变更、内核异常这几类关键安全事件日志要尽量远程存储防止本地销毁日志要定期人工或自动巡检不能只存不看。我还见过一些项目日志服务器收到了告警邮件但没人去看入侵发生了半年才发现。再完善的日志审计体系没有对应的响应机制价值都会大打折扣。8.4 思考题四硬件加密、安全启动与软件安全加固的关系有读者问产品已经做了安全启动Secure Boot和硬件加密是不是软件安全加固就不需要了这两者不是替代关系而是互补关系。安全启动解决的是设备固件的完整性和真实性——防止攻击者把山寨固件或者被篡改的固件刷进设备里。硬件加密解决的是存储数据被物理拆解后无法被直接读取——防止攻击者把 Flash 焊下来读到密钥和敏感数据。而软件安全加固解决的是系统运行期的安全问题——一个攻击者已经通过某个漏洞拿到了一个普通用户权限他能不能提权到 root能不能在系统里留下持久化后门能不能通过网络横向移动这些问题安全启动和硬件加密都管不了。一个完整的设备安全体系应该是安全启动做可信根、硬件加密做数据保护、最小化和权限硬化做攻击面收敛、日志审计做事件追溯、防火墙做网络边界。每一层解决一段问题组合起来才是真正可靠的纵深防御。9. 关于加固工具的选型建议在写这一部分之前先说一个很现实的问题嵌入式 Linux 安全加固的工具链远没有服务器生态那么丰富。很多好用的工具比如 OSSEC、Wazuh、Falco在嵌入式设备上根本跑不起来因为对内存、CPU、存储的要求太高了。所以嵌入式安全加固的逻辑不是“堆工具”而是“用对机制”。Buildroot 和 Yocto 是构建系统层面的两个主流选择。Buildroot 的优势是简单直接、配置项清晰适合产品形态相对固定的团队Yocto 的优势是灵活性强、组件版本可控性高、有完整的 layer 机制适合需要长期演进、多产品线复用的团队。如果你刚入门建议先用 Buildroot 把加固流程跑通理解了整条链路之后再迁移到 Yocto。内核层面的安全检查可以借助checksec脚本扫描内核和二进制的安全属性比如 NX、PIE、Stack Canary、RELRO 是否开启。用户空间的二进制在编译时建议加上这些安全编译选项大部分交叉编译工具链默认可能没有开全需要你主动添加编译参数。这个检查要放到 CI 流程里每次构建之后自动扫描一遍防止某个新加入的组件把关掉了。文件系统层面的辅助工具有busybox本身和一些静态分析脚本。检查 rootfs 里有没有带 setuid 位的文件可以用这条命令find /path/to/rootfs -perm -4000 -type f检查有没有对外开放的监听端口可以用ss -lntup这些基础命令在加固验证阶段非常实用建议整理成一个检查脚本放到发布流程里。10. 最后再分享一点个人经验嵌入式 Linux 安全加固这件事做完一轮之后最深的感受是真正的难点不在技术而在产品节奏的对抗。安全加固每一项动作都牵动着功能可用性、性能开销、开发排期、现场运维效率你加固得越狠运维起来就越不自由——调试工具被裁了、远程操作被限制了、日志要定期清理了这些都会带来额外的维护成本。拿我们自己做的一个边缘网关产品来说。第一版固件出厂时rootfs 可写、root 空密码、telnet 还开着那时候的功能迭代确实很快现场出了任何问题都能第一时间远程登进去查。后来有一批设备因为弱口令被入侵被当成肉鸡去扫描外网的其他主机客户的运维团队发现之后直接下了禁止接入公网的通知整个项目停了一周。从那以后我们彻底改变了思路每一版固件发布前都跑安全基线检查telnet 关掉、root 密码随机化、分区只读、防火墙规则固定成模板。功能迭代的效率确实受了点影响但再也没有出现过因为安全问题导致的项目停滞。所以我建议每一个做嵌入式 Linux 产品的团队安全加固不是等产品稳定了再考虑而是从第一个可启动的固件版本开始就要把基本的加固动作加上。哪怕是先关掉 telnet、改掉默认密码、把 rootfs 挂成只读这三件事成本极低却能在产品上线后拦住绝大多数脚本小子的试探。后面再逐步按着最小化裁剪、权限硬化、日志审计、防火墙的顺序一层一层加厚防御。安全是一个持续演进的过程没有一招制敌的银弹但只要把每层防护都做到位你的设备就会比绝大多数同类产品硬得多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →