本文主要介绍如何使用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");
}
?>版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。



