首页 应用实战正文

ModSecurity日志保存至MySQL数据库(通过mlogc)

王子 应用实战 2019-11-19 600 0

本文主要介绍如何使用ModSecurity自带的日志收集器(mlogc),将审计日志发送至远程服务器,并保存至MySQL数据库中。

mlogc只能搭配ModSecurity 2版本使用,如果安装的ModSecurity为3.x版本,可参见另外一篇文章:ModSecurity日志保存至MySQL数据库(通过Logstash)

本文中涉及的规则均已在景安网络(http://www.zzidc.com)的20万个虚拟主机业务中正常运行(或测试通过),可正常使用。


一、搭建远程服务器环境

mlogc会按配置将审计日志使用PUT请求发送至远程服务器,因此远程服务器环境建议为Nginx+PHP+MySQL,将auditLogReceiver.php上传至网站目前下,同时创建数据库mlogc用于保存日志数据。auditLogReceiver.php文件内容及数据库SQL见该文章最下方。


二、设置WEB服务器中的ModSecurity配置

modsecurity.conf文件需进行以下配置:

SecAuditEngine On
SecAuditLogParts ABIJDEFHZ
SecAuditLogType Concurrent
#配置mlogc的路径及mlogc配置文件的路径,安装modsecurity成功后mlogc在对应的安装目录下,mlogc-default.conf在modsecurity压缩包中,复制出来即可
SecAuditLog "|/usr/local/modsecurity/bin/mlogc /usr/local/modsecurity/bin/mlogc-default.conf"
#配置存放日志的目录,该目录必须存在且赋予777权限
SecAuditLogStorageDir /var/log/modsecurity/


mlogc-default.conf文件需进行以下配置

#配置远程服务器接收日志的URL地址
ConsoleURI          "http://ip:port/auditLogReceiver.php" 
#配置账户及密码用于验证请求,该账户密码需同时在auditLogReceiver.php中进行配置
SensorUsername      "admin"
SensorPassword      "password"
#配置存放日志的目录,mlogc将从配置的目录中读取日志,该目录必须存在且赋予777权限
LogStorageDir       "/var/log/modsecurity/"
#发送成功后是否依然保留日志,1表示保留,0表示不保留,发送后本地日志将被自动删除
#如果需要使用SecAuditLog2指令将日志同时发送至第二个远程服务器的话,需要额外配置一个mlogc-default.conf文件
#SecAuditLog指令对应的mlogc-default.conf文件KeepEntries必须设置为1
#SecAuditLog2指令对应的mlogc-default.conf文件KeepEntries可按需求设置,看是否需要保留本地日志
KeepEntries         1


三、数据库SQL文件内容

-- phpMyAdmin SQL Dump
-- version 4.6.6
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1
-- Generation Time: 2019-11-19 11:55:54
-- 服务器版本: 5.6.45-debug
-- PHP Version: 5.5.38

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Database: `mlogc`
--
CREATE DATABASE IF NOT EXISTS `mlogc` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
USE `mlogc`;

-- --------------------------------------------------------

--
-- 表的结构 `data`
--

CREATE TABLE `data` (
  `ID` int(11) NOT NULL,
  `Host` mediumtext,
  `Url` mediumtext,
  `Time` datetime DEFAULT NULL,
  `PartA` mediumtext,
  `PartB` mediumtext,
  `PartC` mediumtext,
  `PartD` mediumtext,
  `PartE` mediumtext,
  `PartF` mediumtext,
  `PartG` mediumtext,
  `PartH` mediumtext,
  `PartI` mediumtext,
  `PartJ` mediumtext,
  `PartK` mediumtext,
  `PartZ` mediumtext,
  `RawText` mediumblob
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Indexes for dumped tables
--

--
-- Indexes for table `data`
--
ALTER TABLE `data`
  ADD PRIMARY KEY (`ID`);

--
-- 在导出的表使用AUTO_INCREMENT
--

--
-- 使用表AUTO_INCREMENT `data`
--
ALTER TABLE `data`
  MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;


四、auditLogReceiver.php文件内容

<?php
if($_SERVER['REQUEST_METHOD'] == 'PUT') {
	// 用于验证此次访问的账号及密码,需与mlogc.conf配置文件中一致
	$username = "admin";
	$password = "password";
	// 数据库账号密码
	$dbUname = "root";
	$dbPasswd = "123456";

	if($username !== $_SERVER['PHP_AUTH_USER'] or $password !== $_SERVER['PHP_AUTH_PW']){
		error_log("Failed Login Attempt to MLogc PHP Console - Username:" . $_SERVER['PHP_AUTH_USER'], 0);
		die("The username or password was incorrect");
	}
	$dsn = 'mysql:host=localhost;dbname=mlogc';
	$options = array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'); 
	$dbh = new PDO($dsn, $dbUname, $dbPasswd, $options);
	$putdata = fopen("php://input", "r");
	$data = stream_get_contents($putdata);
	$lookup = array("A","B","C","D","E","F","G","H","I","J","K","Z");
	$audit_parts = array_fill(0,sizeof($lookup),"NULL");
	// Analyze data to make sure its of the expected format
	$audit_log = explode("\n", $data);
	$current = 0;
	$host = "";
	foreach ($audit_log as $line){
		//截取出域名(Host)单独保存,便于统计查询
		if(strpos($line,"Host") === 0){
		    $hostline = $line;
			$arrhost = array("Host: ");
			$arrtmp = array("");
			$host = str_replace($arrhost, $arrtmp, $hostline);
		}
		// If we are at the beginning
		if(substr($line,0,2) === "--" && substr(str_replace(array("\r", "\n"), '', $line),-2,2) == "--"){
			$current = array_search(substr(str_replace(array("\r", "\n"), '', $line),-3,1),$lookup);
			// We are unable to find the key
			if($current === false){
				error_log("An invalid Audit Log Part was specified",0);
				die("An invalid audit log part was specified this will not be saved");	
			}
			$audit_parts[$current] = $line;
		}else{
			$audit_parts[$current] = $audit_parts[$current] . '\n' . $line;
		}
	}
	if($audit_parts[11] === "NULL"){
		error_log("The format received does not appear correct",0);
		die("The format received does not appear correct");
	}

	//截取出url单独保存,便于统计查询
	$regurl = "/(GET|POST|PUT|HEAD|DELETE|OPTIONS|TRACE|CONNECT).*?HTTP/";
	$arr = array();
	preg_match($regurl,$audit_parts[1],$arr);
	$arrurl = array("GET ", "POST ", "PUT ", "HEAD ", "DELETE ", "OPTIONS ", "TRACE ", "CONNECT ", " HTTP");
	$arrtmp = array("", "", "", "", "", "", "", "", "");
	$url = str_replace($arrurl, $arrtmp, $arr[0]);

	//截取出访问时间单独保存,便于统计查询
	$regtime = "/\[.*?\]/"; 
	preg_match($regtime,$audit_parts[0],$arr);
	$mstime = substr($arr[0],1,20);
	$date = substr($mstime,0,11);
	$time = substr($mstime,12,8);
	$day = substr($date,0,2);
	$monthEn = substr($date,3,3);
	$year = substr($date,7,4);
	$month = "";
	switch ($monthEn) {
		case "Jan":
			$month = "01";
			break;
		case "Feb":
			$month = "02";
			break;
		case "Mar":
			$month = "03";
			break;
		case "Apr":
			$month = "04";
			break;
		case "May":
			$month = "05";
			break;
		case "Jun":
			$month = "06";
			break;
		case "Jul":
			$month = "07";
			break;
		case "Aug":
			$month = "08";
			break;
		case "Sep":
			$month = "09";
			break;
		case "Oct":
			$month = "10";
			break;
		case "Nov":
			$month = "11";
			break;
		case "Dec":
			$month = "12";
			break;
		default:
			$month = 0;
	}
	$formattime = $year."-".$month."-".$day." ".$time;
	
 	$stmt = $dbh->prepare("INSERT INTO data (Host,Url,Time,PartA,PartB,PartC,PartD,PartE,PartF,PartG,PartH,PartI,PartJ,PartK,PartZ,RawText) VALUES (:host,:url,:time,:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:z,:data)");
	$stmt->bindParam(':host', $host);
	$stmt->bindParam(':url', $url);
	$stmt->bindParam(':time', $formattime);
	$stmt->bindParam(':a', $audit_parts[0]);
	$stmt->bindParam(':b', $audit_parts[1]);
	$stmt->bindParam(':c', $audit_parts[2]);
	$stmt->bindParam(':d', $audit_parts[3]);
	$stmt->bindParam(':e', $audit_parts[4]);
	$stmt->bindParam(':f', $audit_parts[5]);
	$stmt->bindParam(':g', $audit_parts[6]);
	$stmt->bindParam(':h', $audit_parts[7]);
	$stmt->bindParam(':i', $audit_parts[8]);
	$stmt->bindParam(':j', $audit_parts[9]);
	$stmt->bindParam(':k', $audit_parts[10]);
	$stmt->bindParam(':z', $audit_parts[11]);
	$stmt->bindParam(':data', $data);
	$stmt->execute();
	fclose($putdata);
}else{
	die("This application does not respond to such requests");
}

?>


版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。