嵌入式Linux系统级安全加固实战:裁剪、权限、日志与防火墙

发布时间:2026/9/8 12:37:14
嵌入式Linux系统级安全加固实战:裁剪、权限、日志与防火墙 设备被人种了挖矿程序CPU占用100%板子烫得能煎鸡蛋——做嵌入式Linux系统级安全加固实战这一年多我见过太多这样的现场了。现场工程师一脸无辜地问我这设备不是一直在内网吗怎么会被搞这类事故这几年我见过不止一次。上个月还有个做电力监测设备的朋友吐槽他们家的产品出厂时为了方便调试顺手开了Telnetroot密码是admin结果设备上线不到一周就被人当成肉鸡出口带宽被塞满运营商直接打电话来质询。问题出在哪不是某个第三方库的CVE有多厉害而是整个系统从裁剪、权限到网络边界长期以来都在“裸奔”。这期第17讲我们就围绕嵌入式Linux系统级安全加固做一次完整的实战梳理主线是四件事最小化裁剪、权限硬化、日志审计、轻量防火墙。这四件事不是四个孤立的动作而是环环相扣的一套纵深防御体系。我会从原理到命令、从配置到验证全部过一遍最后把第16讲的思考题完整解析放出来。正在做嵌入式Linux产品化、做BSP、或者长期被设备安全问题折腾的工程师这期内容可以直接照着操作。1. 先搞清楚嵌入式Linux的安全威胁到底长什么样1.1 别再拿“内网很安全”安慰自己了很多工程师对嵌入式设备安全的认识还停留在“设备在内网外面人进不来”这个阶段。实际情况是内网横向渗透早就成了最常见的攻击路径。攻击者可能通过一个办公室WiFi、一个USB口、一个暴露的调试串口或者一台已经被攻破的办公电脑进入内网然后扫描整个网段寻找开了Telnet、使用默认口令的嵌入式设备。设备不直接暴露在公网不代表它不会成为跳板。很多物联网僵尸网络就是这么起来的一批弱口令摄像头、弱口令路由器被扫描到之后批量控制。换句话说嵌入式设备的安全问题不是“外面的人能不能连上”而是“一旦有人能连上系统能不能扛得住”。之前有团队做过测试把一个开着22端口、root密码是出厂默认值的开发板直接插到办公网没做任何宣传不到48小时就有人在上面尝试登录用的就是最常见的弱口令字典。这个测试我也复现过结果差不多。所以第一件事就是纠正心态内网不等于安全默认配置绝对不安全。1.2 设备安全风险盘点一张表看清常见弱项我在实际项目里做过比较多的安全风险评估嵌入式设备最常见的弱项通常集中在下面几类风险类别典型表现后果默认口令与空口令root密码为admin/123456或直接空密码设备可被直接登录控制危险服务开启Telnet、FTP、调试用HTTP服务常开明文口令被嗅探、服务被利用调试接口无人看管串口无需登录、JTAG未封、U-Boot无密码本地人员可任意读写固件系统过度膨胀内核编译了一堆用不到的协议和驱动漏洞曝光面大、固件体积大权限过大业务进程全部是root运行单个进程被利用即获得系统最高权限无审计日志设备没有日志或日志不做轮转出事了无从排查无法溯源网络无边界所有端口对所有IP开放攻击者扫描到就能直接访问这张表做出来之后很多朋友才意识到自己手上的产品几乎中了一半以上。不用慌后面几章就是逐项解决这些问题。表里每一项单独看都不难处理难的是把它们系统性地纳入产品化流程而不是等项目出事之后再补窟窿。1.3 加固思路先收敛攻击面再做纵深防御安全加固最容易犯的错误是一上来就去调防火墙参数、装各种安全软件结果系统变得很卡业务还跑不起来。我的建议是先按“收敛-控制-观测-边界”这个顺序来做第一步收效面通过最小化裁剪把系统里用不到的服务、驱动、代码、组件全部拿掉。攻击者连口都找不到后面的事谈都不用谈。第二步控制权限在系统层面把进程权限、文件权限、内核参数收紧。就算攻击者找到入口也无法立刻变成root无法往系统目录里写东西。第三步观测记录把关键操作、登录行为、网络连接都记录下来。攻击发生前有异常攻击发生后有痕迹。第四步边界防守用轻量防火墙把网络入口收成白名单业务不需要的端口一律不对外。这个顺序也是本讲的目录顺序。每一步都建立在前面一步的基础上裁剪做得越干净权限和防火墙要管的范围就越小权限和日志做得到位即便防火墙被绕过系统也不至于被一击致命。把这句话记在心里后面所有操作都是为了实现这个递进关系。2. 最小化裁剪从源头把攻击面削掉一半2.1 内核安全裁剪哪些CONFIG必须看一眼最小化裁剪不是一句口号而是实打实的配置工作。先看内核。拿Linux 5.10以上版本来说执行make menuconfig之后安全相关的选项主要分两类一类是“用不到的功能就别编”另一类是“该开的内核加固特性必须开”。先说用不到就别编的。很多开发板默认内核配置是全功能型的下面这些选项在产品里大概率用不上建议关掉CONFIG_PACKET这个是packet sockettcpdump抓包依赖它。生产环境如果不需要在线抓包关掉能少一个非常容易被滥用的系统调用入口。需要排查问题时再临时开前提是你能重编内核。CONFIG_NETFILTER_XT_TARGET_TCPMSS、一些冷门的IPv6过渡协议比如SIT、GRE隧道业务不需要的时候就关。CONFIG_WIRELESS、CONFIG_CFG80211纯有线设备就没必要编译无线协议栈无线驱动和协议栈是安全漏洞高发区。CONFIG_BLK_DEV_LOOP、CONFIG_NFS_FS、CONFIG_CIFS这类网络文件系统生产环境一般用不到关掉。NFS调试很方便但上了生产环境就是风险。再说该开的内核加固特性。这些选项对性能影响很小但能明显提高利用难度CONFIG_SECURITYy CONFIG_SECURITY_YAMAy CONFIG_HARDENED_USERCOPYy CONFIG_STRICT_KERNEL_RWXy CONFIG_STRICT_MODULE_RWXy CONFIG_RANDOMIZE_BASEyCONFIG_RANDOMIZE_BASE就是内核地址空间随机化KASLR前提是bootloader侧要配合传入随机种子否则每次启动的地址偏移固定效果大打折扣。CONFIG_HARDENED_USERCOPY能在内核拷贝用户数据时做边界检查很多内核溢出利用会被它直接拦掉。CONFIG_STRICT_KERNEL_RWX把内核内存段设为只读防止运行时修改内核代码段对注入类攻击是硬性拦截。这些配置在Buildroot的linux内核配置界面里都能找到花十分钟核对一遍受益是整个产品生命周期的。2.2 文件系统瘦身Buildroot/Yocto里该删什么内核裁完接着看根文件系统。Buildroot和Yocto在这块儿的操作思路一样把业务不需要的包从配置里摘掉然后用编译选项把符号信息去掉。Buildroot里我习惯的做法是先把最终的rootfs解包出来看一眼统计一下哪些目录最占空间du -sh /path/to/rootfs/* | sort -rh | head -20挨个看哪些软件包是业务绝对用不到的。比较常见的“多余大户”gcc、make、gdb、strace、tcpdump、iperf、各种shell如果一个sh够用就不装bash、vimvi都不一定要可以只用busybox里的小编辑器或者干脆不要。这些调试工具在开发板上很有用但它们本身就是攻击者最喜欢的目标——一个带gdb的嵌入式系统被攻进去之后攻击者连编译工具都不用自带。我把这条写在团队规范里开发板可以刷完整rootfs产线和正式发布固件必须用裁剪后的rootfs两套配置分开维护。删完包之后再统一做strip。Buildroot如果没有全局strip可以在make menuconfig的Build options里打开“strip command for binaries”Yocto默认会在打包阶段strip。交叉编译时也可以自己在makefile里加$(CROSS_COMPILE)strip --strip-unneeded $(TARGET_BIN)这一套做下来一个原本带大量调试工具的rootfs通常能瘦掉30%-50%而且很多潜在风险是跟着这些工具一起消失的。要注意的是strip之前最好先确认功能没受影响尤其是动态链接的程序strip做了之后依赖关系还得保持正常。2.3 驱动与内核模块不加载的东西才是最安全的驱动和内核模块的处理很多人容易忽略。编译进内核的代码即使没有对应的硬件也会增加攻击面。最理想的状态是除了平台必须的基础驱动串口、定时器、内存、中断控制器其他驱动全部做成模块并且只保留业务真正用到的模块。模块文件放在/lib/modules/$(uname -r)/下面生效的模块可以用lsmod查。生产环境的做法一般是把整个模块目录清掉重新放一个最小清单。假设你的设备只需要网络和USB存储mkdir -p /lib/modules/$(uname -r)/extra cp usb-storage.ko /lib/modules/$(uname -r)/extra/然后在启动脚本里用modprobe加载需要的模块其余文件不拷入rootfs。对于内核里带自动加载机制的模块子系统可以在/etc/modprobe.d/blacklist.conf里加黑名单防止某些冷门协议比如DCCP、SCTP被自动加载blacklist dccp blacklist sctp blacklist rds blacklist tipc裁剪完之后一定要对着业务功能清单做一遍回归网络通不通、串口正常不正常、外设能不能枚举、休眠唤醒是否正常。这个回归测试的过程我在后面第7章的验证清单里还会再展开。这里额外提醒一句很多团队在裁剪时只关注了“体积减小”没有记录“减掉的到底是什么”等现场缺驱动要重新加回来时翻遍代码库都找不到之前裁剪记录非常痛苦。所以我建议在内核配置目录下提交一个CUTLIST.txt把每次裁剪的选项、原因、操作人、日期都记下来成本很低回报很高。3. 权限硬化攻击者进了门也上不了楼3.1 用户体系整改先让root退出日常业务最小化裁剪解决的是“入口问题”权限硬化解决的是“进入之后能干什么”。嵌入式系统里最常见的问题是业务进程全部拿root权限在跑。一旦某个网络服务被攻击者利用直接就是最高权限后续所有防御都形同虚设。正确做法是建立一套最小特权用户体系。先规划用户。假设设备上运行三个业务网络服务、数据采集、OTA升级。那就建三个独立用户addgroup -S svc-net adduser -S svc-net -G svc-net addgroup -S svc-data adduser -S svc-data -G svc-data addgroup -S svc-ota adduser -S svc-ota -G svc-ota业务进程在init脚本里用start-stop-daemon的--chuid参数或者systemd服务文件里的User字段切换到对应用户。这是嵌入式环境最直接有效的降权手段。这里一个容易被忽略的细节是降权之后要检查业务进程运行时有没有写文件的需求写文件的目录属主和权限必须提前分好否则上线后业务起不来现场就会有人偷偷把服务改回root运行前功尽弃。然后处理登录入口。如果设备必须支持远程登录我建议只用SSH禁用root直接登录。Dropbear是嵌入式最常用的轻量SSH服务配置里加上PermitRootLogin no PasswordAuth no如果开了密钥登录可以更进一步把PasswordAuth关掉。本地调试需要root可以用带权限控制的su或者sudo绝不应该把root口令直接暴露给SSH登录。最后把根本用不到的账号全部锁定。嵌入式rootfs里经常有daemon、bin、sys这类历史遗留账号用passwd -l把它们的密码锁定passwd -l daemon passwd -l bin passwd -l sys/etc/shadow权限改成600/etc/passwd改成644。这些看起来是小事情但对整体安全性的提升非常关键。如果产品通过了等保或者客户安全审计这几点都是必检项。3.2 文件系统挂载与属性把漏洞利用的“落点”堵住权限硬化的第二战场是文件系统。攻击者拿到一个普通用户权限后接下来最想做的事就是往可执行目录里写文件、往临时目录里放payload、尝试挂载新的文件系统。这些行为可以通过挂载选项直接堵死。如果rootfs用squashfs或erofs这类只读文件系统系统分区天然只读这是最让人省心的状态。需要动态变化的数据放/var和/run用tmpfs挂载重启即丢。只读rootfs还有一个好处无论攻击者想篡改系统文件还是想植入后门重启一下设备就全部失效这也是很多网安产品做“合规基线”时特别看重的一点。不是所有产品都能改成只读rootfs那就至少在挂载参数上做文章。下面这行挂载/data分区的方式值得参考mount /dev/mmcblk0p3 /data -t ext4 -o defaults,nodev,nosuid,noexecnodev禁止这个分区上挂载设备文件nosuid禁用setuid位noexec禁止执行程序。noexec在有些场景下要小心如果业务本身需要从/data目录执行脚本或二进制就不能直接noexec可以退而求其次用nosuidnodev再配合只读权限把可写目录清干净。/tmp目录的挂载同样要带上nodev,nosuid。能不能加noexec要看编译器和运行时依赖有些动态加载器需要临时目录做映射盲目加noexec会把业务搞挂。这里我建议在做完整盘权限方案后列一个“业务路径与挂载选项对照表”每个挂载点是什么、为什么这么挂、业务哪些路径需要写写得清清楚楚避免现场拍脑袋改参数。3.3 内核安全特性capabilities、seccomp、Yama用户层面降权之后有些进程仍然需要某些特权操作。比如一个时间同步进程需要绑定1024以下的端口或者一个网络配置进程需要设置IP地址。过去的做法是直接给root权限更精确的做法是用capabilities只授予必要的特权。以非root用户启动一个需要绑定80端口的服务为例可以先给二进制文件授capsetcap cap_net_bind_serviceep /usr/bin/my-web-service这样服务进程启动后只拥有绑定低端口的能力不能读写其他进程内存不能加载内核模块不能改变系统时间。比把整个服务跑在root下安全得多。capabilities的粒度很细常见的还有cap_net_admin配置网络、cap_sys_time改时间、cap_kill发信号按需给就行。需要注意的是setcap之后文件在读取时会带安全标记某些老旧的启动方式会校验文件校验和导致授权后业务无法启动这时需要把授权动作纳入镜像构建流程而不是到现场再敲。seccomp是对系统调用的精细过滤。编译时开启prctl(PR_SET_NO_NEW_PRIVS, 1)之后用seccomp(2)系统调用加载BPF过滤器可以限定进程只能调用白名单里的系统调用。嵌入式上如果不想自己写BPF规则可以借现成工具生成白名单或者只在最核心的网络服务上启用。seccomp对大多数业务场景的干扰不大但对性能极其敏感的场景需要先做基准测试。Yama是相对轻量的LSM模块里面有个非常有用的功能限制ptrace。默认情况下一个进程可以ptrace同用户的另一个进程这对调试器是必要的但对设备来说意味着攻击者一旦拿下一个普通用户进程就能通过ptrace注入同用户的进程。开启CONFIG_SECURITY_YAMA之后设置kernel.yama.ptrace_scope 1把ptrace限制为只能追踪子进程能有效阻断这一类提权和注入路径。这个参数我在好几台设备上实测过对正常业务几乎没有感知但是对想要附加进程做调试的攻击者来说是一道非常硬的墙。3.4 sysctl参数十分钟把手脚都绑起来内核参数里还有一批“免费”的加固项全部改完不超过十分钟但效果立竿见影。我常用的生产基线如下# 禁止IP转发除非你的设备本身就是路由器 net.ipv4.ip_forward 0 # 禁止接受ICMP重定向、禁止路由源路由 net.ipv4.conf.all.accept_redirects 0 net.ipv4.conf.all.accept_source_route 0 net.ipv4.conf.all.secure_redirects 0 # 开启反向路径过滤防IP欺骗 net.ipv4.conf.all.rp_filter 1 # 限制dmesg查看内核日志防止泄露内核地址 kernel.dmesg_restrict 1 # 限制通过/proc/kallsyms查看内核符号 kernel.kptr_restrict 1 # 开启ASLR kernel.randomize_va_space 2这些参数在桌面发行版里一般写在/etc/sysctl.conf启动时自动加载。但嵌入式环境经常没有独立的sysctl服务需要自己在init脚本里手动执行sysctl -p /etc/sysctl.conf注意sysctl.conf里不能有空格错误写错了会导致后面参数全部不生效。加载完之后用sysctl -a逐个确认别偷懒。我在项目里就踩过这样的坑/etc/sysctl.conf里有一行末尾多个空格加载时直接报错后面几行网络参数全部没生效设备照样转发数据包要不是例行检查根本发现不了。所以我的习惯是sysctl执行完之后专门写一行校验脚本把关键参数挨个grep出来比对写成CI检查项。4. 日志审计设备被搞了不能只有板子知道4.1 嵌入式环境日志采集busybox syslogd够不够用权限硬化做得再好也不能保证百分之百不被攻破所以日志是必须做的事。嵌入式环境最常用的日志方案是busybox里的syslogd。busybox syslogd默认会把日志缓冲在内存里用logread查看适合没有Flash写入需求的场景。但纯内存缓冲有两个问题一是掉电日志丢失二是日志量大了之后会覆盖。所以有条件的产品建议把日志通过网络发送到远程日志服务器。先看syslogd支持哪些选项不同busybox版本编译特性不一样syslogd --help重点看有没有-R远程日志和-C内存缓冲大小。我的设备一般这样启动/sbin/syslogd -C 64 -R 192.168.1.100:514-C 64表示开64KB内存缓冲-R表示同时把日志转发到远程日志服务器的514端口。注意-R转发用的是UDP会丢包而且没有任何加密。日志内容在网络上裸奔本身也有泄露风险所以远程日志链路最好是独立网段或者配合防火墙只允许设备到日志服务器的方向通信。内核日志也要一起收很多设备崩溃或者被攻击时内核ring buffer里的信息是最有价值的。用klogdbusybox里通常集成在syslogd中把内核日志也送进syslogd然后在server端统一归档。我见过不少团队只收应用日志、不收内核日志结果设备被攻击后只看到业务进程挂了真正的攻击痕迹全在内核日志里但因为没采集事后什么都查不到非常可惜。4.2 日志轮转怎么在嵌入式上落地完整的日志体系必须考虑轮转否则一个8MB的Flash分区很快就会被日志写满。嵌入式环境一般没有logrotate最实用的方式是自己写一个cron脚本做定期rotate。比如日志目录是/var/log/messages可以用busybox crond跑这个脚本#!/bin/sh LOG/var/log/messages MAXSIZE512 if [ -f $LOG ] [ $(stat -c%s $LOG) -gt $((MAXSIZE*1024)) ]; then mv $LOG $LOG.1 kill -HUP $(pidof syslogd) 2/dev/null fi ls -1 $LOG.* 2/dev/null | sort | tail -n 4 | xargs rm -f脚本的逻辑是日志文件超过512KB就滚动一次最多保留最近3个历史文件。旋转之后要给syslogd发送SIGHUP信号让它重新打开日志文件否则消息会继续写到旧文件名的句柄上看起来像是日志“卡住”了。这个脚本放/etc/cron.daily或者直接用crond的最小间隔跑取决于你的crond支持到哪个周期。如果你用tmpfs挂载/var/log那日志掉电就丢轮转脚本的意义就不大这种情况下远程转发就成了主存储本地内存缓冲区只是临时缓存这个定位要先想清楚。4.3 远程日志让日志离开设备才安全本地日志有个天然缺陷攻击者拿到root之后第一件事通常就是清日志。所以日志安全的重点是“日志不能只存在被攻击的设备上”。远程日志的服务端搭建很简单Ubuntu上装rsyslog就能收。为了区分设备在rsyslog配置里按来源IP归档template(nameRemoteDevice typestring string/var/log/device/%fromhost-ip%/%$year%-%$month%-%$day%.log)然后启用UDP接收模块重启rsyslog。设备端把syslogd的-R指向这个服务器。上线之后任何一台设备的登录、网络、业务异常都会集中到服务器上排查效率一下子高很多。远程日志也需要考虑完整性日志链路如果走明文UDP中间节点可以篡改内容如果走独立网段或者管理VLAN风险会小很多。对安全要求极高的场景可以评估syslog-ng搭配TLS封装但嵌入式侧资源有限一般先用防火墙保证传输链路可控即可。这里还有个小技巧在服务端对每个设备的日志做哈希链校验每天生成一个完整性摘要万一被篡改一眼就能看出来成本极低。4.4 关键行为的审计埋点系统级日志到位之后应用层面的关键行为也要埋点。我一般会要求产品至少记录以下事件用户登录与登出SSH、串口、控制台登录成功的IP和账号。提权操作执行su、sudo的账号和执行命令。配置变更修改网络配置、恢复出厂设置、更新系统时间。OTA升级升级包来源、版本号、校验结果、开始和结束时间。比如业务进程有个写配置的接口埋点只需一行日志调用syslog(LOG_AUTH | LOG_NOTICE, config changed by uid%d, key%s, uid, key);这里的关键是“权限-行为-结果”三要素都记录别只记一句“config changed”否则出事后根本没法溯源是谁改的。审计埋点做得好设备被入侵之后的应急响应效率会完全不一样。如果内核支持auditd也可以考虑用auditctl监控关键路径auditctl -w /etc/passwd -p wa -k passwd-change auditctl -w /usr/sbin/dropbear -p x -k ssh-start不过auditd在低端嵌入式设备上有性能开销一般用在带独立管理CPU的高端设备上。普通MCU级别的设备用syslog埋点加远程转发就够用了。我自己的选型原则是先保证网络层面所有登录行为一定落日志再保证配置变更落日志最后再考虑文件监控按重要性排序避免一上来就想搞全套结果哪个都没做好。5. 轻量防火墙资源受限下的边界防守5.1 为什么业务单一反而更适合做白名单很多工程师对嵌入式设备加防火墙的第一反应是“性能不够”。其实嵌入式设备的业务非常单一对外端口通常就那两三个这恰恰是最适合做白名单的场景。服务器场景的防火墙难点在于业务复杂、人要访问各种端口但嵌入式设备完全相反它不需要给人提供任意访问能力只要把业务端口和管理端口放行其余一律拒绝规则数量很少对性能的影响微乎其微。相反如果不做防火墙设备上由于历史原因开启的服务、内网扫描出来的任意端口全部暴露在网络里那才是真正的性能和安全双重负担。我遇到过最典型的案例是一个工业网关出厂时为了调试开了一个9000端口的自定义调试服务后来调试完忘了关防火墙也没有结果这个端口被内网扫描器扫到被反复探测协议。最后把9000端口在防火墙上DROP掉之后内网扫描记录里这个设备的异常流量才消失。所以嵌入式设备做防火墙本质上是把“无意中暴露的服务”全部收敛掉让攻击面变得完全可控。5.2 iptables vs nftables几十条规则怎么选新内核5.x以上推荐nftables老内核或buildroot自带工具链里只有iptables那就用iptables。两者性能在这个规则量级下没有本质区别主要看你的内核和用户态工具支持哪个。nftables的优势是规则集可以原子替换写错了不容易把正在跑的连接打断。下面是nft配置片段table inet filter { chain input { type filter hook input priority 0; policy drop; ct state established,related accept iif lo accept tcp dport { 22, 443 } accept ip saddr 192.168.10.0/24 tcp dport 22 accept } chain forward { type filter hook forward priority 0; policy drop; } }如果是老内核继续用iptables语法更常见iptables -P INPUT DROP iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -p tcp --dport 22 -s 192.168.10.0/24 -j ACCEPT iptables -A INPUT -p tcp --dport 443 -j ACCEPT iptables -P FORWARD DROP这两套规则的核心思想一样先默认拒绝再放行必要流量。注意iptables的规则顺序非常重要匹配到某条规则之后就直接执行所以放行规则要写在拒绝策略之前。nft相比iptables的另一个好处是配置语法更清晰出错率低如果新项目内核版本支持我会优先用nft。5.3 一套可直接抄的默认拒绝规则我实际项目里常用的生产规则大概长这样放在/etc/init.d/firewall.sh里。以iptables为例#!/bin/sh IPT/usr/sbin/iptables # 清理旧的规则 $IPT -F $IPT -X $IPT -Z # 默认策略全部拒绝 $IPT -P INPUT DROP $IPT -P FORWARD DROP $IPT -P OUTPUT ACCEPT # 回环接口放行 $IPT -A INPUT -i lo -j ACCEPT # 已建立的连接放行 $IPT -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # 管理网段访问SSH管理口 $IPT -A INPUT -i eth0 -p tcp --dport 22 -s 192.168.10.0/24 -j ACCEPT # 业务端口MQTT over TLS $IPT -A INPUT -i eth1 -p tcp --dport 8883 -j ACCEPT # 其他流量拒绝默认策略已经DROP这里只是用于后续日志统计 $IPT -A INPUT -j LOG --log-prefix FW_DROP: --log-level 4注意两个细节。第一默认策略用-P设置而不是靠最后一条-A DROP这样即使规则被排序错误或者误删默认也是拒绝的。第二OUTPUT默认放行因为大多数嵌入式设备需要主动向外发起连接如果对外发连接做限制会让网络诊断变得极其痛苦。日志规则这里用的是log-level 4量大会刷屏所以生产环境建议配合--limit使用$IPT -A INPUT -j LOG --log-prefix FW_DROP: --log-level 4 -m limit --limit 3/min这样既保留了丢包记录又不会因为扫描风暴导致syslog被灌满。5.4 防火墙脚本的防锁死与持久化防火墙最容易翻车的场景是自己把自己锁在外面远程改规则把SSH端口给DROP了结果再也连不上设备。我在项目里用过一个很实用的保护机制——在规则脚本里加一个自动回滚逻辑#!/bin/sh # 应用新规则 sh /etc/init.d/firewall.sh # 等待20秒如果远程仍能连上则保持规则 sleep 20 if ping -c 3 -W 1 192.168.1.1 /dev/null 21; then : else # ping不通管理主机回滚规则 iptables -F iptables -P INPUT ACCEPT fi这个“看门狗”逻辑平时不用但它能在配置错误的情况下自动恢复网络。当然更稳妥的做法是先备份旧规则再加载新规则确认无误后保存。持久化也是个常见坑。桌面系统有iptables-persistent嵌入式一般没有。所以规则要写在启动脚本里在network服务起来之后执行#!/bin/sh case $1 in start) echo Starting firewall... /etc/init.d/firewall.sh ;; stop) iptables -F iptables -P INPUT ACCEPT ;; esac如果设备有reboot后自动加载rc.local的机制把规则脚本放进去也行。总之原则只有一条规则必须能在每次开机后自动恢复不能只靠手工执行一次。我见过不止一个项目现场工程师手动敲了一遍防火墙规则当时测着正常结果设备一重启规则全没了业务端口又暴露出来安全审计直接不通过。所以“自动加载”这四个字一定要验到不能想当然。5.5 规则调试怎么看丢包、怎么验证防火墙加完之后不是写完脚本就结束了一定要验证。我常用的验证方式分三层。第一层看规则命中计数。iptables每条规则都有计数执行iptables -L -v -n可以看到每条规则的包数和字节数。如果1883端口的业务流量一直在增长说明规则对业务放行是生效的如果input链drop计数暴涨说明有地方在丢包需要排查源地址和端口。第二层抓包确认。生产环境关了CONFIG_PACKET的话可以临时开抓包或者直接用tcpdump从另一台设备抓。看SYN包有没有被对端响应能快速定位是防火墙丢的还是服务本身没起来。第三层外部验证。从另外一台机器nmap扫一下所有端口nmap -sS -p 1-65535 设备IP安全的设备应该只显示放行的少数端口其余全是filtered或closed。这个扫描结果就是最好的“攻击面说明书”也可以作为交付文档的一部分提交给客户。我自己每次做完防火墙规则都会保留一份扫描截图放在交付资料里客户问起来直接贴出来比解释半天都有用。6. 第16讲课后思考题完整解析应读者要求把第16讲课后思考题里问得最多的四道题目的完整解析放在这里。这几道题都是嵌入式安全加固里特别容易答空的点也是面试和客户审计常问的方向。6.1 思考题1业务进程为什么不能用root直接跑这道题考察的是最小特权原则。核心答案有三层第一Linux的权限模型是进程级的进程一旦以root身份运行就能绕过所有文件权限、能加载内核模块、能篡改系统配置一个Web服务的高危漏洞直接升级为设备完全沦陷。第二实际攻击链条中攻击者通常先拿下一个非特权进程然后才尝试提权如果业务进程本身就是root省去了提权这一步攻击成本大幅下降。第三从产品运维角度看root跑业务还会导致故障定位困难无法区分是业务问题还是系统权限配置问题。所以业务进程必须用独立普通用户运行配合capabilities按需授权。6.2 思考题2本地调试需要Telnet能不能留答案是可以留但必须“受控”。最基础的要求是Telnet只能绑定在本地回环或者调试串口对应的网卡上绝不能监听业务网口只允许调试用户在特定时间段连接Telnet传输是明文口令会暴露所以至少要用强口令并限制登录失败次数。更推荐的做法是彻底禁掉Telnet本地调试走串口远程调试走SSH密钥登录。串口本身也要设登录密码否则谁插一根USB转串口线都能进系统防火墙和权限做得再好也白搭。我之前处理过一个事故设备被“内鬼”用串口线清了日志就是因为串口没有登录保护后来所有量产设备强制开启串口登录认证问题才彻底止住。6.3 思考题3怎样证明自己的加固措施真的有效这个问题容易答空很多人只背“我做了裁剪、做了权限、做了防火墙”但缺乏验证。我会用三个动作来证明第一攻击面验证拿nmap从外部扫全端口确认放行端口只有业务所需再从设备本地执行netstat/ss确认没有多余的监听端口。第二权限验证创建测试账号尝试写/etc目录、尝试加载内核模块确认被拒绝用setcap验证非root进程能否绑定特权端口。第三日志验证模拟一次错误登录和一次配置变更确认syslog里能看到记录且远程日志服务器能收到。这三组证据放到一起比任何“我觉得应该安全了”都有说服力。6.4 思考题4OTA升级把安全配置冲掉了怎么办这个问题非常贴近生产。很多设备做了加固之后第一次OTA就把安全配置全覆盖了因为OTA升级通常是用新的rootfs整包替换旧的安全配置没有落进“受保护”的分区。我建议在方案设计阶段就做分区规划系统分区只读放内核和基础rootfs配置分区放所有可修改的安全配置、网络配置、业务配置数据分区放日志和临时数据。OTA升级时只更新系统分区配置分区在升级脚本里通过备份-restore机制保留。这样即使系统更新了防火墙规则、SSH密钥、用户账号这些安全配置依然能延续下去。这个方案我在多个量产项目里验证过最大的收益是安全基线可以随产品持续演进而不是每次升级都“归零重来”。7. 加固效果验证与长期维护别让安全配置变成心理安慰7.1 上板后的安全自检清单加固代码写完不代表产品真的安全。我每次在交付之前都会跑一遍自检清单下面这张表可以直接复制使用检查项自查方法期望结果默认/空口令逐个用户尝试空口令和弱口令登录全部失败危险服务nmap扫描全端口 netstat查监听只剩业务和管理端口root远程登录尝试用root账号SSH登录被拒绝普通用户可登录业务进程权限ps查看关键服务运行用户非root用户关键目录可写性用普通用户尝试写/etc写入失败日志转发触发一次登录/配置事件查远程服务器能收到日志防火墙规则iptables -L -v -n查看计数默认DROP放行端口正常OTA后配置保留做一次升级测试安全配置不丢失这个清单我建议做成一个脚本每次产线烧录完成之后自动执行一遍输出一个文本报告跟着批次存档。客户做安全审计的时候这份报告比任何口头说明都管用。脚本里还可以加一条“关键配置文件校验”比如对/etc/shadow、/etc/iptables.rules、/etc/ssh/dropbear_rsa_host_key做SHA256校验存基准镜像里上线后定期比对一旦有变更立刻告警这个思路能把配置漂移问题也一并盯住。7.2 上线之后的安全生命周期安全加固不是一次性的项目而是一个生命周期管理过程。至少要盯住三件事。第一组件漏洞跟踪内核、busybox、openssl、dropbear这些组件会持续出现新CVE要定期对照厂商公告做版本评估该升级就升级不能“上线了就不管”。第二日志巡检远程日志服务器每天要有例行检查最好有简单脚本把异常登录、drop日志数量做统计出现异常趋势时及时告警。第三配置变更管理生产环境的安全配置不能随手就改任何变更要可回溯、可回滚我在防火墙上加的自动回滚机制就是这个思路的具体落地。我个人的体会是嵌入式Linux安全加固最难的从来不是某个技术点而是工程师心里那根弦。裁剪干净一下、权限收紧一下、日志多看一眼、防火墙规则亲测一遍这些工作单独看都不复杂但能把每一环都落到代码和脚本里并且经得起客户审计的团队才是真正在产品化上成熟的团队。最后再补一个实战心得加固做完之后别忘了把整套方案写进产品需求文档让后续迭代的同事知道哪些配置是安全基线、不能随意改动否则下一个人改代码时顺手把某个安全选项关了你前面做的所有工作都会在不知不觉中失效。