使用 Snort 和 PHP 构建一个小型网络防御系统

2024-04-23 12:32

本文主要是介绍使用 Snort 和 PHP 构建一个小型网络防御系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用 Snort 和 PHP 构建一个小型网络防御系统         developerWorks

级别: 初级

王丽辉, 硕士研究生, 四川大学计算机学院

2005 年 7 月 01 日

本文在 Linux 环境下,利用 Snort 和 Iptables 构建了一个小型网络防御系统,由PHP 页面提供了一个远程管理工具,并给出关键程序的实现和说明。

引言

Snort 是目前十分流行的轻型入侵检测系统。但是目前人们对Snort检测结果的处理大都停留在记录日志或简单通知网络管理员,由管理员进行审计再决定网络防御策略的阶段。Snort的检测结果并没有及时地用来抵御网络入侵。本文通过为Snort的报警输出模块提供一个服务监听程序的办法,及时获取Snort的报警信息并对其进行解析,根据解析结果向iptables添加相应的防火墙规则,达到实时阻止网络攻击的目的。服务程序同时监听来自PHP的管理请求,并进行相应的操作。由于服务程序独立于Snort而运行,故不会影响到Snort的运行效率。管理员可以通过PHP页面对服务程序阻塞的主机进行监控和管理,如查看当前阻塞的主机IP地址、事件发生时间、阻塞时间、阻塞原因,修改阻塞时间等。





总体思想

Snort的输出plugin为我们提供了丰富的报警输出方式:输出到文件,syslog,数据库,Unix域Socket等。其中当Snort的报警输出到Unix域Socket时,输出模块相当于一个报警的客户端,Snort的用户可以通过编写服务器端代码,获取Snort的报警输出消息,并根据这些消息采取相应的对策。本文基于这种思想,并利用Linux下自带的防火墙Iptables,构建了一个网络防御系统,系统总体结构如下:



服务处理程序是该系统的核心部分,包括报警输出处理子程序(AO_Handler)和PHP请求处理子程序(Web_Handler)两个部分。报警输出处理子程序主要负责接收Snort报警,解析报警信息,并通过向iptables加规则的方式对攻击主机进行阻塞;而PHP请求处理子程序主要负责与PHP管理页面通信,并根据页面的请求做出相应的处理。

服务程序中维护了一个规则相关信息表,这个表以队列(报警队列和阻塞队列)的数据结构进行组织,其中报警队列中记录的是经Snort报警输出解析得到的攻击主机信息(即报警结点,由AO_Handler生成), 而阻塞队列中记录了正在被阻塞的攻击主机信息(即阻塞结点)。如果报警结点不在阻塞队列中,程序将对这个结点所代表的主机进行阻塞操作,并把该结点加入到阻塞队列中。对每个阻塞操作所对应的阻塞结点设定一个有效期,当阻塞时间超过这个有效期时,程序将该阻塞结点从阻塞队列中删除,并删除iptables中对应的阻塞规则,对该阻塞主机放行。采用两个列队来维护报警和阻塞信息可以防止程序在多次收到有关同一个主机地址的报警时,添加重复的阻塞规则而为规则管理和取消阻塞操作时所带来的混乱。另外阻塞信息的维护使得程序可以为终端用户提供当前被系统自动阻塞的主机的所有信息:报警时间,阻塞时间,阻塞原因等,为用户了解和管理这些信息提供了接口。





回页首


具体实现

服务程序实现方式

服务程序需要同时处理三种事件:监听Snort报警输出、维护规则相关信息表(即报警队列和阻塞队列)、处理来自PHP页面的请求。因此,服务程序采用多线程并发模式。由主线程监听来自Snort的报警数据,解析该数据、创建报警结点并将其加入报警队列,另外主线程还执行服务程序的初始化工作;由两个线程来专门维护规则相关信息表,一个线程(AlertHandler)用于检测报警队列,当其中有符合要求的报警结点时,将其加入阻塞队列,执行阻塞操作,另一个(BlockHanlder)用于监测阻塞队列,将其中到达阻塞有效期的阻塞节点删除;对于来自PHP页面的请求,服务程序采用建立一个线程池的办法,对PHP请求做出相应操作,如:列表阻塞主机,删除某阻塞主机,修改阻塞有效时间等。

主线程和规则维护线程构成了报警输出处理子程序, 而PHP请求处理线程构成了PHP请求处理子程序。由于这些线程都涉及到对规则相关信息表的读写操作,因此,需要一定的同步机制来保证操作的正确性。本文中的程序采用了建立互斥锁和条件变量的办法来达到这个目的。程序主要数据结构默认情况下,Snort的报警套接字以数据报方式向path为/var/log/snort_alert的Unix域套接字发送报警数据,报警数据被封装在一个Alertpkt的结构体中(在Snort源码包的Spo_alert_unixsock.h中定义),Alertpkt的定义如下:


