在网络攻防过程中,Linux系统的持久化技术是指攻击者通过各种手段在目标系统中建立长期存在的访问权限,使其能够在系统重启、更新或其他安全措施下依然保持对系统的控制。持久化的手段包括但不限于通过修改启动脚本、添加定时任务、植入恶意内核模块、创建后门账户等方式。这些技术使得攻击者能够在网络攻击活动中保持对系统的持久控制,并在进行其他攻击操作时不易被发现。
持久化的重要性在于,它极大地提升了攻击者的攻击持续性和隐蔽性。即使防御者发现并清除了一些初期的攻击迹象,持久化机制仍然能保证攻击者可以迅速恢复对目标系统的控制,从而造成更长时间的潜在威胁。因此,在防守过程中,安全团队需要特别关注系统中的持久化迹象,定期检查系统的启动项、用户权限、网络连接等方面,以及时发现并清除这些持久化机制。
根据持久化的手段的不同,本文从浅到深介绍Linux系统下常见的持久化的方法。
Linux系统的账户分为Root账户和User账户,后者也是系统操作中常用的账户,在服务器被攻击中,攻击者常常会利用漏洞(比如越权、RCE、LFI等)查看/etc/passwd文件,其目的一方面是为了查看漏洞是否可以被利用成功,一方面是为了查看系统中的账户有哪些。
在获取服务器权限后,创建Linux系统账户有两个命令,一个是adduser,一个是useradd,前者是后者封装的结果,使用更为简单,但可设置项不灵活,比如用户分组、家目录、Shell的设置。因此,为了更隐蔽地创建系统账户,建议使用useradd命令,比如:
# create a user account without home directory
sudouseradd -M <username>
# set password for account
sudopasswd <username>
通过SSH密钥文件
创建账户许多时候往往太多于明显,除了账户之外,还可以通过修改authorized_keys文件来进行持久化,这个文件位于<user-home>/.ssh/authorized_keys。
在早年的Redis未授权漏洞利用中,便是利用Redis未授权修改该文件,增加了SSH密钥文件对中的公钥内容,从而可以通过SSH密钥方式访问服务器。
# Generate key files though SSH command
ssh-keygen -t rsa -b 2048
生成后的密钥文件包含私钥文件
id_rsa和公钥文件id_rsa.pub,将id_rsa.pub文件内容复制到authorized_keys文件中即可完成配置。
公钥内容的最后通常会有生成密钥对主机的信息,比如root@kali之类,这个内容可以删除或修改为其他主机用户信息。
在Linux系统中,计划任务是指设置在指定时间或周期自动执行某个命令或脚本。最常见的计划任务工具是crontab命令,正常情况下,使用crontab -e命令可以编辑计划任务,这个命令生成的计划任务存放在/var/spool/cron/crontabs/<user-name>下。
如果获得了系统的Root权限,除了crontab命令添加计划任务,还可以通过在/etc/cron.d目录下添加系统级别的计划任务,比如:
0 0 * * * root /root/bin/backdoor.sh
相比
crontab命令,该目录下的计划任务需要在时间后指定执行该脚本的用户,比如上述示例中的root。
另外,也可以直接将脚本放在/etc/cron.daily、/etc/cron.weekly、/etc/cron.monthly等目录下根据日、周、月定时执行。
另外一种计划任务是systemd计划任务,这个方法利用的是systemd服务的timer模块,类似于在Windows系统中创建服务(实际上,写一个有迷惑性的服务文件也可以单独作为后门使用),通过systemd list-timers可以查看当前系统所有这类计划任务:
创建systemd timer计划任务需要一个.service文件和一个.timer文件,前者是服务文件,后者是定时文件。
比如,在/etc/systemd/system目录下创建test_script.service文件:
[Unit]
Description=Thisis a test service
[Service]
ExecStart=/root/test.sh
接着,在
/etc/systemd/system目录下创建test_timer.timer文件:
[Unit]
Description=Thisis a test timer
[Timer]
OnBootSec=60
OnCalendar=daily
OnUnitActiveSec=5m
Unit=test-script.service
[Install]
WantedBy=timers.target
上述文件中,
OnBootSec是指系统启动后多少秒开始执行计划,OnCalendar用于指定任务的触发事件或频率,其值可以是daily、weekly、monthly或hourly,也可以是类似2025-01-03 12:00:00这样的具体时间,OnUnitActiveSec指定任务触发后多长时间开始执行,上面文件中是触发后的5分钟执行任务,Unit指定的是对应的服务文件。
最后,通过以下命令启动计划任务:
# reload systemd configuration
systemctldaemon-reload
# enable timer on boot
systemctlenable test_timer.timer
# start timer
systemctlstart test_timer.timer
修改Shell环境配置
Linux系统中和Shell环境配置的文件有很多,它们基于用户级或系统级可以在Shell脚本执行前或执行后执行相关的Shell脚本。
常见的配置文件包括用户级的~/.bashrc、~/.bash_profile、~/.profile,以及系统级的/etc/bash.bashrc、/etc/profile、/etc/profile.d,总的来说分为profile、bash_profile和bashrc三类文件,其中profile是登录Shell时候执行的,且适用于所有类型的Shell,bash_profile是登录Shell执行的,bashrc是交互式非登录Shell执行的。
因此,可以将持久化的脚本写在/etc/profile或/etc/profile.d中,亦或者是~/.profile中,这意味着当攻击目标的用户登录服务器Shell时候会执行持久化脚本,当然,也可以利用bashrc等其他文件。
在Linux系统中,动态链接(Dynamic Linking)是指程序在运行时通过动态链接库来加载和使用函数和数据,这种机制允许程序在不重新编译的情况下更新或替换库文件,从而能够在不改变程序源代码的前提下进行修复和升级。其中,动态链接器(Dynamic Linker)是负责在程序运行时加载和链接共享库的程序,当程序启动时,其动态链接的部分或共享库引用的部分通过符号进行标识,动态链接器负责程序与共享库的符号解析,并确保程序中的函数调用能够正确指向内存中加载的库。
Linux系统中的动态链接器通常指的是ld-linux.so文件,该文件通常位于/lib或/lib64目录下,比如/lib64/ld-linux-x86-64.so.2。
除此之外,在Linux系统中还有一个叫LD_PRELOAD的环境变量与动态链接器密切相关,这个环境变量允许动态链接器在加载程序时候首先加载LD_PRELOAD中定义的共享库,因此也会覆盖程序真正加载的共享库中的符号信息,这个功能常常用于程序的调试、功能或性能测试,但也会被用于恶意软件的植入。
LD_PRELOAD可以通过LD_PRELOAD环境变量设置,也可以通过/etc/ld.so.preload文件设置,但默认情况下这两者都是未设置的。
如果要查看一个程序的共享库依赖,可以使用ldd命令查看,比如:
另外,通过系统调用和信号追踪的strace命令可以看到/etc/ld.so.preload是否存在:
我们以在许多程序中常用的fopen函数为例进行覆盖,首先编写示例的共享库preload.c:
#include<stdio.h>
__attribute__((constructor))void preload_init() {
printf("[preload_demo.so] Dynamiclibrary loaded successfully.\n");
}
FILE *fopen(const char *path, const char *mode) {
printf("THis is evilfopen.\n");
return NULL;
}
上述代码中,__attribute__((constructor))属性将
preload_init()声明为在main函数之前执行的函数,这样可以通过该函数的执行检查动态链接是否成功,接着是重新定义了fopen函数,输出“THis is evil fopen”。
而后通过下面的命令进行编译:
gcc -Wall -fPIC -shared -nostartfile -o preload.so preload.c
其中,
-Wall不是必需,是为了方便查看所有编译期间的错误,-nostartfiles参数为了避免标准启动文件的链接,降低不必要的代码。
再将编译后的文件地址(如/home/kali/Desktop/preload.so)写入LD_PRELOAD环境变量(之所以不推荐/etc/ld.so.preload,是该方法无法恢复动态链接,会导致系统其他操作无法正常)。
假设系统中有另一个正常的程序中也调用了fopen函数,比如下面的normal.c:
#include<stdio.h>
int main() {
printf("Calling the fopen()function.\n");
FILE *fd = fopen("test.txt","r");
if (!fd) {
printf("fopen() returnedNULL.\n");
return 1;
}
printf("fopen()secceeded.\n");
return 0;
}
编译后执行该程序
normal.o,即可看到fopen函数被替换后的结果:
如果查看normal.o的共享库的依赖关系,也可以看到其依赖结果:
LD_PRELOAD虽然可以替代函数符号,但是也存在诸多限制,比如不能利用SetUID进行提权(比如普通用户执行所有权是Root的程序结合LD_PRELOAD进行提权),不适用于静态链接、内联函数(如printf函数)、系统函数(如syscall函数)以及自定义的函数。
因为Linux系统中的一切都是文件化,在Linux系统下可以进行持久化的方法还有很多,结合应用或服务的特性,一切可以执行脚本或有hook机制的地方都可以隐藏持久化脚本,比如Shell脚本中的trap命令,又或者是Alias命令,甚至是MOTD信息的修改等等。