拓冰建站拓冰建站
首页 / 资讯中心 / 正文

等保2.0内存安全基线检测:用Shell脚本自动核查Linux内核配置

简介《内存安全基线检测符合等保2.0的配置核查自动化脚本》是一份系统讲解内存安全基线检测与等保2.0合规核查自动化脚本设计的 PDF 文档适合网络安全工程师、等保测评人员及 CTF-Misc 学习者阅读。文档覆盖等保2.0对内存安全的要求、基线检测核心技术原理静态/动态/混合检测、模式匹配、数据流分析、污点传播等并完整展开自动化脚本的架构设计、关键算法、部署与集成、测试验证以及金融/医疗/工控/政务云等应用场景可用于合规自查、测评准备和安全技术进阶。资源包内为 1 个 PDF 文件大小 4.61MB支持目录跳转和左侧大纲定位页面内容完整图表与公式显示正常。目前已有 78 人学习读者可快速建立内存安全基线检测知识框架理解等保2.0合规核查的自动化落地方案脚本执行流程、规则引擎、修复功能模块及 SIEM/DevOps 集成细节也为后续二次开发提供直接参考。1. 内存安全基线检测不是走过场等保2.0测评最先盯的就是这里的状态等保2.0测评机构进场时不会只扫一遍端口就收工。他们会在被测评的业务服务器上执行一批配置核查命令其中相当一部分盯着内存相关状态地址空间随机化开没开、内核日志权限够不够紧、core dump 会不会把敏感数据写进磁盘、共享内存挂载是不是裸奔。这些检查项汇总起来就是“内存安全基线检测”。它解决的问题很具体让一台服务器的内存侧加固状态可量化、可留痕、可随时复检。适合谁负责等保测评整改的运维、做安全基线的开发、以及要应付检查又要兼顾业务的系统管理员。这篇文章把这件事拆成标准依据、脚本实现、参数含义和坑最终给出一份能直接跑起来并自我验证的 shell 方案。2. 等保2.0的内存相关条款先把核查项钉在标准上脚本才不会自嗨很多团队做配置核查是从网上抄一份“安全基线”开始抄完发现测评机构不认因为脚本里的检查项和 GB/T 22239—2019 对不上。等保2.0 的技术要求分成安全物理环境、安全通信网络、安全区域边界、安全计算环境、安全管理中心五大块内存安全相关的要求几乎都落在“安全计算环境”里。写脚本之前先搞清楚标准里到底哪几条管得到内存这是整个方案能不能过测评的前提。2.1 等保2.0里管内存的条款在“安全计算环境”里找答案安全计算环境这一块和内存安全直接相关的主要是“访问控制”和“入侵防范”两类要求。访问控制要求在操作系统层面对主体、客体进行标识和权限管理落到具体配置上就是非特权用户能不能读取内核地址、能不能通过 ptrace 附加高权限进程、内存地址空间是否做了随机化入侵防范则要求遵循最小安装、配置内核防护机制、关闭不必要的系统功能和服务。测评机构在实际操作时不会对着标准原文一条条念而是拿着检查表执行命令。以三级系统为例常见的核查动作包括查看/proc/sys/kernel/randomize_va_space是否等于 2查看/proc/sys/kernel/kptr_restrict是否大于等于 1查看/proc/sys/kernel/dmesg_restrict是否等于 1查看/proc/sys/fs/suid_dumpable是否为 0。这些命令的输出是即时的、可复现的评审专家可以直接截图存档。这里有一个容易被忽略的点等保2.0 要求的是“配置核查留痕”不是“当时配置是对的”就行。测评机构往往会要求提供最近一次基线核查的自动化记录包括执行时间、执行人、核查结果。如果团队只是手工执行几条 sysctl 命令然后把结果截图发到群里后续审计时很难交代。这也是自动化脚本能解决的真实痛点脚本本身就是证据链的一部分。2.2 核查和漏洞扫描不是一回事为什么内存基线适合用脚本做配置核查和漏洞扫描常被混为一谈但两者的工作方式完全不同。漏洞扫描器会向目标发送构造好的数据包或尝试利用已知漏洞存在一定的业务风险而且对内存这类“看不见摸不着”的配置状态覆盖有限配置核查则是读取系统当前配置状态和预先定义的基线值做比较不发攻击流量对业务几乎无影响非常适合做成自动化脚本在业务服务器上直接执行。内存安全基线特别适合脚本化还有一个实际原因内存相关的安全配置绝大多数是可枚举的键值对。sysctl参数是一个路径、一个字符串或数字挂载选项是一组逗号分隔的标志资源限制是一个数值。这种“状态可读、期望值可写、结果可比较”的特征决定了它不需要复杂的语义分析用 bash 加几个判断就能做得很扎实。但这也带来一个陷阱正因为适合脚本化网上流传的基线脚本特别多质量良莠不齐。有的脚本只查当前运行值不查持久化配置服务器重启后配置回退也发现不了有的脚本把内核版本相关的参数写死换了发行版就误报一片。等保测评要的不是一个“看起来跑得通”的脚本而是能稳定反映真实状态的核查工具。所以接下来的第 3 章我会按“采集、判定、报告”三层结构给出一个可复用的脚本框架每一层的职责都拆清楚。2.3 内存基线的范围圈定内核参数、挂载属性、进程资源限制在写脚本之前先明确“内存安全基线”要圈哪些范围。我一般按三个维度来圈定既保证覆盖等保2.0 的核心要求又不至于把无关参数拉进来造成误报。第一层是内核参数。这一层是内存安全基线的核心包括地址空间随机化、内核指针可见性、内核日志访问控制、suid 程序 core dump 策略、空指针映射防护等。它们全部通过/proc/sys/下的接口暴露用sysctl -n就能读取。第二层是挂载属性重点看 tmpfs 类型的内存文件系统典型是/dev/shm是否以安全选项挂载比如是否带了nosuid、nodev这一层在某些测评机构的检查表里会单独出现。第三层是进程资源限制包括 core dump 的大小限制、进程可锁定内存的上限等它们决定了极端情况下内存侧的风险能放多大。不建议在这个阶段就把 HugePages、内存热插拔、NUMA 绑定这类运维调优参数纳入基础基线。它们和“安全”的关联度不直接而且业务影响大适合作为进阶补充项放在第 5 章讨论。先守住等保2.0 能明确对应的检查项再谈扩展这是做配置核查脚本的基本原则。3. 把配置核查写成自动化脚本采集、判定、报告三件事分开做网上很多基线脚本是“读一个值、if 判断、echo 输出”的平面结构写的时候爽后期维护非常痛苦。等保要求是半年一核查但核查项是会变的——内核版本升级、测评机构检查表调整、业务新增大页内存每改一次都要重读整个脚本这本质上是因为采集、判定、报告混在了一起。我推荐的做法是把它拆成三层采集层只负责读取系统当前状态判定层只负责把采集到的值和基线做比较输出 PASS、FAIL 或 WARN报告层负责把结果汇总成可留痕的记录。这样改一个核查项时只需要动“基线定义”和“判定规则”采集层基本不用碰。3.1 一个可复用的脚本骨架先看整体结构再填细节整个脚本的典型结构如下先列出来方便对照后面每一段代码#!/bin/bash # memory_baseline_check.sh - 内存安全基线配置核查等保2.0 配置核查 # 只读脚本不改动任何系统配置。 # 用法bash memory_baseline_check.sh [--strict] PASS0 FAIL0 WARN0 declare -a DETAILSPASS、FAIL、WARN是三个计数器DETAILS数组用来暂存每一行的检查明细最后统一输出。先声明这些全局变量是因为后面所有检查函数都要往它们上面累加结果。用关联数组存基线值会让脚本更清晰考虑到 bash 4.0 以上才支持关联数组而绝大多数发行版都满足所以放心用。3.2 采集与判定代码用 sysctl、findmnt、ulimit 拿状态并对比先做最核心的内核参数检查。它会读取当前运行值和基线值比对。这一步同时覆盖“采集”和“判定”两层declare -A BASELINE( [kernel.randomize_va_space]2 [kernel.kptr_restrict]1 [kernel.dmesg_restrict]1 [fs.suid_dumpable]0 [vm.mmap_min_addr]65536 ) check_sysctl() { local key$1 expect$2 cur cur$(sysctl -n $key 2/dev/null) if [ -z $cur ]; then echo [WARN] $key 当前内核不支持该参数跳过 WARN$((WARN1)) elif [ $cur $expect ]; then echo [PASS] $key$cur (期望 $expect) PASS$((PASS1)) else echo [FAIL] $key$cur (期望 $expect) FAIL$((FAIL1)) fi }这段代码的逻辑说明sysctl -n $key只输出参数值不输出参数名方便直接和期望值比较2/dev/null把“参数不存在”的错误信息吞掉留给后面的-z $cur判断。这里最关键的细节是参数不存在和参数值为 0 是两种不同的情况sysctl -n对不存在的参数输出为空而对值为 0 的参数正常输出0所以用-z判断“空”不会和真实值混淆。接下来检查持久化配置。等保核查不仅要看当前运行值还要看重启后是否还能保持check_persist_sysctl() { local key$1 file for file in /etc/sysctl.conf /etc/sysctl.d/*.conf; do [ -f $file ] || continue if grep -qE ^[[:space:]]*${key}[[:space:]]* $file; then echo [INFO] $key 在 $file 中已显式配置 return 0 fi done echo [WARN] $key 未在 sysctl.conf 或 sysctl.d 中显式配置重启后可能与当前值不一致 WARN$((WARN1)) }这段的逻辑说明它只做“是否显式配置过”的检查不判断配置的值对不对因为不同发行版的配置加载优先级不同值是否生效要交给sysctl --system这类命令去确认。为什么只做存在性检查因为在第 4 章避坑里会讲配置文件的加载顺序很容易让人翻车一个脚本如果在这里想当然地按某个发行版的优先级去解析换台机器就会得出错误结论。与其冒险不如把“是否配置”和“值对不对”分开看。然后是内存文件系统挂载属性。这一步用findmnt比直接解析/proc/mounts更不易踩坑check_shm_mount() { local opts opts$(findmnt -no OPTIONS /dev/shm) if [ -z $opts ]; then echo [WARN] /dev/shm 未挂载跳过 WARN$((WARN1)) elif [[ $opts ~ nosuid ]] [[ $opts ~ nodev ]]; then echo [PASS] /dev/shm 挂载选项: $opts PASS$((PASS1)) else echo [FAIL] /dev/shm 缺少 nosuid 或 nodev当前: $opts FAIL$((FAIL1)) fi }这里的参数说明findmnt -no OPTIONS中的-n表示不加表头-o OPTIONS表示只输出挂载选项列。[[ $opts ~ nosuid ]]是 bash 的正则匹配看起来简单但对挂载选项这种逗号分隔的字符串足够可靠。需要注意/proc、/sys这类虚拟文件系统本身默认就带nosuid,nodev不需要专门核查反而要注意别把它们误判成风险项。3.3 参数说明与扩展增加一个核查项要改哪三处脚本设计成上面以后日常使用中改动最多的场景是“要加一个新的核查项”。很多同事习惯直接在check_sysctl函数里再加一行判断结果改完以后报告计数不对、显示也不完整。正确的做法是保持三处对应修改第一往BASELINE关联数组里加一条比如[vm.overcommit_memory]0第二确认判定规则是否需要用专门的函数而不仅仅是等值比较——比如某个参数的期望值是“大于等于 1”就不能放进等值匹配的循环里第三在main函数或者脚本末尾的主流程里调用对应的检查函数。改完以后跑一遍带--strict参数的测试确认新增项在正常和异常两种状态下都能正确报出来再投入使用。参数说明vm.overcommit_memory的合法值包括 0、1、2其中 0 是内核默认的启发式策略1 表示总是允许超额分配2 表示禁止超过规定比例的分配。等保2.0 测评一般不会强制要求某个固定值但如果业务系统内存压力大这个参数设置不当会直接引发 OOM所以在基线上建议按发行版默认值核查而不是拍脑袋写死一个数。4. 内存基线检测避坑指南五个翻车现场和它们的修法配置核查脚本写起来不难难在跑出来的结果能不能信、能不能在测评机构面前站住脚。这一章直接给五个我在实际落地中踩过的坑全部按“现象 → 原因 → 解决”来写。前两个和参数有关第三个涉及容器环境第四个是典型的核心转储误判第五个是基线本身的设计问题。4.1 现象sysctl.conf 里写的值明明是对的脚本却读到另一个值第一次做内存基线核查时我在一台 CentOS 7 服务器上发现/etc/sysctl.conf里写着kernel.randomize_va_space 2但脚本读当前运行值却是 0。当时第一反应是“配置没生效”重新执行sysctl -p之后运行值才变成 2。原因发行版对 sysctl 配置有三层来源按优先级从低到高依次是/usr/lib/sysctl.d/、/etc/sysctl.d/、/etc/sysctl.conf后加载的覆盖先加载的。有的云镜像会在/usr/lib/sysctl.d/或者/etc/sysctl.d/下放一个默认配置文件把randomize_va_space设成 0而/etc/sysctl.conf里的配置又在系统启动过程的某个阶段没有正常加载。这个状态在测评机构看来就是“未启用地址空间随机化”属于实打实的风险项。解决脚本里的check_persist_sysctl不能只看/etc/sysctl.conf一个文件。修改后的逻辑是先用sysctl --systemsystemd 系统看全部配置文件叠加后的最终结果再对比/etc/sysctl.conf里的显式配置两者不一致时输出 WARN 并给出提示。sysctl --system在 sysvinit 系统上不可用但等保测评面对的主流服务器基本都是 CentOS 7 以上或 Ubuntu 16.04 以上可以放心用它。4.2 现象内核没有的核查项被当成“不通过”报出来把脚本扔到一台运行老内核的嵌入式服务器上vm.mmap_min_addr这个参数在旧内核里本来就不存在。脚本当时的逻辑是“读不到值就 FAIL”结果这台机器报了一堆红最后人工确认才知道是内核版本根本不支持这个参数。原因内核参数是否存在取决于编译选项和内核版本同一套脚本跑在不同内核上可能出现部分参数读取为空。这里要特别区分参数值为 0 是“存在但状态不符”参数读取为空是“当前内核没有这个能力”两者含义完全不同不能共用一套判定。解决第 3 章里check_sysctl已经写了对应逻辑读取为空时输出 WARN 而不是 FAIL并在报告里注明“当前内核不支持”。同时建议脚本开头就记录内核版本号uname -r报告里带上版本信息这样测评机构看到 WARN 时能立即判断是环境差异而不是配置缺陷。注意这条规则要写在脚本注释里防止后续接手的人为了消除 WARN 而把判定改回 FAIL。4.3 现象在容器里跑核查读到的全是宿主机的内核状态容器环境下最容易翻车。在 docker 容器里执行sysctl -n kernel.randomize_va_space读出来的是宿主机的值不是容器自身的状态。因为/proc/sys下的内核参数属于宿主机内核容器默认共享内核并没有自己独立的一套内存安全配置。如果团队把容器当虚拟机用在容器里跑了核查脚本会把宿主机状态当成容器状态上报数据完全失真。原因容器不是完整的操作系统它共享宿主内核。等保2.0 测评中容器场景通常要求既核查宿主机内核参数也核查容器镜像和运行配置。宿主机的内核参数影响所有容器的底层安全性容器的ulimit、/dev/shm挂载、Capabilities 限制则属于容器自身配置不能互相替代。解决在脚本开头加一段环境检测发现容器环境时跳过 sysctl 类检查只保留挂载属性和资源限制类检查并明确在报告头部标注“当前运行环境为容器内核参数未核查”。检测方法很简单[ -f /.dockerenv ]或者检查/proc/1/cgroup中是否包含docker、containerd、kubepods关键字。这个功能必须在脚本里做成默认行为而不是靠执行时手工选择模式因为很容易忘记。4.4 现象当前会话 core dump 是关的业务进程一崩还是出 dump 文件核查脚本里有一条是检查 core dump 是否关闭我用ulimit -c查当前会话是 0判定 PASS。结果某个 Java 服务崩溃后工作目录里还是出现了core.*文件里面可能带了堆内存中的敏感数据。测不过事小数据落盘事大。原因ulimit -c展示的是当前 shell 会话的资源限制而生产环境的服务大多由 systemd 管理。systemd 服务进程的资源限制由LimitCore指令控制默认值未必继承自 shell。换句话说shell 里看是关的服务进程里可能没关。解决脚本里加一个专门针对 systemd 服务配置的检查项用systemctl show service -p LimitCore查看具体服务的 core 限制同时在/etc/security/limits.conf里检查* hard core 0这样的硬限制配置。如果目标服务器跑着多个业务服务建议按服务的 systemd unit 逐个核查而不是只依赖全局配置。顺带说一句这套逻辑同样适用于LimitMEMLOCK也就是进程内存锁定上限第 5 章会再提到。4.5 现象基线值写死成“二选一”换系统版本后误报暴涨脚本里某个参数判断写的是if [ $cur ! 0 ] [ $cur ! 1 ]; then FAIL; fi当时是为了兼容两种常见基线但系统从 CentOS 7 换到 Ubuntu 22.04 后这个参数的默认值变成了第三种脚本突然把所有机器都报成 FAIL。原因基线值本质上是“期望状态”它会随业务需求、发行版默认策略、内核版本变化。把基线硬编码在脚本逻辑里意味着每次环境变化都要改代码改完还要重新测试很容易改出问题。解决把基线值从脚本逻辑中抽出来放到脚本同目录下的一个配置文件中脚本启动时读取。这样新增机器或切换系统版本时只需要换配置文件不需要动脚本。这个改动看起来只是“把值挪个地方”实际效果是让脚本变成通用的核查引擎基线变成可维护的数据。等保测评周期一般是半年到一年时间久了基线一定会变这步抽离值得提前做。5. 核查参数表与控制点映射这些值该是多少、能不能动、谁来定脚本有了避坑也列了接下来是一张可以直接抄进整改方案里的参数表。这张表解决三个问题核查项具体指什么、期望值是多少、对应等保2.0 的哪个控制点。表格里的值按主流 Linux 发行版默认安全基线整理部分参数在等保2.0 的测评作业指导书中能直接找到对应描述。5.1 一张可抄的参数表九个核查项、推荐基线与配置位置核查项推荐基线配置位置对应等保控制点说明kernel.randomize_va_space2/etc/sysctl.conf 或 /etc/sysctl.d/入侵防范-内核防护1 只随机化栈2 同时随机化堆量产服务器必须为 2kernel.kptr_restrict1 或 2同上入侵防范-内核防护限制非特权用户读取内核地址监控工具兼容性需验证kernel.dmesg_restrict1同上入侵防范-内核防护禁止非特权用户通过 dmesg 查看内核日志fs.suid_dumpable0同上访问控制禁止 suid 程序产生 core dump防止内核镜像落盘vm.mmap_min_addr65536同上访问控制防止空指针映射攻击旧内核需确认支持vm.overcommit_memory0默认同上安全配置基线不强制,但应明确写死基线值并核查一致性/dev/shm 挂载属性nosuid,nodev/etc/fstab访问控制有的发行版默认仅 nodev需补充 nosuidcore dump 限制0 或受限大小/etc/security/limits.conf、systemd unit安全配置基线需要同时覆盖 shell 会话和 systemd 服务RLIMIT_MEMLOCK有限值limits.conf、systemd LimitMEMLOCK访问控制防止进程无限锁定物理内存拖垮系统参数说明fs.suid_dumpable这一项容易被忽略。suid 类程序在崩溃时产生的 core dump 权限模型复杂可能连带把敏感内存内容写进文件供低权限用户读取所以基线直接要求为 0。而vm.mmap_min_addr的值 65536 是 x86 架构下的通行值内核默认就是它核查时主要确认没有被改小。5.2 哪些参数建议“只核查不修改”把脚本定位成基线检测而不是加固工具写到这里必须强调一个定位问题这个自动化脚本应该是“检测工具”而不是“加固工具”。很多团队拿到脚本后觉得“既然你知道期望值为什么不直接改”于是又加了一段自动sysctl -w的逻辑。这个想法非常危险。原因有两层。第一层是业务风险kernel.randomize_va_space对启用旧版 JVM 的应用有潜在性能影响kernel.kptr_restrict会让某些性能监控工具读不到内核符号导致功能降级kernel.dmesg_restrict会影响开发环境排查问题。盲目改成基线值很可能在测评之前先把业务搞挂。第二层是等保要求本身测评机构要的是“制度执行记录”自动加固虽然省事但缺少人工审核环节出了问题很难界定责任。所以我一般把脚本定位为“状态发现工具”输出 FAIL 项和对应修复建议由系统管理员确认影响面后手工修改改完再跑一遍脚本复核形成一个“发现问题→评估影响→修改→复核留痕”的闭环。脚本里只保留只读操作不写任何sysctl -w、ulimit -c之类的修改动作从设计上杜绝误操作。5.3 新增大内存、大页内存场景时的补充项HugePages 与 Memory Lock上面那张表是通用基线覆盖等保2.0 最常见的核查项。如果被测评的业务服务器启用了大页内存或高并发内存锁定还需要补两个进阶核查项。一个是vm.nr_hugepages这个参数决定系统预分配的 HugePages 数量。大页内存一旦预分配就会被进程独占锁定无法被回收。如果配置了过大值而实际业务用不满相当于白白浪费物理内存还可能造成其他进程 OOM。另一个是RLIMIT_MEMLOCK它限制单个进程能锁定的内存上限。数据库类服务通常会把mlockall打开防止内存被换出但如果不对这个上限做约束一个失控进程就能把物理内存锁死严重影响同机其他业务。这两个项的基线值没有“通用标准答案”需要根据服务器的物理内存和业务分配制定并写入环境专用的基线配置文件。这也是第 4 章提到“基线值参数化”的典型场景同一个脚本在普通业务服务器上跑通用基线在数据库服务器上跑大内存专用基线靠配置文件区分而不是靠改代码区分。6. 用“故意破坏”验证脚本上线前把每个检查项打疼一遍脚本写完不代表它能用。配置核查脚本有个特点正常状态下所有检查项都是 PASS看不到判定逻辑是否真的在工作。我见过有人把基线值写反了全部 PASS 了一个月直到测评机构手工抽查才发现。所以上线前的验证方法是“故意破坏”把系统状态改成不符合基线看脚本能不能准确报出来。验证步骤很简单。第一备份当前参数值比如sysctl -n kernel.randomize_va_space存到一个变量里第二把参数改成坏值例如sysctl -w kernel.randomize_va_space0第三跑脚本确认该项输出 FAIL第四恢复原值再跑一遍确认输出 PASS。对表里的每一项都这样做一遍核心是确认“FAIL 是由状态变化触发的而不是脚本逻辑本身有误”。注意这组操作只建议在专门的测试机上做不要在业务生产服务器上尝试因为改内核参数可能影响正在运行的服务。还有一个值得做的收尾动作把脚本接进日常巡检。常见做法是在/etc/cron.d/下放一个只读脚本的定时任务每月自动执行一次并把结果追加到带时间戳的日志文件比如/var/log/mem_baseline_$(date \%Y\%m).log。下个月再跑时用diff对比两个月的报告重点看新出现的 FAIL 项——这通常是业务变更或系统升级引入的风险点值早于测评机构发现。我现在的习惯是每次要改系统内核参数或上线新服务之前先跑一遍内存安全基线核查把当前状态存档再动手改动。改完再跑一遍对比两份报告的差异就知道这次变更动了哪些安全配置有没有把之前封好的口子又打开。这个习惯在等保测评整改期间救了我好几次希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门