typedef struct _Alertpkt
{
u_int8_t alertmsg[ALERTMSG_LENGTH]; /* 报警消息 */
struct pcap_pkthdr pkth;
u_int32_t dlthdr;       /* datalink header offset. (ethernet, etc.. ) */
u_int32_t nethdr;       /* network header offset. (ip etc...) */
u_int32_t transhdr;     /* transport header offset (tcp/udp/icmp ..) */
u_int32_t data;
u_int32_t val;  /*指出有效的字段*/
……
Event event; /* 报警事件的相关信息 */
} Alertpkt;

报警/阻塞结点HNInfo、报警队列和阻塞队列的数据结构定义如下:


typedef struct _HNInfo
{
int event_id; // 结点ID
char blockIP[16]; //阻塞主机的IP地址
time_t timestamp; // 阻塞开始时间
int blocktime; // 主机阻塞时间
char msg[500]; // 阻塞原因
char desIP[16]; // 引起报警的包的目的地址
char protocol[5]; // tcp, udp or icmp
uint16_t srcPort; // 源端口
uint16_t desPort; // 目的端口
struct _HNInfo* next; // 指向下一个结点
} HNInfo; // 队列里的结点结构
typedef struct  _HNQueue
{
struct _HNInfo  *head, *tail;
int count;
} AlertQ; // 报警队列
typedef struct AlertQ BlockQ; // 阻塞队列

报警输出处理子程序

报警输出子程序(AO_Handler)由主线程和规则信息表维护线程组成。AO_Handler在/var/log/snort_alert上建立监听套接字与Snort的报警输出模块通信,它的处理流程如下:

1) 接收Snort的报警输出,根据Alertpkt的内容,解析出引发报警的源地址,报警内容,报警事件ID号等必要信息,并将其连同报警发生的时间、需要阻塞的时间等信息并形成报警结点放入报警队列;

2) 检测报警队列,当其不为空时,取出报警结点并与阻塞队列中的结点进行比较,如果报警的主机地址已出现在阻塞队列中,说明该主机已经被阻塞,只需修改阻塞队列中该主机的报警时间戳,使其与现在的报警结点的时间戳相等;如果报警主机未出现,则将报警结点加入阻塞队列,并执行阻塞操作。

3) 检测阻塞队列,当发现主机的阻塞时间已到时,从阻塞队列中删除该主机结点,且取消对该主机的阻塞。报警输出处理子程序的主要代码和相关全局变量定义如下:


int sockfd; // 监听Snort的报警消息
int btime = (1 << 29) - 1; // 默认的阻塞时间
struct _HNQueue AQ; // 报警队列
struct _HNQueue BQ; // 阻塞队列
// 线程同步所需mutex和条件变量
pthread_mutex_t AQ_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t BQ_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t AQ_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t BQ_cond = PTHREAD_COND_INITIALIZER;
pthread_cond_t BQ_timedcond = PTHREAD_COND_INITIALIZER;
pthread_t mtid; // 记录主线程ID
Int main (int argc,char** argv)
{
struct sockaddr_un snortaddr;
struct sockaddr_un bogus;
Alertpkt alert;
Packet *p = NULL;
int recv;
socklen_t len = sizeof (struct sockaddr_un);
HNInfo *cur, *node, *pivot;
pthread_t tidA, tidB;
if(argc == 2 && !strcmp(argv[1],"-D")) 
daemon_init(); // 以后台方式运行
//程序初始化工作,包括AQ、BQ的初始化,建立php请求监听端口和建立php请求处理线程池。
goInit();
// 捕获用户终止或kill消息并进行清理操作
signal(SIGINT, sig_int);
signal(SIGTERM, sig_term);
// 建立维护报警队列和阻塞队列的两个线程
pthread_create(&tidA, NULL, alertHandler, NULL);
pthread_create(&tidB, NULL, blockHandler, NULL);
mtid = pthread_self(); // 记录主线程ID
// 使用UDP套接字接收Snort的报警消息
if ((sockfd = socket (AF_UNIX, SOCK_DGRAM, 0)) < 0)
{
perror ("socket");
exit (1);
}
unlink(UNSOCK_FILE);
bzero (&snortaddr, sizeof (snortaddr));
snortaddr.sun_family = AF_UNIX; // 设置为Unix域套接字
strcpy (snortaddr.sun_path, UNSOCK_FILE); // 将path设为UNSOCK_FILE
if (bind (sockfd, (struct sockaddr *) &snortaddr, sizeof (snortaddr)) < 0)
{
perror ("bind");
exit (1);
}
// 接收Snort的报警数据
while ((recv = recvfrom (sockfd, (void *) &alert, sizeof (alert),
0, (struct sockaddr *) &bogus, &len)) > 0)
{
p = ParsePacket(&alert); // 解析报警信息
if(p != NULL)
{
node = MakeNode(&alert, p);  // 建立报警信息结点
if(node != NULL)
{
PushQueue(node); // 将报警信息加入报警队列
}
free(p);
p = NULL;
}
}
exitClean(); // 清理工作
return 0;
}

