构建集中式的病毒扫描机制

Clam AntiVirus(ClamAV)是一款免费而且开放源代码的防毒软件,软件与病毒库的更新皆由社区免费发布,官网地址: http://www.clamav.net/lang/en/ 。目前 ClamAV 主要为 Linux、Unix 系统提供病毒扫描、查杀等服务。pyClamad( http://xael.org/norman/python/pyclamd/ )是一个 Python 第三方模块,可让 Python 直接使用 ClamAV 病毒扫描守护进程 clamd,来实现一个高效的病毒检测功能,另外,pyClamad 模块也非常容易整合到我们已有的平台当中。下面详细进行说明。

pyClamad 模块的安装方法如下:

# 1、客户端(病毒扫描源)安装步骤
# yum install -y clamav clamd clamav-update    #安装clamavp相关程序包
# chkconfig --levels 235 clamd on    #添加扫描守护进程clamd系统服务
# /usr/bin/freshclam    #更新病毒库,建议配置到crontab中定期更新
# setenforce 0    #关闭SELinux,避免远程扫描时提示无权限的问题
# 更新守护进程监听IP配置文件,根据不同环境自行修改监听的IP,“0.0.0.0”为监听所有主机IP
# sed -i -e '/^TCPAddr/{ s/127.0.0.1/0.0.0.0/; }' /etc/clamd.conf
# /etc/init.d/clamd start    #启动扫描守护进程

# 2、主控端部署pyClamad环境步骤
# wget http://xael.org/norman/python/pyclamd/pyClamd-0.3.4.tar.gz
# tar -zxvf pyClamd-0.3.4.tar.gz
# cd pyClamd-0.3.4
# python setup.py install

模块常用方法说明

pyClamad 提供了两个关键类,一个为 ClamdNetworkSocket() 类,实现使用网络套接字操作 clamd;另一个为 ClamdUnixSocket() 类,实现使用 Unix 套接字类操作 clamd。两个类定义的方法完全一样,本节以 ClamdNetworkSocket() 类进行说明。

  • __init__(self, host='127.0.0.1', port=3310,timeout=None) 方法,是 ClamdNetworkSocket 类的初始化方法,参数 host 为连接主机 IP;参数 port 为连接的端口,默认为 3310,与 /etc/clamd.conf 配置文件中的 TCPSocket 参数要保持一致;timeout 为连接的超时时间。

  • contscan_file(self, file) 方法,实现扫描指定的文件或目录,在扫描时发生错误或发现病毒将不终止,参数 file(string 类型)为指定的文件或目录的绝对路径。

  • multiscan_file(self, file) 方法,实现多线程扫描指定的文件或目录,多核环境速度更快,在扫描时发生错误或发现病毒将不终止,参数 file(string 类型)为指定的文件或目录的绝对路径。

  • scan_file(self, file) 方法,实现扫描指定的文件或目录,在扫描时发生错误或发现病毒将终止,参数 file(string 类型)为指定的文件或目录的绝对路径。

  • shutdown(self) 方法,实现强制关闭 clamd 进程并退出。

  • stats(self) 方法,获取 Clamscan 的当前状态。

  • reload(self) 方法,强制重载 clamd 病毒特征库,扫描前建议做 reload 操作。

  • EICAR(self) 方法,返回 EICAR 测试字符串,即生成具有病毒特征的字符串,便于测试。

实践:实现集中式的病毒扫描

本次实践实现了一个集中式的病毒扫描管理,可以针对不同业务环境定制扫描策略,比如扫描对象、描述模式、扫描路径、调度频率等。示例实现的架构见图4-1,首先业务服务器开启 clamd 服务(监听 3310 端口),管理服务器启用多线程对指定的服务集群进行扫描,扫描模式、扫描路径会传递到 clamd,最后返回扫描结果给管理服务器端。

image 2023 12 08 11 52 18 449
Figure 1. 图4-1 集群病毒扫描架构图

本次实践通过 ClamdNetworkSocket() 方法实现与业务服务器建立扫描 socket 连接,再通过启动不同扫描方式实施病毒扫描并返回结果。实现代码如下:

pyClamad/simple1.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import time
import pyclamd
from threading import Thread


class Scan(Thread):
    def __init__(self, IP, scan_type, file):
        """构造方法,参数初始化"""
        Thread.__init__(self)
        self.IP = IP
        self.scan_type = scan_type
        self.file = file
        self.connstr = ""
        self.scanresult = ""

    def run(self):
        """多进程run方法"""
        try:
            cd = pyclamd.ClamdNetworkSocket(self.IP, 3310)  # 创建网络套接字连接对象
            if cd.ping():  # 探测连通性
                self.connstr = self.IP + " connection [OK]"
                cd.reload()  # 重载clamd病毒特征库,建议更新病毒库后做reload()操作
                if self.scan_type == "contscan_file":  # 选择不同的扫描模式
                    self.scanresult = "{0}\n".format(cd.contscan_file(self.file))
                elif self.scan_type == "multiscan_file":
                    self.scanresult = "{0}\n".format(cd.multiscan_file(self.file))
            elif self.scan_type == "scan_file":
                self.scanresult = "{0}\n".format(cd.scan_file(self.file))
                time.sleep(1)  # 线程挂起1秒
            else:
                self.connstr = self.IP + " ping error,exit"
                return
        except Exception as e:
            self.connstr = self.IP + " " + str(e)


IPs = ['192.168.1.21', '192.168.1.22']  # 扫描主机列表
scantype = "multiscan_file"  # 指写扫描模式,支持multiscan_file、contscan_file、scan_file
scanfile = "/data/www"  # 指定扫描路径
i = 1
threadnum = 2  # 指定启动的线程数
scanlist = []  # 存储扫描Scan类线程对象列表

for ip in IPs:

    currp = Scan(ip, scantype, scanfile)  # 创建扫描Scan类对象,参数(IP,扫描模式,扫描路径)
    scanlist.append(currp)  # 追加对象到列表

    if i % threadnum == 0 or i == len(IPs):  # 当达到指定的线程数或IP列表数后启动、退出线程
        for task in scanlist:
            task.start()  # 启动线程

        for task in scanlist:
            task.join()  # 等待所有子线程退出,并输出扫描结果
            print(task.connstr)  # 打印服务器连接信息
            print(task.scanresult)  # 打印扫描结果
        scanlist = []
    i += 1

通过 EICAR() 方法生成一个带有病毒特征的文件 /tmp/EICAR,代码如下:

void = open('/tmp/EICAR','w').write(cd.EICAR())

生成带有病毒特征的字符串内容如下,复制文件 /tmp/EICAR 到目标主机的扫描目录当中,以便进行测试。

#cat /tmp/EICAR
u'X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*'

最后,启动扫描程序,在本次实践过程中启用两个线程,可以根据目标主机数量随意修改,代码运行结果如图4-2,其中 192.168.1.21 主机没有发现病毒,192.168.1.22 主机发现了病毒测试文件 EICAR。

image 2023 12 08 12 06 38 883
Figure 2. 图4-2 集中式病毒扫描程序运行结果

参考提示