从零开始构建HIDS主机入侵检测系统:Python Flask全栈开发实战
引言
在当今网络安全威胁日益复杂的环境下,主机入侵检测系统(HIDS)已成为企业安全防护体系中不可或缺的一环。本文将详细介绍如何使用Python Flask框架构建一个功能完整的HIDS系统,涵盖文件完整性监控、网络连接监控、进程监控和智能报警等核心功能。
通过本文的学习,你将掌握:
- Python Flask Web应用开发
- SQLite数据库设计与操作
- 系统监控技术实现
- 前端可视化开发
- 前后端数据交互
- 安全监控算法设计
项目概述
什么是HIDS?
HIDS(Host-based Intrusion Detection System)主机入侵检测系统是一种安装在主机上的安全软件,通过监控主机的文件系统、网络连接、进程行为等来检测入侵行为。与网络入侵检测系统(NIDS)不同,HIDS专注于单个主机的安全状态监控。
系统架构
我们的HIDS系统采用经典的Web应用架构:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Web浏览器 │◄──►│ Flask后端 │◄──►│ SQLite数据库 │
│ Bootstrap │ │ Python │ │ 数据存储 │
│ Charts.js │ │ psutil │ │ 监控数据 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
核心功能模块
- 文件完整性监控模块:实时监控指定目录的文件变化
- 网络连接监控模块:监控主机的网络连接状态
- 进程监控模块:监控系统进程的运行状态
- 智能报警模块:基于规则引擎生成安全报警
- Web管理界面:提供友好的可视化操作界面
开发环境准备
技术栈选择
后端技术栈:
- Python 3.7+:主要开发语言
- Flask 2.0+:Web框架
- SQLite 3:轻量级数据库
- psutil 5.8+:系统监控库
- threading:多线程处理
前端技术栈:
- HTML5 + CSS3:页面结构和样式
- JavaScript ES6+:交互逻辑
- Bootstrap 5:UI框架
- Charts.js:数据可视化
- Ajax:异步数据交互
环境配置
创建项目目录结构:
hids/
├── app.py # Flask主应用
├── database.py # 数据库模块
├── monitor.py # 监控核心模块
├── run.py # 应用启动脚本
├── config.yaml # 配置文件
├── requirements.txt # 依赖包列表
├── static/ # 静态资源
│ ├── css/
│ │ └── style.css
│ └── js/
│ └── app.js
├── templates/ # HTML模板
│ └── index.html
├── data/ # 数据目录
│ └── hids.db
└── logs/ # 日志目录
└── hids.log
安装依赖包:
bash
pip install flask psutil pyyaml sqlite3
requirements.txt内容:
Flask==2.3.2
psutil==5.9.5
PyYAML==6.0
Werkzeug==2.3.6
数据库设计实现
数据库架构设计
良好的数据库设计是HIDS系统的基础。我们需要设计三个核心数据表来存储不同类型的监控数据。
创建database.py文件:
python
import sqlite3
import logging
from datetime import datetime
import os
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/hids.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
def get_db_connection():
"""获取数据库连接"""
try:
# 确保data目录存在
os.makedirs('data', exist_ok=True)
conn = sqlite3.connect('data/hids.db', check_same_thread=False)
conn.row_factory = sqlite3.Row
return conn
except Exception as e:
logger.error(f"数据库连接错误: {e}")
raise
def init_database():
"""初始化数据库表结构"""
conn = get_db_connection()
cursor = conn.cursor()
try:
# 网络连接表
cursor.execute('''
CREATE TABLE IF NOT EXISTS network_connections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
local_address TEXT,
local_port INTEGER,
remote_address TEXT,
remote_port INTEGER,
process_name TEXT,
protocol INTEGER,
connection_state TEXT,
established_time TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'normal'
)
''')
# 文件完整性表
cursor.execute('''
CREATE TABLE IF NOT EXISTS file_integrity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_path TEXT UNIQUE,
file_hash TEXT,
file_size INTEGER,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'normal'
)
''')
# 报警表
cursor.execute('''
CREATE TABLE IF NOT EXISTS alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alert_type TEXT,
severity TEXT,
title TEXT,
description TEXT,
source_ip TEXT,
source_process TEXT,
target_file TEXT,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
resolved BOOLEAN DEFAULT 0
)
''')
# 创建索引优化查询性能
cursor.execute('CREATE INDEX IF NOT EXISTS idx_network_created ON network_connections(created_at)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_file_path ON file_integrity(file_path)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_alert_created ON alerts(created_at)')
cursor.execute('CREATE INDEX IF NOT EXISTS idx_alert_resolved ON alerts(resolved)')
conn.commit()
logger.info("数据库初始化完成")
except Exception as e:
logger.error(f"数据库初始化错误: {e}")
conn.rollback()
raise
finally:
conn.close()
def reset_database():
"""重置数据库"""
try:
if os.path.exists('data/hids.db'):
os.remove('data/hids.db')
logger.info("数据库文件已删除")
init_database()
logger.info("数据库重置完成")
except Exception as e:
logger.error(f"数据库重置错误: {e}")
raise
if __name__ == '__main__':
init_database()
数据库连接池优化
为了提高数据库访问性能,我们实现连接池管理:
python
class DatabasePool:
"""数据库连接池"""
def __init__(self, max_connections=10):
self.max_connections = max_connections
self.connections = []
self.in_use = set()
def get_connection(self):
"""获取数据库连接"""
if self.connections:
conn = self.connections.pop()
self.in_use.add(id(conn))
return conn
else:
conn = get_db_connection()
self.in_use.add(id(conn))
return conn
def return_connection(self, conn):
"""归还数据库连接"""
conn_id = id(conn)
if conn_id in self.in_use:
self.in_use.remove(conn_id)
if len(self.connections) < self.max_connections:
self.connections.append(conn)
else:
conn.close()
def close_all(self):
"""关闭所有连接"""
for conn in self.connections:
conn.close()
self.connections.clear()
self.in_use.clear()
# 全局连接池实例
db_pool = DatabasePool()
监控核心模块开发
网络连接监控实现
网络连接监控是HIDS的核心功能之一。我们使用psutil库获取系统网络连接信息:
python
import psutil
import socket
import threading
import time
from datetime import datetime
import logging
from database import get_db_connection, db_pool
logger = logging.getLogger(__name__)
class HIDSMonitor:
def __init__(self, config=None):
self.config = config or {}
self.monitoring = False
self.monitor_threads = {}
# 协议映射
self.protocol_map = {
socket.SOCK_STREAM: 1, # TCP
socket.SOCK_DGRAM: 2, # UDP
}
# 连接状态映射
self.state_map = {
'ESTABLISHED': '已建立',
'SYN_SENT': '同步已发送',
'SYN_RECV': '同步已接收',
'FIN_WAIT1': '结束等待1',
'FIN_WAIT2': '结束等待2',
'TIME_WAIT': '时间等待',
'CLOSE': '已关闭',
'CLOSE_WAIT': '关闭等待',
'LAST_ACK': '最后确认',
'LISTEN': '监听中',
'CLOSING': '正在关闭',
'NONE': '无状态'
}
def monitor_network_connections(self):
"""监控网络连接"""
logger.info("网络连接监控线程启动")
while self.monitoring:
try:
conn = None
conn = db_pool.get_connection()
cursor = conn.cursor()
# 获取当前网络连接
connections = psutil.net_connections()
for conn_item in connections:
try:
# 提取连接信息
local_addr = ""
local_port = 0
if hasattr(conn_item, 'laddr') and conn_item.laddr:
if len(conn_item.laddr) >= 2:
local_addr = str(conn_item.laddr[0])
local_port = int(conn_item.laddr[1])
remote_addr = ""
remote_port = 0
if hasattr(conn_item, 'raddr') and conn_item.raddr:
if len(conn_item.raddr) >= 2:
remote_addr = str(conn_item.raddr[0])
remote_port = int(conn_item.raddr[1])
# 获取进程名
process_name = "Unknown"
try:
if conn_item.pid:
process = psutil.Process(conn_item.pid)
process_name = process.name()
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
# 协议识别
protocol = self.protocol_map.get(conn_item.type, 0)
# 状态转换
connection_state = conn_item.status or 'NONE'
# 建立时间
established_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 检查是否已存在相同连接
cursor.execute('''
SELECT id FROM network_connections
WHERE local_address = ? AND local_port = ?
AND remote_address = ? AND remote_port = ?
AND process_name = ?
ORDER BY created_at DESC LIMIT 1
''', (local_addr, local_port, remote_addr, remote_port, process_name))
existing = cursor.fetchone()
if not existing:
# 插入新连接记录
cursor.execute('''
INSERT INTO network_connections
(local_address, local_port, remote_address, remote_port,
process_name, protocol, connection_state, established_time)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (local_addr, local_port, remote_addr, remote_port,
process_name, protocol, connection_state, established_time))
except Exception as e:
logger.warning(f"处理网络连接时出错: {e}")
continue
conn.commit()
logger.debug(f"网络连接监控完成,处理了 {len(connections)} 个连接")
except Exception as e:
logger.error(f"网络连接监控错误: {e}")
if conn:
conn.rollback()
finally:
if conn:
db_pool.return_connection(conn)
# 等待下次扫描
time.sleep(self.config.get('network_scan_interval', 60))
def monitor_file_integrity(self):
"""监控文件完整性"""
logger.info("文件完整性监控线程启动")
monitored_dirs = self.config.get('monitored_directories', [
'C:\\Windows\\System32',
'C:\\Program Files'
])
while self.monitoring:
try:
for directory in monitored_dirs:
if os.path.exists(directory):
self.scan_directory(directory)
logger.debug("文件完整性扫描完成")
except Exception as e:
logger.error(f"文件完整性监控错误: {e}")
# 等待下次扫描
time.sleep(self.config.get('file_scan_interval', 300))
def scan_directory(self, directory):
"""扫描目录"""
conn = None
try:
conn = db_pool.get_connection()
cursor = conn.cursor()
for root, dirs, files in os.walk(directory):
for file in files:
file_path = os.path.join(root, file)
try:
# 计算文件哈希
file_hash = self.calculate_file_hash(file_path)
file_size = os.path.getsize(file_path)
# 检查文件是否已存在
cursor.execute('''
SELECT * FROM file_integrity WHERE file_path = ?
''', (file_path,))
existing = cursor.fetchone()
if existing:
# 检查文件是否发生变化
if existing['file_hash'] != file_hash:
cursor.execute('''
UPDATE file_integrity
SET file_hash = ?, file_size = ?,
updated_at = ?, status = 'changed'
WHERE file_path = ?
''', (file_hash, file_size,
datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
file_path))
# 生成报警
self.create_alert(
'file_integrity',
'MEDIUM',
'文件被修改',
f'文件 {file_path} 内容发生变化',
target_file=file_path
)
logger.warning(f"文件被修改: {file_path}")
else:
# 插入新文件记录
cursor.execute('''
INSERT INTO file_integrity
(file_path, file_hash, file_size)
VALUES (?, ?, ?)
''', (file_path, file_hash, file_size))
logger.info(f"发现新文件: {file_path}")
except Exception as e:
logger.warning(f"扫描文件错误 {file_path}: {e}")
continue
conn.commit()
except Exception as e:
logger.error(f"目录扫描错误 {directory}: {e}")
if conn:
conn.rollback()
finally:
if conn:
db_pool.return_connection(conn)
def calculate_file_hash(self, file_path):
"""计算文件SHA-256哈希值"""
try:
sha256_hash = hashlib.sha256()
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
return sha256_hash.hexdigest()
except Exception as e:
logger.error(f"计算文件哈希错误 {file_path}: {e}")
return ""
def monitor_processes(self):
"""监控进程"""
logger.info("进程监控线程启动")
while self.monitoring:
try:
processes = []
for proc in psutil.process_iter([
'pid', 'name', 'cpu_percent', 'memory_percent',
'create_time', 'username', 'cmdline'
]):
try:
proc_info = proc.info
processes.append(proc_info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
# 检测可疑进程
suspicious_processes = self.detect_suspicious_processes(processes)
for process in suspicious_processes:
self.create_alert(
'process',
'HIGH',
'检测到可疑进程',
f'发现可疑进程: {process["name"]} (PID: {process["pid"]})',
source_process=process['name']
)
logger.debug(f"进程监控完成,扫描了 {len(processes)} 个进程")
except Exception as e:
logger.error(f"进程监控错误: {e}")
# 等待下次扫描
time.sleep(self.config.get('process_scan_interval', 30))
def detect_suspicious_processes(self, processes):
"""检测可疑进程"""
suspicious = []
# 简单的可疑进程检测规则
suspicious_names = [
'mimikatz', 'pwdump', 'hashdump', 'meterpreter',
'backdoor', 'trojan', 'keylogger', 'rootkit'
]
for process in processes:
process_name = process.get('name', '').lower()
# 检查进程名是否包含可疑关键词
for suspicious_name in suspicious_names:
if suspicious_name in process_name:
suspicious.append(process)
break
# 检查CPU使用率异常
cpu_percent = process.get('cpu_percent', 0)
if cpu_percent > 80:
suspicious.append(process)
# 检查内存使用率异常
memory_percent = process.get('memory_percent', 0)
if memory_percent > 80:
suspicious.append(process)
return suspicious
def create_alert(self, alert_type, severity, title, description,
source_ip=None, source_process=None, target_file=None):
"""创建报警记录"""
conn = None
try:
conn = db_pool.get_connection()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO alerts
(alert_type, severity, title, description,
source_ip, source_process, target_file)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (alert_type, severity, title, description,
source_ip, source_process, target_file))
conn.commit()
logger.warning(f"生成报警: {title} - {description}")
except Exception as e:
logger.error(f"创建报警错误: {e}")
if conn:
conn.rollback()
finally:
if conn:
db_pool.return_connection(conn)
def start_monitoring(self):
"""启动所有监控模块"""
if self.monitoring:
logger.warning("监控已在运行中")
return
self.monitoring = True
logger.info("启动HIDS监控")
# 启动网络监控线程
network_thread = threading.Thread(
target=self.monitor_network_connections,
name="NetworkMonitor"
)
network_thread.daemon = True
network_thread.start()
self.monitor_threads['network'] = network_thread
# 启动文件监控线程
file_thread = threading.Thread(
target=self.monitor_file_integrity,
name="FileMonitor"
)
file_thread.daemon = True
file_thread.start()
self.monitor_threads['file'] = file_thread
# 启动进程监控线程
process_thread = threading.Thread(
target=self.monitor_processes,
name="ProcessMonitor"
)
process_thread.daemon = True
process_thread.start()
self.monitor_threads['process'] = process_thread
logger.info("所有监控模块已启动")
def stop_monitoring(self):
"""停止所有监控模块"""
self.monitoring = False
logger.info("停止HIDS监控")
# 等待所有线程结束
for name, thread in self.monitor_threads.items():
if thread.is_alive():
thread.join(timeout=5)
logger.info(f"{name}监控线程已停止")
self.monitor_threads.clear()
logger.info("所有监控模块已停止")
def get_monitoring_status(self):
"""获取监控状态"""
return {
'monitoring': self.monitoring,
'threads': {
name: thread.is_alive()
for name, thread in self.monitor_threads.items()
}
}
Flask Web应用开发
主应用架构设计
现在我们来开发Flask主应用,提供RESTful API接口和Web界面:
python
from flask import Flask, render_template, jsonify, request
import sqlite3
import logging
from datetime import datetime
from monitor import HIDSMonitor
from database import get_db_connection, init_database
import yaml
import os
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/hids.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# 创建Flask应用
app = Flask(__name__)
app.config['SECRET_KEY'] = 'hids-secret-key-2024'
# 全局监控器实例
monitor = None
# 配置文件路径
CONFIG_FILE = 'config.yaml'
def load_config():
"""加载配置文件"""
try:
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
else:
# 默认配置
return {
'monitoring': {
'file_integrity': {
'enabled': True,
'scan_interval': 300,
'monitored_directories': [
'C:\\Windows\\System32',
'C:\\Program Files'
]
},
'network': {
'enabled': True,
'scan_interval': 60,
'max_connections': 1000
},
'process': {
'enabled': True,
'scan_interval': 30
}
},
'web': {
'host': '0.0.0.0',
'port': 5000,
'debug': False
}
}
except Exception as e:
logger.error(f"加载配置错误: {e}")
return {}
@app.route('/')
def index():
"""主页"""
return render_template('index.html')
@app.route('/api/stats')
def get_stats():
"""获取统计信息"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# 获取网络连接统计
cursor.execute('SELECT COUNT(*) as total FROM network_connections')
network_total = cursor.fetchone()['total']
cursor.execute('SELECT COUNT(*) as tcp FROM network_connections WHERE protocol = 1')
tcp_count = cursor.fetchone()['tcp']
cursor.execute('SELECT COUNT(*) as udp FROM network_connections WHERE protocol = 2')
udp_count = cursor.fetchone()['udp']
# 获取文件完整性统计
cursor.execute('SELECT COUNT(*) as total FROM file_integrity')
file_total = cursor.fetchone()['total']
cursor.execute('SELECT COUNT(*) as changed FROM file_integrity WHERE status = "changed"')
file_changed = cursor.fetchone()['changed']
# 获取报警统计
cursor.execute('SELECT COUNT(*) as total FROM alerts')
alert_total = cursor.fetchone()['total']
cursor.execute('SELECT COUNT(*) as unresolved FROM alerts WHERE resolved = 0')
alert_unresolved = cursor.fetchone()['unresolved']
# 获取进程统计(从最近的监控数据)
cursor.execute('SELECT COUNT(*) as total FROM alerts WHERE alert_type = "process"')
process_alerts = cursor.fetchone()['total']
conn.close()
return jsonify({
'network_stats': {
'total': network_total,
'tcp_count': tcp_count,
'udp_count': udp_count,
'listen_count': 0 # 可以扩展实现
},
'file_stats': {
'total': file_total,
'changed': file_changed,
'normal': file_total - file_changed
},
'alert_stats': {
'total': alert_total,
'unresolved': alert_unresolved,
'resolved': alert_total - alert_unresolved
},
'process_stats': {
'alerts': process_alerts
}
})
except Exception as e:
logger.error(f"获取统计信息错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/network')
def get_network():
"""获取网络连接数据"""
try:
limit = request.args.get('limit', 100, type=int)
protocol_filter = request.args.get('protocol', type=str)
conn = get_db_connection()
cursor = conn.cursor()
# 构建查询条件
query = '''
SELECT
id, local_address, local_port, remote_address, remote_port,
process_name, protocol, connection_state, established_time,
created_at, status
FROM network_connections
WHERE 1=1
'''
params = []
if protocol_filter:
if protocol_filter.upper() == 'TCP':
query += ' AND protocol = 1'
elif protocol_filter.upper() == 'UDP':
query += ' AND protocol = 2'
query += ' ORDER BY created_at DESC LIMIT ?'
params.append(limit)
cursor.execute(query, params)
connections = []
for row in cursor.fetchall():
connection_data = dict(row)
# 协议显示名称映射
if str(connection_data.get('protocol')) == '1':
connection_data['protocol_display'] = 'TCP'
elif str(connection_data.get('protocol')) == '2':
connection_data['protocol_display'] = 'UDP'
else:
connection_data['protocol_display'] = f"协议{connection_data.get('protocol', '未知')}"
# 连接状态显示映射
state = connection_data.get('connection_state', 'NONE')
state_display = {
'ESTABLISHED': '已建立',
'LISTEN': '监听中',
'CLOSE_WAIT': '关闭等待',
'TIME_WAIT': '时间等待',
'SYN_SENT': '同步已发送',
'SYN_RECV': '同步已接收',
'FIN_WAIT1': '结束等待1',
'FIN_WAIT2': '结束等待2',
'CLOSING': '正在关闭',
'LAST_ACK': '最后确认',
'NONE': '无状态'
}.get(state, state)
connection_data['connection_state_display'] = state_display
connections.append(connection_data)
conn.close()
# 返回标准格式,包含value和Count字段
return jsonify({
"value": connections,
"Count": len(connections)
})
except Exception as e:
logger.error(f"获取网络连接数据错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/files')
def get_files():
"""获取文件完整性数据"""
try:
limit = request.args.get('limit', 100, type=int)
status_filter = request.args.get('status', type=str)
conn = get_db_connection()
cursor = conn.cursor()
query = 'SELECT * FROM file_integrity WHERE 1=1'
params = []
if status_filter:
query += ' AND status = ?'
params.append(status_filter)
query += ' ORDER BY updated_at DESC LIMIT ?'
params.append(limit)
cursor.execute(query, params)
files = [dict(row) for row in cursor.fetchall()]
conn.close()
return jsonify(files)
except Exception as e:
logger.error(f"获取文件完整性数据错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/processes')
def get_processes():
"""获取进程数据"""
try:
limit = request.args.get('limit', 50, type=int)
conn = get_db_connection()
cursor = conn.cursor()
# 从报警表中获取进程相关报警
cursor.execute('''
SELECT * FROM alerts
WHERE alert_type = 'process'
ORDER BY created_at DESC
LIMIT ?
''', (limit,))
processes = []
for row in cursor.fetchall():
process_data = dict(row)
processes.append(process_data)
conn.close()
return jsonify(processes)
except Exception as e:
logger.error(f"获取进程数据错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/alerts')
def get_alerts():
"""获取报警数据"""
try:
limit = request.args.get('limit', 100, type=int)
severity_filter = request.args.get('severity', type=str)
resolved_filter = request.args.get('resolved', type=str)
conn = get_db_connection()
cursor = conn.cursor()
query = 'SELECT * FROM alerts WHERE 1=1'
params = []
if severity_filter:
query += ' AND severity = ?'
params.append(severity_filter)
if resolved_filter is not None:
query += ' AND resolved = ?'
params.append(1 if resolved_filter.lower() == 'true' else 0)
query += ' ORDER BY created_at DESC LIMIT ?'
params.append(limit)
cursor.execute(query, params)
alerts = [dict(row) for row in cursor.fetchall()]
conn.close()
return jsonify(alerts)
except Exception as e:
logger.error(f"获取报警数据错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/monitoring_status')
def get_monitoring_status():
"""获取监控状态"""
try:
if monitor:
status = monitor.get_monitoring_status()
return jsonify(status)
else:
return jsonify({'monitoring': False, 'threads': {}})
except Exception as e:
logger.error(f"获取监控状态错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/start_monitoring', methods=['POST'])
def start_monitoring():
"""启动监控"""
try:
if not monitor:
return jsonify({'error': '监控器未初始化'}), 500
if monitor.monitoring:
return jsonify({'message': '监控已在运行中'})
monitor.start_monitoring()
return jsonify({'message': '监控启动成功'})
except Exception as e:
logger.error(f"启动监控错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/stop_monitoring', methods=['POST'])
def stop_monitoring():
"""停止监控"""
try:
if not monitor:
return jsonify({'error': '监控器未初始化'}), 500
if not monitor.monitoring:
return jsonify({'message': '监控未运行'})
monitor.stop_monitoring()
return jsonify({'message': '监控停止成功'})
except Exception as e:
logger.error(f"停止监控错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/resolve_alert/<int:alert_id>', methods=['POST'])
def resolve_alert(alert_id):
"""标记报警为已解决"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('UPDATE alerts SET resolved = 1 WHERE id = ?', (alert_id,))
conn.commit()
affected = cursor.rowcount
conn.close()
if affected > 0:
return jsonify({'message': '报警已标记为已解决'})
else:
return jsonify({'error': '报警不存在'}), 404
except Exception as e:
logger.error(f"标记报警错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/clear_alerts', methods=['POST'])
def clear_alerts():
"""清空所有报警"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('DELETE FROM alerts')
conn.commit()
affected = cursor.rowcount
conn.close()
return jsonify({'message': f'已清空 {affected} 条报警记录'})
except Exception as e:
logger.error(f"清空报警错误: {e}")
return jsonify({'error': str(e)}), 500
@app.route('/api/delete_alert/<int:alert_id>', methods=['DELETE'])
def delete_alert(alert_id):
"""删除报警"""
try:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('DELETE FROM alerts WHERE id = ?', (alert_id,))
conn.commit()
affected = cursor.rowcount
conn.close()
if affected > 0:
return jsonify({'message': '报警已删除'})
else:
return jsonify({'error': '报警不存在'}), 404
except Exception as e:
logger.error(f"删除报警错误: {e}")
return jsonify({'error': str(e)}), 500
@app.errorhandler(404)
def not_found(error):
"""404错误处理"""
if request.path.startswith('/api/'):
return jsonify({'error': '接口不存在'}), 404
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
"""500错误处理"""
logger.error(f"服务器内部错误: {error}")
if request.path.startswith('/api/'):
return jsonify({'error': '服务器内部错误'}), 500
return render_template('500.html'), 500
def create_app():
"""应用工厂函数"""
global monitor
# 初始化数据库
init_database()
# 加载配置
config = load_config()
# 创建监控器实例
monitor_config = config.get('monitoring', {})
monitor = HIDSMonitor(monitor_config)
logger.info("HIDS应用初始化完成")
return app
if __name__ == '__main__':
# 创建应用
app = create_app()
# 获取Web配置
config = load_config()
web_config = config.get('web', {})
# 启动应用
app.run(
host=web_config.get('host', '0.0.0.0'),
port=web_config.get('port', 5000),
debug=web_config.get('debug', False),
threaded=True
)
前端界面开发
HTML模板设计
创建templates/index.html文件:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HIDS - 主机入侵检测系统</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<!-- Charts.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- 自定义CSS -->
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<i class="fas fa-shield-alt"></i> HIDS 主机入侵检测系统
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="#dashboard">仪表板</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#network">网络监控</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#files">文件监控</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#alerts">报警管理</a>
</li>
</ul>
</div>
</div>
</nav>
<div class="container-fluid mt-3">
<!-- 监控控制面板 -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-primary text-white">
<i class="fas fa-cogs"></i> 监控控制
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<div class="d-flex align-items-center">
<span class="me-3">监控状态:</span>
<span id="monitoringStatus" class="badge bg-secondary">检测中...</span>
</div>
</div>
<div class="col-md-6 text-end">
<button id="startMonitoring" class="btn btn-success btn-sm me-2">
<i class="fas fa-play"></i> 启动监控
</button>
<button id="stopMonitoring" class="btn btn-danger btn-sm me-2">
<i class="fas fa-stop"></i> 停止监控
</button>
<button id="refreshData" class="btn btn-primary btn-sm">
<i class="fas fa-sync-alt"></i> 刷新数据
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 统计概览 -->
<div class="row mb-4" id="dashboard">
<div class="col-md-3">
<div class="card text-white bg-info">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">网络连接</h6>
<h3 id="totalConnections">0</h3>
</div>
<div class="align-self-center">
<i class="fas fa-network-wired fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-warning">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">文件变化</h6>
<h3 id="fileChanges">0</h3>
</div>
<div class="align-self-center">
<i class="fas fa-file-alt fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-success">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">进程警报</h6>
<h3 id="processAlerts">0</h3>
</div>
<div class="align-self-center">
<i class="fas fa-tasks fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card text-white bg-danger">
<div class="card-body">
<div class="d-flex justify-content-between">
<div>
<h6 class="card-title">未解决报警</h6>
<h3 id="unresolvedAlerts">0</h3>
</div>
<div class="align-self-center">
<i class="fas fa-exclamation-triangle fa-2x"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="row mb-4">
<div class="col-md-4">
<div class="card">
<div class="card-header">
<i class="fas fa-chart-pie"></i> 网络连接分布
</div>
<div class="card-body">
<canvas id="networkChart" width="200" height="200"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<i class="fas fa-chart-bar"></i> 文件状态分布
</div>
<div class="card-body">
<canvas id="fileChart" width="200" height="200"></canvas>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<i class="fas fa-chart-line"></i> 报警趋势
</div>
<div class="card-body">
<canvas id="alertChart" width="200" height="200"></canvas>
</div>
</div>
</div>
</div>
<!-- 网络连接监控 -->
<div class="row mb-4" id="network">
<div class="col-12">
<div class="card">
<div class="card-header">
<i class="fas fa-network-wired"></i> 网络连接监控
<div class="float-end">
<select id="protocolFilter" class="form-select form-select-sm d-inline-block w-auto">
<option value="">所有协议</option>
<option value="TCP">TCP</option>
<option value="UDP">UDP</option>
</select>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>协议</th>
<th>本地地址</th>
<th>远程地址</th>
<th>连接状态</th>
<th>进程</th>
<th>建立时间</th>
<th>状态</th>
</tr>
</thead>
<tbody id="networkListBody">
<tr>
<td colspan="7" class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 文件完整性监控 -->
<div class="row mb-4" id="files">
<div class="col-12">
<div class="card">
<div class="card-header">
<i class="fas fa-file-alt"></i> 文件完整性监控
<div class="float-end">
<select id="fileStatusFilter" class="form-select form-select-sm d-inline-block w-auto">
<option value="">所有状态</option>
<option value="normal">正常</option>
<option value="changed">已变化</option>
</select>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>文件路径</th>
<th>文件大小</th>
<th>哈希值</th>
<th>创建时间</th>
<th>更新时间</th>
<th>状态</th>
</tr>
</thead>
<tbody id="fileListBody">
<tr>
<td colspan="6" class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- 报警管理 -->
<div class="row mb-4" id="alerts">
<div class="col-12">
<div class="card">
<div class="card-header">
<i class="fas fa-exclamation-triangle"></i> 报警管理
<div class="float-end">
<select id="severityFilter" class="form-select form-select-sm d-inline-block w-auto me-2">
<option value="">所有级别</option>
<option value="HIGH">高危</option>
<option value="MEDIUM">中危</option>
<option value="LOW">低危</option>
</select>
<select id="resolvedFilter" class="form-select form-select-sm d-inline-block w-auto me-2">
<option value="">所有状态</option>
<option value="false">未解决</option>
<option value="true">已解决</option>
</select>
<button id="clearAlerts" class="btn btn-danger btn-sm">
<i class="fas fa-trash"></i> 清空报警
</button>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>级别</th>
<th>类型</th>
<th>标题</th>
<th>描述</th>
<th>源IP</th>
<th>源进程</th>
<th>目标文件</th>
<th>创建时间</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody id="alertListBody">
<tr>
<td colspan="10" class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">加载中...</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- 自定义JavaScript -->
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>
JavaScript交互逻辑
创建static/js/app.js文件:
javascript
// HIDS前端主逻辑
class HIDSDashboard {
constructor() {
this.refreshIntervals = {};
this.charts = {};
this.init();
}
init() {
this.bindEvents();
this.loadInitialData();
this.startAutoRefresh();
}
bindEvents() {
// 监控控制按钮
document.getElementById('startMonitoring').addEventListener('click', () => this.startMonitoring());
document.getElementById('stopMonitoring').addEventListener('click', () => this.stopMonitoring());
document.getElementById('refreshData').addEventListener('click', () => this.refreshAllData());
// 过滤器
document.getElementById('protocolFilter').addEventListener('change', () => this.loadNetwork());
document.getElementById('fileStatusFilter').addEventListener('change', () => this.loadFiles());
document.getElementById('severityFilter').addEventListener('change', () => this.loadAlerts());
document.getElementById('resolvedFilter').addEventListener('change', () => this.loadAlerts());
// 报警管理
document.getElementById('clearAlerts').addEventListener('click', () => this.clearAlerts());
}
async loadInitialData() {
try {
await this.checkMonitoringStatus();
await this.loadStats();
await this.loadNetwork();
await this.loadFiles();
await this.loadAlerts();
this.initCharts();
} catch (error) {
console.error('初始化数据加载失败:', error);
this.showAlert('数据加载失败', 'error');
}
}
startAutoRefresh() {
// 设置定时刷新
this.refreshIntervals.stats = setInterval(() => this.loadStats(), 30000);
this.refreshIntervals.network = setInterval(() => this.loadNetwork(), 5000);
this.refreshIntervals.files = setInterval(() => this.loadFiles(), 60000);
this.refreshIntervals.alerts = setInterval(() => this.loadAlerts(), 15000);
this.refreshIntervals.monitoring = setInterval(() => this.checkMonitoringStatus(), 10000);
}
async checkMonitoringStatus() {
try {
const response = await fetch('/api/monitoring_status');
const data = await response.json();
const statusElement = document.getElementById('monitoringStatus');
if (data.monitoring) {
statusElement.className = 'badge bg-success';
statusElement.textContent = '监控运行中';
// 更新按钮状态
document.getElementById('startMonitoring').disabled = true;
document.getElementById('stopMonitoring').disabled = false;
} else {
statusElement.className = 'badge bg-danger';
statusElement.textContent = '监控已停止';
// 更新按钮状态
document.getElementById('startMonitoring').disabled = false;
document.getElementById('stopMonitoring').disabled = true;
}
} catch (error) {
console.error('检查监控状态失败:', error);
const statusElement = document.getElementById('monitoringStatus');
statusElement.className = 'badge bg-secondary';
statusElement.textContent = '状态未知';
}
}
async startMonitoring() {
try {
const response = await fetch('/api/start_monitoring', {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
this.showAlert('监控启动成功', 'success');
await this.checkMonitoringStatus();
} else {
this.showAlert(data.error || '启动监控失败', 'error');
}
} catch (error) {
console.error('启动监控失败:', error);
this.showAlert('启动监控失败', 'error');
}
}
async stopMonitoring() {
try {
const response = await fetch('/api/stop_monitoring', {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
this.showAlert('监控停止成功', 'success');
await this.checkMonitoringStatus();
} else {
this.showAlert(data.error || '停止监控失败', 'error');
}
} catch (error) {
console.error('停止监控失败:', error);
this.showAlert('停止监控失败', 'error');
}
}
async loadStats() {
try {
const response = await fetch('/api/stats');
const data = await response.json();
// 更新统计数字
document.getElementById('totalConnections').textContent =
data.network_stats?.total || 0;
document.getElementById('fileChanges').textContent =
data.file_stats?.changed || 0;
document.getElementById('processAlerts').textContent =
data.process_stats?.alerts || 0;
document.getElementById('unresolvedAlerts').textContent =
data.alert_stats?.unresolved || 0;
// 更新图表
this.updateCharts(data);
} catch (error) {
console.error('加载统计数据失败:', error);
}
}
async loadNetwork() {
try {
const protocolFilter = document.getElementById('protocolFilter').value;
const params = new URLSearchParams({
limit: 50
});
if (protocolFilter) {
params.append('protocol', protocolFilter);
}
const response = await fetch(`/api/network?${params}`);
const data = await response.json();
const tbody = document.getElementById('networkListBody');
if (data.error) {
tbody.innerHTML = `<tr><td colspan="7" class="text-center text-danger">${data.error}</td></tr>`;
return;
}
const connections = data.value || data;
if (connections.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" class="text-center text-muted">暂无网络连接数据</td></tr>';
return;
}
tbody.innerHTML = connections.map(conn => `
<tr>
<td><span class="badge bg-${conn.protocol_display === 'TCP' ? 'primary' : 'success'}">${conn.protocol_display}</span></td>
<td>${conn.local_address}:${conn.local_port}</td>
<td>${conn.remote_address}:${conn.remote_port}</td>
<td>${conn.connection_state_display}</td>
<td>${conn.process_name || 'N/A'}</td>
<td>${this.formatDateTime(conn.established_time)}</td>
<td>
<span class="badge bg-${this.getConnectionStatusClass(conn.status)}">
${this.getConnectionStatusText(conn.status)}
</span>
</td>
</tr>
`).join('');
} catch (error) {
console.error('加载网络连接数据失败:', error);
document.getElementById('networkListBody').innerHTML =
'<tr><td colspan="7" class="text-center text-danger">数据加载失败</td></tr>';
}
}
async loadFiles() {
try {
const statusFilter = document.getElementById('fileStatusFilter').value;
const params = new URLSearchParams({
limit: 50
});
if (statusFilter) {
params.append('status', statusFilter);
}
const response = await fetch(`/api/files?${params}`);
const files = await response.json();
const tbody = document.getElementById('fileListBody');
if (files.error) {
tbody.innerHTML = `<tr><td colspan="6" class="text-center text-danger">${files.error}</td></tr>`;
return;
}
if (files.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center text-muted">暂无文件数据</td></tr>';
return;
}
tbody.innerHTML = files.map(file => `
<tr>
<td><small>${file.file_path}</small></td>
<td>${this.formatFileSize(file.file_size)}</td>
<td><small>${file.file_hash.substring(0, 16)}...</small></td>
<td>${this.formatDateTime(file.created_at)}</td>
<td>${this.formatDateTime(file.updated_at)}</td>
<td>
<span class="badge bg-${file.status === 'changed' ? 'warning' : 'success'}">
${file.status === 'changed' ? '已变化' : '正常'}
</span>
</td>
</tr>
`).join('');
} catch (error) {
console.error('加载文件数据失败:', error);
document.getElementById('fileListBody').innerHTML =
'<tr><td colspan="6" class="text-center text-danger">数据加载失败</td></tr>';
}
}
async loadAlerts() {
try {
const severityFilter = document.getElementById('severityFilter').value;
const resolvedFilter = document.getElementById('resolvedFilter').value;
const params = new URLSearchParams({
limit: 100
});
if (severityFilter) {
params.append('severity', severityFilter);
}
if (resolvedFilter !== '') {
params.append('resolved', resolvedFilter);
}
const response = await fetch(`/api/alerts?${params}`);
const alerts = await response.json();
const tbody = document.getElementById('alertListBody');
if (alerts.error) {
tbody.innerHTML = `<tr><td colspan="10" class="text-center text-danger">${alerts.error}</td></tr>`;
return;
}
if (alerts.length === 0) {
tbody.innerHTML = '<tr><td colspan="10" class="text-center text-muted">暂无报警数据</td></tr>';
return;
}
tbody.innerHTML = alerts.map(alert => `
<tr>
<td>
<span class="badge bg-${this.getSeverityClass(alert.severity)}">
${alert.severity}
</span>
</td>
<td>${alert.alert_type}</td>
<td>${alert.title}</td>
<td><small>${alert.description}</small></td>
<td>${alert.source_ip || 'N/A'}</td>
<td>${alert.source_process || 'N/A'}</td>
<td><small>${alert.target_file || 'N/A'}</small></td>
<td>${this.formatDateTime(alert.created_at)}</td>
<td>
<span class="badge bg-${alert.resolved ? 'success' : 'warning'}">
${alert.resolved ? '已解决' : '未解决'}
</span>
</td>
<td>
${!alert.resolved ?
`<button class="btn btn-success btn-sm" onclick="hids.resolveAlert(${alert.id})">解决</button>` :
'<span class="text-success">✓</span>'
}
<button class="btn btn-danger btn-sm" onclick="hids.deleteAlert(${alert.id})">删除</button>
</td>
</tr>
`).join('');
} catch (error) {
console.error('加载报警数据失败:', error);
document.getElementById('alertListBody').innerHTML =
'<tr><td colspan="10" class="text-center text-danger">数据加载失败</td></tr>';
}
}
initCharts() {
// 网络连接分布图
const networkCtx = document.getElementById('networkChart').getContext('2d');
this.charts.network = new Chart(networkCtx, {
type: 'doughnut',
data: {
labels: ['TCP连接', 'UDP连接', '监听端口'],
datasets: [{
data: [0, 0, 0],
backgroundColor: ['#4e73df', '#1cc88a', '#36b9cc']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
// 文件状态分布图
const fileCtx = document.getElementById('fileChart').getContext('2d');
this.charts.file = new Chart(fileCtx, {
type: 'doughnut',
data: {
labels: ['正常文件', '变化文件'],
datasets: [{
data: [0, 0],
backgroundColor: ['#28a745', '#ffc107']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
// 报警趋势图
const alertCtx = document.getElementById('alertChart').getContext('2d');
this.charts.alert = new Chart(alertCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '报警数量',
data: [],
borderColor: '#dc3545',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
updateCharts(data) {
// 更新网络连接图表
if (this.charts.network && data.network_stats) {
this.charts.network.data.datasets[0].data = [
data.network_stats.tcp_count || 0,
data.network_stats.udp_count || 0,
data.network_stats.listen_count || 0
];
this.charts.network.update();
}
// 更新文件状态图表
if (this.charts.file && data.file_stats) {
this.charts.file.data.datasets[0].data = [
(data.file_stats.total - data.file_stats.changed) || 0,
data.file_stats.changed || 0
];
this.charts.file.update();
}
}
async resolveAlert(alertId) {
try {
const response = await fetch(`/api/resolve_alert/${alertId}`, {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
this.showAlert('报警已标记为已解决', 'success');
this.loadAlerts();
this.loadStats();
} else {
this.showAlert(data.error || '操作失败', 'error');
}
} catch (error) {
console.error('解决报警失败:', error);
this.showAlert('操作失败', 'error');
}
}
async deleteAlert(alertId) {
if (!confirm('确定要删除这条报警吗?')) {
return;
}
try {
const response = await fetch(`/api/delete_alert/${alertId}`, {
method: 'DELETE'
});
const data = await response.json();
if (response.ok) {
this.showAlert('报警已删除', 'success');
this.loadAlerts();
this.loadStats();
} else {
this.showAlert(data.error || '删除失败', 'error');
}
} catch (error) {
console.error('删除报警失败:', error);
this.showAlert('删除失败', 'error');
}
}
async clearAlerts() {
if (!confirm('确定要清空所有报警吗?此操作不可恢复。')) {
return;
}
try {
const response = await fetch('/api/clear_alerts', {
method: 'POST'
});
const data = await response.json();
if (response.ok) {
this.showAlert(data.message, 'success');
this.loadAlerts();
this.loadStats();
} else {
this.showAlert(data.error || '清空失败', 'error');
}
} catch (error) {
console.error('清空报警失败:', error);
this.showAlert('清空失败', 'error');
}
}
refreshAllData() {
this.loadStats();
this.loadNetwork();
this.loadFiles();
this.loadAlerts();
this.checkMonitoringStatus();
}
// 工具函数
formatDateTime(dateTime) {
if (!dateTime) return 'N/A';
return new Date(dateTime).toLocaleString('zh-CN');
}
formatFileSize(bytes) {
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
getSeverityClass(severity) {
const classMap = {
'HIGH': 'danger',
'MEDIUM': 'warning',
'LOW': 'info'
};
return classMap[severity] || 'secondary';
}
getConnectionStatusClass(status) {
const classMap = {
'normal': 'success',
'warning': 'warning',
'danger': 'danger'
};
return classMap[status] || 'secondary';
}
getConnectionStatusText(status) {
const textMap = {
'normal': '正常',
'warning': '警告',
'danger': '危险'
};
return textMap[status] || '未知';
}
showAlert(message, type = 'info') {
// 创建临时提示
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
alertDiv.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
document.body.appendChild(alertDiv);
// 3秒后自动消失
setTimeout(() => {
if (alertDiv.parentNode) {
alertDiv.parentNode.removeChild(alertDiv);
}
```css
/* static/css/style.css */
/* 全局样式 */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f8f9fa;
}
/* 导航栏样式 */
.navbar-brand {
font-weight: bold;
font-size: 1.2rem;
}
/* 卡片样式 */
.card {
border: none;
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15);
border-radius: 0.35rem;
}
.card-header {
font-weight: 600;
border-bottom: 1px solid #e3e6f0;
}
/* 统计卡片 */
.card.text-white {
transition: transform 0.2s;
}
.card.text-white:hover {
transform: translateY(-2px);
}
/* 表格样式 */
.table {
font-size: 0.9rem;
}
.table-dark {
background-color: #5a5c69;
}
.table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.02);
}
/* 按钮样式 */
.btn {
border-radius: 0.35rem;
font-weight: 500;
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.8rem;
}
/* 徽章样式 */
.badge {
font-weight: 500;
padding: 0.375rem 0.75rem;
}
/* 图表容器 */
.chart-container {
position: relative;
height: 300px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.table-responsive {
font-size: 0.8rem;
}
.card-header .float-end {
float: none !important;
margin-top: 0.5rem;
}
}
/* 加载动画 */
.spinner-border {
width: 1rem;
height: 1rem;
}
/* 状态指示器 */
.status-indicator {
display: inline-block;
width: 0.5rem;
height: 0.5rem;
border-radius: 50%;
margin-right: 0.25rem;
}
.status-indicator.online {
background-color: #28a745;
}
.status-indicator.offline {
background-color: #dc3545;
}
/* 自定义滚动条 */
.table-responsive::-webkit-scrollbar {
height: 0.5rem;
}
.table-responsive::-webkit-scrollbar-track {
background: #f1f1f1;
}
.table-responsive::-webkit-scrollbar-thumb {
background: #888;
border-radius: 0.25rem;
}
.table-responsive::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* 动画效果 */
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* 工具提示 */
.tooltip {
font-size: 0.8rem;
}
/* 表单样式 */
.form-select-sm {
font-size: 0.8rem;
}
/* 警告框 */
.alert {
border: none;
border-radius: 0.35rem;
}
.alert-fixed {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
min-width: 300px;
}
系统部署与运行
环境准备
-
Python环境要求
- Python 3.7 或更高版本
- pip 包管理器
- 虚拟环境(推荐)
-
系统依赖
- Windows 10/11 或 Linux 系统
- 至少 2GB 可用内存
- 100MB 可用磁盘空间
安装步骤
- 克隆项目代码
bash
git clone https://github.com/yourusername/hids-project.git
cd hids-project
- 创建虚拟环境
bash
# Windows
python -m venv venv
venv\Scripts\activate
# Linux/Mac
python3 -m venv venv
source venv/bin/activate
- 安装依赖包
bash
pip install -r requirements.txt
- 初始化数据库
bash
python database.py
- 启动应用
bash
python run.py
配置文件详解
create config.yaml:
yaml
# HIDS系统配置文件
monitoring:
file_integrity:
enabled: true
scan_interval: 300 # 文件扫描间隔(秒)
monitored_directories:
- 'C:\\Windows\\System32'
- 'C:\\Program Files'
- 'C:\\Program Files (x86)'
network:
enabled: true
scan_interval: 60 # 网络扫描间隔(秒)
max_connections: 1000 # 最大连接数限制
process:
enabled: true
scan_interval: 30 # 进程扫描间隔(秒)
web:
host: '0.0.0.0' # 监听地址
port: 5000 # 监听端口
debug: false # 调试模式
logging:
level: 'INFO' # 日志级别
file: 'logs/hids.log' # 日志文件路径
max_size: 10MB # 日志文件最大大小
backup_count: 5 # 日志备份数量
启动脚本开发
create run.py:
python
#!/usr/bin/env python3
"""
HIDS系统启动脚本
"""
import os
import sys
import argparse
import signal
import time
from app import create_app
from database import init_database, reset_database
import logging
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def signal_handler(signum, frame):
"""信号处理函数"""
logger.info(f"接收到信号 {signum},正在关闭应用...")
sys.exit(0)
def main():
"""主函数"""
parser = argparse.ArgumentParser(description='HIDS主机入侵检测系统')
parser.add_argument('--host', default='0.0.0.0', help='监听地址')
parser.add_argument('--port', type=int, default=5000, help='监听端口')
parser.add_argument('--debug', action='store_true', help='调试模式')
parser.add_argument('--reset-db', action='store_true', help='重置数据库')
parser.add_argument('--init-db', action='store_true', help='初始化数据库')
args = parser.parse_args()
# 注册信号处理
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
# 确保必要的目录存在
os.makedirs('data', exist_ok=True)
os.makedirs('logs', exist_ok=True)
os.makedirs('static/css', exist_ok=True)
os.makedirs('static/js', exist_ok=True)
os.makedirs('templates', exist_ok=True)
# 数据库操作
if args.reset_db:
logger.info("正在重置数据库...")
reset_database()
logger.info("数据库重置完成")
elif args.init_db:
logger.info("正在初始化数据库...")
init_database()
logger.info("数据库初始化完成")
else:
# 自动初始化数据库(如果不存在)
if not os.path.exists('data/hids.db'):
logger.info("检测到新安装,正在初始化数据库...")
init_database()
# 创建应用
app = create_app()
logger.info(f"启动HIDS监控系统...")
logger.info(f"Web界面地址: http://{args.host}:{args.port}")
logger.info(f"调试模式: {'开启' if args.debug else '关闭'}")
# 启动应用
app.run(
host=args.host,
port=args.port,
debug=args.debug,
threaded=True
)
except KeyboardInterrupt:
logger.info("应用被用户中断")
except Exception as e:
logger.error(f"应用启动失败: {e}")
sys.exit(1)
if __name__ == '__main__':
main()
Docker容器化部署
create Dockerfile:
dockerfile
# 使用Python 3.9官方镜像
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建必要的目录
RUN mkdir -p data logs static/css static/js templates
# 暴露端口
EXPOSE 5000
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV FLASK_APP=app.py
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/api/stats || exit 1
# 启动命令
CMD ["python", "run.py", "--host", "0.0.0.0", "--port", "5000"]
create docker-compose.yml:
yaml
version: '3.8'
services:
hids:
build: .
ports:
- "5000:5000"
volumes:
- ./data:/app/data
- ./logs:/app/logs
- ./config.yaml:/app/config.yaml
environment:
- PYTHONUNBUFFERED=1
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/stats"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
性能优化策略
-
数据库优化
- 创建合适的索引
- 定期清理历史数据
- 使用连接池管理
-
内存优化
- 限制监控数据缓存大小
- 及时释放无用对象
- 使用生成器处理大数据
-
网络优化
- 启用数据压缩
- 使用CDN加速静态资源
- 实现请求缓存
-
监控优化
- 调整扫描频率
- 实现智能扫描策略
- 使用异步处理
功能测试与验证
API接口测试
create test_network_api.py:
python
#!/usr/bin/env python3
"""
HIDS API测试脚本
"""
import requests
import json
from datetime import datetime
def test_network_api():
"""测试网络连接API"""
print("=== 网络连接API测试 ===")
try:
# 测试基本请求
response = requests.get('http://localhost:5000/api/network?limit=20')
print(f"状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"数据类型: {type(data)}")
# 检查返回格式
if isinstance(data, dict) and 'value' in data:
connections = data['value']
print(f"总连接数: {data.get('Count', 0)}")
print(f"连接数组长度: {len(connections)}")
# 统计协议分布
tcp_count = 0
udp_count = 0
udp_samples = []
for conn in connections:
protocol = conn.get('protocol_display', '未知')
if protocol == 'TCP':
tcp_count += 1
elif protocol == 'UDP':
udp_count += 1
if len(udp_samples) < 3:
udp_samples.append(conn)
print(f"TCP连接: {tcp_count}")
print(f"UDP连接: {udp_count}")
# 显示UDP样本
print("\nUDP连接样本:")
for i, sample in enumerate(udp_samples, 1):
print(f" {i}. ID: {sample.get('id')}, 进程: {sample.get('process_name')}, "
f"本地: {sample.get('local_address')}:{sample.get('local_port')}, "
f"远程: {sample.get('remote_address')}:{sample.get('remote_port')}")
# 验证协议显示逻辑
print(f"\n协议显示验证:")
print(f"TCP样本协议值: {[conn.get('protocol') for conn in connections[:5] if conn.get('protocol_display') == 'TCP']}")
print(f"UDP样本协议值: {[conn.get('protocol') for conn in connections[:5] if conn.get('protocol_display') == 'UDP']}")
else:
print("错误: API返回格式不正确")
print(f"返回数据: {json.dumps(data, indent=2, ensure_ascii=False)}")
else:
print(f"API请求失败: {response.status_code}")
print(f"错误信息: {response.text}")
except requests.exceptions.ConnectionError:
print("错误: 无法连接到HIDS服务器")
print("请确保服务器正在运行: python run.py")
except Exception as e:
print(f"测试过程中发生错误: {e}")
def test_stats_api():
"""测试统计API"""
print("\n=== 统计API测试 ===")
try:
response = requests.get('http://localhost:5000/api/stats')
print(f"状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
print("统计信息:")
print(f" 网络连接总数: {data.get('network_stats', {}).get('total', 0)}")
print(f" TCP连接数: {data.get('network_stats', {}).get('tcp_count', 0)}")
print(f" UDP连接数: {data.get('network_stats', {}).get('udp_count', 0)}")
print(f" 文件总数: {data.get('file_stats', {}).get('total', 0)}")
print(f" 文件变化数: {data.get('file_stats', {}).get('changed', 0)}")
print(f" 报警总数: {data.get('alert_stats', {}).get('total', 0)}")
print(f" 未解决报警: {data.get('alert_stats', {}).get('unresolved', 0)}")
else:
print(f"统计API请求失败: {response.status_code}")
except Exception as e:
print(f"统计API测试失败: {e}")
def test_monitoring_status():
"""测试监控状态API"""
print("\n=== 监控状态API测试 ===")
try:
response = requests.get('http://localhost:5000/api/monitoring_status')
print(f"状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
print(f"监控状态: {'运行中' if data.get('monitoring') else '已停止'}")
print(f"线程状态: {data.get('threads', {})}")
else:
print(f"监控状态API请求失败: {response.status_code}")
except Exception as e:
print(f"监控状态API测试失败: {e}")
if __name__ == '__main__':
print("HIDS API测试工具")
print("=" * 50)
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print()
# 运行所有测试
test_network_api()
test_stats_api()
test_monitoring_status()
print("\n" + "=" * 50)
print("测试完成")
功能验证测试
运行测试脚本验证系统功能:
bash
# 启动HIDS服务器
python run.py
# 在另一个终端运行测试
python test_network_api.py
预期输出:
=== 网络连接API测试 ===
状态码: 200
数据类型: <class 'dict'>
总连接数: 20
连接数组长度: 20
TCP连接: 15
UDP连接: 5
UDP连接样本:
1. ID: 219640, 进程: svchost.exe, 本地: 0.0.0.0:3702, 远程: 0.0.0.0:0
2. ID: 219641, 进程: svchost.exe, 本地: [::]:3702, 远程: [::]:0
3. ID: 219642, 进程: chrome.exe, 本地: 192.168.1.100:52341, 远程: 142.250.185.78:443
协议显示验证:
TCP样本协议值: ['1', '1', '1', '1', '1']
UDP样本协议值: ['2', '2', '2', '2', '2']
性能优化与扩展
性能监控指标
-
响应时间监控
- API接口响应时间 < 500ms
- 页面加载时间 < 2秒
- 数据库查询时间 < 100ms
-
资源使用监控
- CPU使用率 < 80%
- 内存使用率 < 70%
- 磁盘I/O < 50%
-
监控频率优化
- 网络连接:60秒
- 文件完整性:300秒
- 进程监控:30秒
扩展功能规划
-
机器学习集成
- 异常行为检测
- 威胁情报关联
- 预测性分析
-
高级报警机制
- 邮件通知
- 短信告警
- WebHook集成
-
可视化增强
- 实时图表
- 地理分布图
- 时间序列分析
-
多平台支持
- Linux系统适配
- macOS系统适配
- 移动端监控
安全考虑
安全最佳实践
-
认证授权
- 实现用户认证系统
- 基于角色的访问控制
- API访问令牌
-
数据安全
- 敏感数据加密
- 数据库连接加密
- 日志脱敏处理
-
系统安全
- 最小权限原则
- 输入验证
- SQL注入防护
-
网络安全
- HTTPS通信
- CORS配置
- 请求频率限制
合规性考虑
-
数据保护
- GDPR合规
- 数据本地化
- 隐私保护
-
审计要求
- 操作日志
- 访问记录
- 变更追踪
项目总结与展望
项目成果
通过本文的详细介绍,我们成功构建了一个功能完整的HIDS主机入侵检测系统,主要成果包括:
-
核心功能实现
- 网络连接实时监控
- 文件完整性检测
- 进程行为监控
- 智能报警系统
-
技术架构优势
- 模块化设计
- 可扩展架构
- 高性能实现
- 用户友好界面
-
开发实践价值
- 完整的开发流程
- 最佳实践应用
- 测试验证充分
- 文档齐全
技术收获
-
后端开发技能
- Flask框架深度应用
- 数据库设计与优化
- 多线程编程
- RESTful API设计
-
前端开发技能
- 现代JavaScript开发
- Bootstrap响应式设计
- Charts.js数据可视化
- Ajax异步交互
-
系统监控技术
- psutil库应用
- 系统资源监控
- 安全检测算法
- 日志管理
未来发展方向
-
智能化升级
- 集成机器学习算法
- 实现自适应检测
- 威胁情报关联
-
云原生适配
- 容器化部署
- 微服务架构
- 云监控集成
-
企业级功能
- 多主机管理
- 集中式控制台
- 高级报表功能
-
社区生态
- 开源社区建设
- 插件系统开发
- 用户贡献机制
学习建议
对于想要深入学习HIDS系统开发的读者,建议:
-
理论基础
- 学习网络安全基础知识
- 了解入侵检测原理
- 掌握操作系统原理
-
实践项目
- 从简单功能开始
- 逐步增加复杂度
- 注重测试验证
-
技术栈扩展
- 学习其他Web框架
- 掌握多种数据库
- 了解云平台服务
-
社区参与
- 参与开源项目
- 分享技术经验
- 持续学习更新
结语
HIDS主机入侵检测系统的开发是一个综合性很强的项目,涉及网络安全、系统编程、Web开发、数据库设计等多个技术领域。通过本文的详细讲解,相信读者已经掌握了构建此类系统的核心技术和方法。
网络安全是一个持续演进的领域,新的威胁和攻击手段不断涌现。作为安全从业者,我们需要保持学习的热情,不断更新知识体系,提升技术能力。希望这个项目能够成为你技术成长道路上的一个有益实践。