主线程接收到报警数据并解析后,由MakeNode函数生成报警信息并由PushQueue函数加入报警队列:


HNInfo * MakeNode(Alertpkt* alert, Packet *p)
{
分配新结点node;
strcpy(node->blockIP , (char*) inet_ntoa (p->iph->ip_src)); // 解析源IP地址
strcpy(node->desIP, (char*) inet_ntoa(p->iph->ip_dst)); // 解析目的IP地址
node->timestamp = time(NULL); // 结点生成时间
node->blocktime = btime; // 阻塞时间
node->event_id = alert->event.event_id; // 事件ID号
strcpy(node->msg, alert->alertmsg); // 报警原因
// 解析报警协议及端口号
if (!(alert->val & NOPACKET_STRUCT))
if (p->iph && (p->tcph || p->udph || p->icmph))
{
switch (p->iph->ip_proto)
{
case IPPROTO_TCP:
strcpy(node->protocol, "TCP");
node->srcPort = ntohs (p->tcph->th_sport);
node->desPort = ntohs (p->tcph->th_dport);
break;
case IPPROTO_UDP:
strcpy(node->protocol, "UDP");
node->srcPort = ntohs (p->udph->uh_sport);
node->desPort = ntohs (p->udph->uh_dport);
break;
case IPPROTO_ICMP:
strcpy(node->protocol, "ICMP");
break;
}
}
return node;
}
void PushQueue(HNInfo* node)
{
pthread_mutex_lock(&AQ_mutex);
加入报警信息结点;
// 通知报警信息处理线程,有新结点加入
pthread_cond_signal(&AQ_cond);
pthread_mutex_unlock(&AQ_mutex);
}

AlertHandler检测报警队列AQ,当队列中有结点时,则将其取出,与BQ中的结点比较,若BQ中没有相应主机的结点,则在BQ中添加该结点,否则只是修改BQ中相应结点的时间戳和报警事件原因。检测时使用条件变量,当AQ不为空时,再进行操作,否则程序进入睡眠,这比轮询的方式节省CPU资源。


void *alertHandler(void * vptr)
{
HNInfo* cur, *pivot, *tmp;
char cmd[200];
while(1)
{
pthread_mutex_lock(&AQ_mutex);
// 直到AQ不为空,线程被唤醒
while(AQ.count == 0)
pthread_cond_wait(&AQ_cond, &AQ_mutex);
cur = AQ.head;
while(cur != NULL)
{
// 检验BQ中是否含相同主机结点
pthread_mutex_lock(&BQ_mutex);
if(BQ.count) // BQ不为空
{
cur = PopQueue(&AQ);
if(找到相应结点)修改BQ中对应结点的值并释放cur;
else{// 结点未找到,将其添加入BQ并添加防火墙规则	
结点加入BQ;
snprintf(cmd,sizeof(cmd),"/sbin/iptables -I INPUT 
-s %s -j DROP",cur->blockIP);
system(cmd); // 添加防火墙规则
}
}
// BQ为空,只需添加结点、防火墙规则,并通知阻塞队列维护线程,BQ中有新结点产生
else 
{
结点加入BQ并添加防火墙规则 ;
//通知阻塞队列维护线程
pthread_cond_signal(&BQ_cond);
}
pthread_mutex_unlock(&BQ_mutex);
cur = AQ.head;
}
pthread_mutex_unlock(&AQ_mutex);
}
}
// blockHandler检测阻塞主机队列,一旦某个主机阻塞时间已到,
则从阻塞队列中删除,并// 将对应的阻塞主机放行
void *blockHandler(void * vptr)
{
HNInfo* pivot, *cur;
char cmd[200];
time_t min, tmp;
while(1)
{
pthread_mutex_lock(&BQ_mutex);
while(BQ.count == 0) // 若BQ为空,线程休眠
pthread_cond_wait(&BQ_cond, &BQ_mutex);
pivot = BQ.head;
cur = BQ.head;
while(cur != NULL)
{
tmp = cur->timestamp + cur->blocktime ;
if(cur == BQ.head)min = tmp;
else min =  min > tmp ? tmp : min;
if(tmp <= time(NULL)) 时间到删除结点及防火墙规则;
else
{
pivot = cur;
cur = cur->next;
}
}
// 当肯定没有主机阻塞时间到时,线程休眠。
struct timespec ts;
ts.tv_sec = min;
ts.tv_nsec = 0;
pthread_cond_timedwait(&BQ_timedcond, &BQ_mutex, &ts);
pthread_mutex_unlock(&BQ_mutex);
}
}

PHP请求处理子程序

用户通过PHP页面对阻塞的主机进行管理操作,主要的操作有:

1) 实时查看阻塞主机信息;

