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