2个小时拿下Perl语言:自动监控服务器内网站访问ip次数并预警(附源码)
[作者]
网名: 猪头三
Email: [email protected]
QQ: 643439947
编程生涯: 2001~至今[2014年]
职业生涯: 11年
开发语言: C/C++; x86asm; Object Pascal; Object-C; C#;
开发工具: VC++; Delphi;
研发领域: Windows应用软件安全; Windows系统内核安全; Windows系统磁盘数据安全;
技能种类: 逆向 驱动 磁盘 文件
[序言]
一直都不是Linux服务器专业人员. 但恰恰自己又拥有2台国外服务器, 上面有好多关于我国外软件的产品网站. 这2台服务器每天都遭受各种第3世界国家攻击以及主流国家恶意爬虫和wp_login.php攻击. 每次维护一次访问日记都需要1个多小时, 很麻烦. 再一个Linux命令苦涩难懂,
勉强会用几个, 但还是不顶用. 就在前几天心烦了, 干脆自己写脚本解析日记算了. 就这个念头一闪, 就诞生了这个perl脚本出来.
[学习]
perl语言认真看2个小时, 然后再mac os x系统装上eclips+perl插件就开工了(备注: 不会用vi, 因为人笨), 断断续续用了2天时间终于完成了第一个版本, 大约700多行. 在开发过程中, 其实很简单: 就是把C语言的逻辑思维转换成perl代码.
为什么要这样做, 因为脚本语言通病就是语法糖过多, 会让你迷失在语法糖里面而无法写出正常的程序, 不是每个开发人员有拥有那种开发语言的天赋(备注:我个人认为只有这样的天赋才能接受各种奇怪的语法糖). 也就是一直坚持这个原则才让这个perl脚本诞生.
[差异]
perl和python都很好上手, 只要你有C语言基础, 玩这个很简单. 比JS简单多了. 那我为什么用perl呢?其实是我的服务器默认没有安装python, 倒反默认安装了ruby, 比较神奇. 本来想用ruby的, 因为我第一个脚本语言就是ruby, 但是后来想想linux服务器一般大头都是perl语言,
因此选择了perl而不是ruby.
[源码]
#!usr/bin/perl
#作用: 监视服务器的网站目录下access-log文件夹里的日记记录
# 并分析日记里面的ip访问次数, 如果达到预警次数则自动发送email
# 给管理员
#作者: 猪头三 http://www.x86asm.com
#版本: v0.0.1 测试版
#时间: 2014-07-15
#留言: 本人不是Linux系统专家, 因此写这个脚本是纯粹自己用用,
# 如果你有更好的建议和方法请联系我.
#使用方式: 按照提示修好源码之后, 就传到linux服务器并通过crontab -e加入计划任务定时执行即可
#解决其他路径下的模块包含问题
BEGIN {
push @INC,"/Users/PigHeadThree/perl5/lib/perl5" ;
}
#use 5.010;
use strict ;
use autodie;
use Try::Tiny;
use Tie::File;
use Fcntl 'O_RDONLY' ;
use File::Spec::Functions qw(rel2abs) ;
use File::Basename qw(dirname basename) ;
use File::Spec;
use File::Copy qw(copy);
use MIME::Lite ;
use MIME::Base64;
use Authen::SASL; # 重要:如果服务器没有该模块将会导致email无法发送
#=================测试代码==================
#=================全局变量==================
#目录特征, 防止错误删除(表示你执行脚本要放在此目录下面)(可修改)
my $g_str_App_Flag = 'pht_monitor' ;
#匹配access-logs的每条记录是否包含google和bing机器人(可修改)
#google bing的ip都是合法ip
my $g_str_grep_Bot = 'googlebot|bingbot' ;
#ip访问次数预警(可修改)
my $g_int_Limit_VisitIp = 900 ;
#服务器名称(可修改)
my $g_str_ServerName = "PHT Server" ;
#自动发送email(可修改)
my $g_str_email_Pass = 'xxxxxxx' ;
my $g_str_eamil_Smtp = 'smtp.163.com' ;
#=================函数入口点==================
try
{
&fun_RunningLog("main() start...") ;
&main() ;
&fun_RunningLog("main() end.") ;
}
catch
{
&fun_RunningLog("===========main() running error===========") ;
};
sub main()
{
#比如你的服务器存存在一个站点 www.x86asm.com
#那么该服务器内部会有一个专门的文件夹存放该站点的所有文件
#假设为: x86asm
#那么该目录下会存放一个名为x86asm.com的日记文件,一般路径如下
#/home/x86asm/access-logs/x86asm.com
#我们可以通过分析这个日记文件来得到ip的访问次数
my $str_Site_URL = "www.x86asm.com" ;
my $str_Site_Name = "x86asm_" ;
my $str_Site_LogFilePath = "/home/x86asm/access-logs/x86asm.com" ;
fun_CountTargetSite($str_Site_URL,
$str_Site_Name,
$str_Site_LogFilePath) ;
}#End main()
#=================函数======================
#统计目标网站的ip访问次数
#param_1 : 目标网站域名
#param_2 : 目标网站简称
#param_3 : 目标网站access_log文件路径
sub fun_CountTargetSite()
{
#本函数不支持0参数传递
if (@_ != 3)
{
return 0 ;
}
(my $str_param_Site_URL,
my $str_param_Site_Name,
my $str_param_Site_LogFilePath) = @_ ;
my %hash_VisitIp_Count ;
&fun_RunningLog("[$str_param_Site_URL] : start...");
&fun_GetVisitIp($str_param_Site_LogFilePath,
$g_int_Limit_VisitIp,
&fun_GetLocaltime("/"),
\%hash_VisitIp_Count) ;
if (&fun_IsNeedUpdate($str_param_Site_Name.&fun_GetLocaltime("_").".txt",
\%hash_VisitIp_Count) eq 1)
{
#保存最新统计
&fun_VisitIpSaveToCurrentPathFile($str_param_Site_Name.&fun_GetLocaltime("_").".txt",
\%hash_VisitIp_Count);
#发送EMAIL
my $str_SendEmail_Data = &fun_GetSendEmailToData($str_param_Site_Name.&fun_GetLocaltime("_").".txt") ;
if ($str_SendEmail_Data ne 0)
{
&fun_AutoSendEmailToAlarm($g_str_ServerName."[$str_param_Site_URL]"."Alarm Ip Visit",
$str_SendEmail_Data) ;
}
}
else
{
}
&fun_RunningLog("[$str_param_Site_URL] : end.");
}# End fun_CountTargetSite()
#获取本地时间
#param_1 : 时间分隔符
sub fun_GetLocaltime()
{
#本函数不支持0参数传递
if (@_ != 1)
{
return 0 ;
}
#月份对照表
my %hash_montoname = (
"01" => "Jan",
"02" => "Feb",
"03" => "Mar",
"04" => "Apr",
"05" => "May",
"06" => "Jun",
"07" => "Jul",
"08" => "Aug",
"09" => "Sep",
"10" => "Oct",
"11" => "Nov",
"12" => "Dec") ;
#获取本地时间
my ($sec,$min,$hour,$mday,$mon,$year_off,$wday,$yday,$isdat) = localtime;
($mday, $mon, $year_off) = (
sprintf("%02d", $mday),
sprintf("%02d", $mon+1),
$year_off+1900);
#月份转换为英文描述
$mon = $hash_montoname{$mon} ;
return $mday.$_[0].$mon.$_[0].$year_off;
}# End fun_GetLocaltime()
#用于日记记录的时间格式
sub fun_GetLocaltimeToLog()
{
#本函数不支持参数传递
if (@_)
{
return 0 ;
}
#月份对照表
my @array_montoname = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec) ;
#获取本地时间
my ($sec,$min,$hour,$mday,$mon,$year_off,$wday,$yday,$isdat) = localtime;
($sec, $min, $hour, $mday, $year_off) = (
sprintf("%02d", $sec),
sprintf("%02d", $min),
sprintf("%02d", $hour),
sprintf("%02d", $mday),
$year_off+1900);
#月份转换为英文描述
$mon = $array_montoname[$mon] ;
return $mday.'/'.$mon.'/'.$year_off.' '.$hour.':'.$min.':'.$sec ;
}# End fun_GetLocaltimeToLog()
#日记记录
#param_1 : 日记内容
sub fun_RunningLog()
{
#本函数不支持0个参数传递
if (@_ != 1)
{
return 0 ;
}
(my $str_param_Print) = @_ ;
#获取时间
my $str_LogTime = '['.&fun_GetLocaltimeToLog().']' ;
#打开日记文件, 如果没有则创建
my $str_Log_FileName = 'run_'.&fun_GetLocaltime('_').'.txt' ;
$str_Log_FileName = File::Spec->join(&fun_GetCurrentAppPath(),
$str_Log_FileName) ;
unless(-e $str_Log_FileName)
{
unless(open(HANDLE_OPEN, ">$str_Log_FileName"))
{
return 0 ;
}
}
else
{
unless(open(HANDLE_OPEN, ">>$str_Log_FileName"))
{
return 0 ;
}
}
#开始写入
print HANDLE_OPEN $str_LogTime.' : '.$str_param_Print."\n" ;
close(HANDLE_OPEN) ;
return 1 ;
}#End fun_RunningLog()
#获取当前执行目录
sub fun_GetCurrentAppPath()
{
#本函数不支持参数传递
if (@_)
{
return 0 ;
}
return dirname(rel2abs(__FILE__)) ;
}# End fun_GetCurrentAppPath()
#复制目标文件到当前目录
#param_1 : 需要复制到目标文件全路径
sub fun_CopyFileToCurrentAppPath()
{
my $str_CopyFileToPath = "" ;
my $str_CopyFileName = "" ;
my $str_CurrentAppPath = "" ;
#本函数不支持0个参数传递
if (@_ != 1)
{
return 0 ;
}
else
{
my $str_param_CopyFilePath = $_[0] ;
#判断文件是否存在 -e 表示 文件 -d 表示目录
if (-e $str_param_CopyFilePath)
{
#开始复制(先执行旧文件删除)
$str_CurrentAppPath = &fun_GetCurrentAppPath() ;
$str_CopyFileName = basename($str_param_CopyFilePath) ;
$str_CopyFileToPath = File::Spec->join($str_CurrentAppPath, $str_CopyFileName) ;
if (-e $str_CopyFileToPath)
{
#删除旧文件
if ($str_CopyFileToPath =~ /$g_str_App_Flag/i)
{
&fun_RunningLog("fun_CopyFileToCurrentAppPath() deleteing $str_CopyFileToPath...") ;
unlink($str_CopyFileToPath);
&fun_RunningLog("fun_CopyFileToCurrentAppPath() deleted $str_CopyFileToPath.") ;
}
}
copy($str_param_CopyFilePath, $str_CopyFileToPath) ;
return $str_CopyFileToPath ;
}
}
return 0 ;
}# End fun_CopyFileToCurrentAppPath()
#正则表达式提取ip地址
sub fun_GetIpByStr()
{
#本函数不支持0个参数传递
if (@_ != 1)
{
return 0 ;
}
(my $str_param_Target) = @_ ;
if ($str_param_Target =~ /(\d+\.\d+\.\d+\.\d+)/)
{
return "$1" ; #IP地址
}
return 0 ;
}# End fun_GetIpByStr()
#统计目标ip访问次数
#param_1 : 目标访问日记文件路径
#param_2 : IP访问次数报警
#param_3 : 目标时间时间到访问记录 (02/jul/2014)
#param_4 : 哈希表 (地址传递)
sub fun_GetVisitIp()
{
#本函数不支持0个参数传递
if (@_ != 4)
{
return 0 ;
}
#获取参数
(my $str_param_VisitIp_LogFilePath,
my $int_param_VisitIp_Count,
my $time_param_Get,
my $hash_param_VisitIp) = @_ ;
#获取本地时间
if ($time_param_Get eq "NO")
{
$time_param_Get = &fun_GetLocaltime("/") ;
}
#开始统计
if (-e $str_param_VisitIp_LogFilePath)
{
my $str_VisitIp_CopyToPath = &fun_CopyFileToCurrentAppPath($str_param_VisitIp_LogFilePath) ;
if ($str_VisitIp_CopyToPath eq 0)
{
#获取日记文件失败
return 0 ;
}
#打开日记文件
my @array_VisitIp_Load ;
my $int_VisitIp_FileLineNums ;
if (-e $str_VisitIp_CopyToPath)
{
tie @array_VisitIp_Load,
'Tie::File',
$str_VisitIp_CopyToPath,
mode => O_RDONLY ;
#获取行数
$int_VisitIp_FileLineNums = @array_VisitIp_Load - 1 ;
#逆向循环匹配
my $str_Temp_Line = "" ;
my $str_Temp_VisitIp = "" ;
my $str_Temp_VisitIp_Count = 0 ;
my %hash_Temp_VisitIp = () ;
while ($int_VisitIp_FileLineNums >= 0)
{
$str_Temp_Line = $array_VisitIp_Load[$int_VisitIp_FileLineNums] ;
if ($str_Temp_Line =~ /$time_param_Get/i)
{
if ($str_Temp_Line =~ /$g_str_grep_Bot/i)
{
}
else
{
#捕获ip地址
$str_Temp_VisitIp = &fun_GetIpByStr($str_Temp_Line) ;
if ($str_Temp_VisitIp eq 0)
{
}
else
{
#保存ip地址以及访问次数(临时)
if (exists $hash_Temp_VisitIp{$str_Temp_VisitIp})
{
$hash_Temp_VisitIp{$str_Temp_VisitIp} += 1 ;
#判断是否大于报警次数,如果大于则保存
if ($hash_Temp_VisitIp{$str_Temp_VisitIp} >= $int_param_VisitIp_Count)
{
if (exists $$hash_param_VisitIp{$str_Temp_VisitIp})
{
$$hash_param_VisitIp{$str_Temp_VisitIp} += 1 ;
}
else
{
$$hash_param_VisitIp{$str_Temp_VisitIp} = $int_param_VisitIp_Count ;
}
}
}
else
{
$hash_Temp_VisitIp{$str_Temp_VisitIp} = 1 ;
}
}
}
}
else
{
last ;
}
$int_VisitIp_FileLineNums -= 1 ;
}
#关闭文件
untie @array_VisitIp_Load ;
return 1 ;
}
}
else
{
&fun_RunningLog("fun_GetVisitIp() $str_param_VisitIp_LogFilePath no found.") ;
}
return 0 ;
}# End fun_GetVisitIp()
#把哈希表到内容保存到当前执行目录下
#param_1 : 文件名称
#param_2 : 哈希内容
sub fun_VisitIpSaveToCurrentPathFile()
{
#本函数不支持0个参数传递
if (@_ != 2)
{
return 0 ;
}
(my $str_param_SaveToFileName,
my $hash_param_Conent) = @_ ;
#校验参数
if ((length($str_param_SaveToFileName) == 0) or
((keys %$hash_param_Conent) == 0))
{
return 0 ;
}
#开始保存
my $str_SaveToFilePath = File::Spec->join(&fun_GetCurrentAppPath(),
$str_param_SaveToFileName) ;
if (-e $str_SaveToFilePath)
{
#删除旧文件
if ($str_SaveToFilePath =~ /$g_str_App_Flag/i)
{
&fun_RunningLog("fun_VisitIpSaveToCurrentPathFile() deleteing $str_SaveToFilePath...") ;
unlink($str_SaveToFilePath);
&fun_RunningLog("fun_VisitIpSaveToCurrentPathFile() deleted $str_SaveToFilePath.") ;
}
}
if (open(HANDLE_FILE, ">$str_SaveToFilePath"))
{
my $hash_Keys ;
my $hash_Values ;
foreach $hash_Keys (sort keys %$hash_param_Conent)
{
$hash_Values = $$hash_param_Conent{$hash_Keys} ;
print HANDLE_FILE "$hash_Keys => $hash_Values \n" ;
}
close(HANDLE_FILE) ;
}
else
{
return 0 ;
}
}# End fun_VisitIpSaveToCurrentPathFile()
#判断是否需要更新日记文件(.txt)
#param_1 : 文件名称
#param_2 : 哈希内容
sub fun_IsNeedUpdate()
{
#本函数不支持0个参数传递
if (@_ != 2)
{
return 0 ;
}
(my $str_param_FileName,
my $hash_param_Conent) = @_ ;
#校验参数
if ((length($str_param_FileName) == 0) or
(keys %$hash_param_Conent == 0))
{
return 0 ;
}
my $bool_IsNeedUpdate = 0 ; #更新标志位
my $str_Ip = "" ;
my %hash_Load_Ip ;
my $str_OpenFilePath = File::Spec->join(&fun_GetCurrentAppPath(),
$str_param_FileName) ;
#打开文件并匹配ip是否有更新
if (-e $str_OpenFilePath)
{
if (open (HANDLE_OPEN, $str_OpenFilePath))
{
while(my $str_Temp_Line = <HANDLE_OPEN>)
{
$str_Ip = &fun_GetIpByStr($str_Temp_Line) ;
if ($str_Ip eq 0)
{
}
else
{
# 保存之前统计过的ip
$hash_Load_Ip{$str_Ip} = 1 ;
}
}
close(HANDLE_OPEN);
#开始循环匹配
my $str_Temp_Value ;
while(($str_Ip, $str_Temp_Value) = each %$hash_param_Conent)
{
#判断ip是否是存在, 如果不存在则表示需要更新, 返回1
if (exists $hash_Load_Ip{$str_Ip})
{
}
else
{
$bool_IsNeedUpdate = 1 ;
last ;
}
}
}
}
else
{
#没有日记记录文件也表示需要更新
$bool_IsNeedUpdate = 1 ;
}
return $bool_IsNeedUpdate ;
}# End fun_IsNeedUpdate()
#自动发送email进行报警
#param_1 : 标题
#param_2 : 内容
sub fun_AutoSendEmailToAlarm()
{
#本函数不支持0参数传递
if (@_ != 2)
{
return 0 ;
}
(my $str_param_Subject,
my $str_param_Data) = @_ ;
if ((length($str_param_Subject) == 0) or
(length($str_param_Data) == 0))
{
return 0
}
my $msg_Send = MIME::Lite->new(From => $g_str_email_From,
To => $g_str_eamil_To,
Subject => $str_param_Subject,
Data => $str_param_Data) ;
#$msg_Send->attr("content-type" => "text/html") ;
$msg_Send->send("smtp", $g_str_eamil_Smtp,
AuthUser=>$g_str_email_From,
AuthPass=>$g_str_email_Pass,
Timeout => 30) ; # Debug => 1 表示打印发送时的各种状态记录
return 1 ;
}# End fun_AutoSendEmailToAlarm()
#从指定文件获取内容当作email内容进行发送
#param_1 : 目标文件名 (默认只在当前目录下获取)
sub fun_GetSendEmailToData()
{
#本函数不支持0参数传递
if (@_ != 1)
{
return 0 ;
}
(my $str_param_FileName) = @_ ;
if (length($str_param_FileName) == 0)
{
return 0 ;
}
#开始打开文件
my $str_OpenFilePath = File::Spec->join(&fun_GetCurrentAppPath(),
$str_param_FileName) ;
if (-e $str_OpenFilePath)
{
my $str_Content = "" ;
if (open (HANDLE_OPEN, $str_OpenFilePath))
{
while(my $str_Temp_Line = <HANDLE_OPEN>)
{
chomp($str_Temp_Line) ;
$str_Content .= $str_Temp_Line .= "\n" ;
}
close(HANDLE_OPEN);
if (length($str_Content) != 0)
{
return $str_Content ;
}
}
}
return 0 ;
}# End fun_GetSendEmailToData()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.