2) 修改某阻塞主机的阻塞时间;

3) 将某主机从阻塞列表中删除。

此时,PHP服务器端程序需要和服务程序交互,由于交互过程比较简单,故采用问答方式,通信协议如下:

Client: command[&event_id[&blocktime]]
Server: cont&event_id&blockip&timestamp&blocktime&msg&desIP&protocol&srcport&desport#
或over&result#

客户端发出的消息分为查询,删除,修改三种情况,对应command值为query、del、mod,当command为del时,需要填入event_id的值,而当command为mod时,需要加入event_id和blocktime的值,各个字段之间以&分隔。

服务器端的回应消息主要分两种,一种针对query,一种针对del和mod。对del和mod,只是简单返回执行结果,即over&result#,result为ok或err;而对query,需要返回所有阻塞主机的信息,每条信息放在一个'cont'开头的串中,各主机信息以'#'分隔,而各字段信息以'&'分隔。当所有的信息传送完毕时,发送一个over&result#消息。

PHP请求处理子程序主要代码如下:


int listenfd; // 处理PHP请求的监听端口
pthread_t THREADPOOL[50]; // 处理php请求的线程池
pthread_mutex_t WEB_mutex = PTHREAD_MUTEX_INITIALIZER;
webHandler线程处理来自php的请求,并对阻塞队列进行相应操作。
void* webHandler(void *vptr)
{
int connfd; // 与php通信端口
…
while(1)
{
pthread_mutex_lock(&WEB_mutex);
// 监听来自php的请求并建立通信连接
connfd = accept(listenfd,  &cliaddr, &clilen);
pthread_mutex_unlock(&WEB_mutex);
memset(buf, 0, sizeof(buf));
if((n = read(connfd, buf, sizeof(buf))) == 0)continue; // 读取php请求
if(strlen(buf) == 0)continue;
解析客户端请求;
if(查询BQ状态)
{
pthread_mutex_lock(&BQ_mutex);
读取并发送BQ信息;
pthread_mutex_unlock(&BQ_mutex);
}
else if(删除或修改BQ中某主机信息)
{
pthread_mutex_lock(&BQ_mutex);
删除或修改主机信息和防火墙规则,并返回执行结果;
pthread_mutex_unlock(&BQ_mutex);
}
else 发送出错消息;
close(connfd);
}
}

Php管理页面使用socket向服务程序发送请求,并显示相应结果。 PHP管理页面的查询和修改代码如下:


function DoQuery(&$errno)
{
$servSocket = OpenAlertSock(); // 打开通信端口
$buf = "query";
socket_write($servSocket, $buf, strlen($buf));
while ($out = socket_read($servSocket, 2048)) 
{
$result = $result.$out;
}
socket_close($servSocket);
解析阻塞主机信息,并将其存入数组返回。
}
function DoModify($cmd)
{
$servSocket = OpenAlertSock();
socket_write($servSocket, $cmd, strlen($cmd)); // 发送删除或修改请求
while ($out = socket_read($servSocket, 2048)) 
{
$result = $result.$out;
}
socket_close($servSocket);
解析操作结果并返回。
}





结束语

本文利用Snort和php构建了一个小型的网络防御系统,由于采用了独立的服务程序,防御系统对Snort和iptables的运行效率影响很小。





参考资料

  • Snort官方网站 http://www.snort.org

  • Snort用户手册http://www.snort.org/docs/snort_htmanuals/htmanual_232

  • Snort源码包snort-2.3.2.tar.gz http://www.snort.org/dl/

  • W.Richard Stevens,施振川 周利民 孙宏晖等译,UNIX 网络编程(第2版)第1卷:套接口API和X/Open 传输接口API





关于作者

王丽辉 四川大学计算机学院硕士研究生,主要从事网络安全方面的研究,目前正在研究和开发防火墙和IDS的相关产品,可以通过gailya@sohu.com联系。

这篇关于使用 Snort 和 PHP 构建一个小型网络防御系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/928795

相关文章

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听