[作者]
网名: 猪头三
Email: pliceman_110@163.com
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
# 给管理员
#版本: 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_From
'xxxxxxx@163.com'
;
my
$g_str_email_Pass
'xxxxxxx'
;
my
$g_str_eamil_Smtp
'smtp.163.com'
;
my
$g_str_eamil_To
'xxxxxxx@qq.com'
;
#=================函数入口点==================
try
{
&
fun_RunningLog
(
"main() start..."
) ;
&
main
() ;
"main() end."
) ;
catch
"===========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
;
"[$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
\%
hash_VisitIp_Count
);
#发送EMAIL
my
$str_SendEmail_Data
=
&
fun_GetSendEmailToData
(
$str_param_Site_Name
".txt"
) ;
if
(
$str_SendEmail_Data
ne
0
)
{
&
fun_AutoSendEmailToAlarm
(
$g_str_ServerName
"[$str_param_Site_URL]"
"Alarm Ip Visit"
$str_SendEmail_Data
) ;
}
else
}
"[$str_param_Site_URL] : end."
);
# End fun_CountTargetSite()
#获取本地时间
#param_1 : 时间分隔符
sub
fun_GetLocaltime
()
#月份对照表
%
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
)
$mon
+1
)
$year_off
+1900
);
#月份转换为英文描述
$mon
=
$hash_montoname
{
$mon
} ;
return
$mday
.
$_
[
0
]
.
$mon
.
$year_off
;
# End fun_GetLocaltime()
#用于日记记录的时间格式
sub
fun_GetLocaltimeToLog
()
#本函数不支持参数传递
if
(@
_
)
my
@
array_montoname
=
qw
(
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
) ;
(
$sec
$min
$hour
$mday
$sec
)
$min
)
$hour
)
=
$array_montoname
[
$mon
] ;
'/'
.
$year_off
' '
.
$hour
':'
.
$min
.
$sec
;
# End fun_GetLocaltimeToLog()
#日记记录
#param_1 : 日记内容
sub
fun_RunningLog
()
#本函数不支持0个参数传递
my
$str_param_Print
)
#获取时间
my
$str_LogTime
'['
.&
fun_GetLocaltimeToLog
()
']'
;
#打开日记文件, 如果没有则创建
my
$str_Log_FileName
'run_'
'_'
)
'.txt'
;
$str_Log_FileName
=
File
::
Spec
->
join
(
&
fun_GetCurrentAppPath
()
$str_Log_FileName
) ;
unless
(
-
e
$str_Log_FileName
)
unless
(
open
(
HANDLE_OPEN
">$str_Log_FileName"
))
">>$str_Log_FileName"
))
#开始写入
print
HANDLE_OPEN
$str_LogTime
' : '
.
$str_param_Print
"\n"
;
close
(
HANDLE_OPEN
) ;
1
;
#End fun_RunningLog()
#获取当前执行目录
sub
fun_GetCurrentAppPath
()
return
dirname
(
rel2abs
(
__FILE__
)) ;
# End fun_GetCurrentAppPath()
#复制目标文件到当前目录
#param_1 : 需要复制到目标文件全路径
sub
fun_CopyFileToCurrentAppPath
()
my
$str_CopyFileToPath
""
;
my
$str_CopyFileName
my
$str_CurrentAppPath
my
$str_param_CopyFilePath
=
$_
[
0
] ;
#判断文件是否存在 -e 表示 文件 -d 表示目录
-
e
$str_param_CopyFilePath
)
#开始复制(先执行旧文件删除)
$str_CurrentAppPath
&
fun_GetCurrentAppPath
() ;
$str_CopyFileName
=
basename
(
$str_param_CopyFilePath
) ;
$str_CopyFileToPath
join
(
$str_CurrentAppPath
$str_CopyFileName
) ;
-
e
$str_CopyFileToPath
)
{
#删除旧文件
if
(
$str_CopyFileToPath
=~
/$g_str_App_Flag/i
)
{
"fun_CopyFileToCurrentAppPath() deleteing $str_CopyFileToPath..."
) ;
unlink
(
$str_CopyFileToPath
);
"fun_CopyFileToCurrentAppPath() deleted $str_CopyFileToPath."
) ;
}
}
copy
(
$str_param_CopyFilePath
$str_CopyFileToPath
) ;
return
$str_CopyFileToPath
;
# End fun_CopyFileToCurrentAppPath()
#正则表达式提取ip地址
sub
fun_GetIpByStr
()
my
$str_param_Target
)
if
(
$str_param_Target
/(\d+\.\d+\.\d+\.\d+)/
)
return
"$1"
;
#IP地址
# End fun_GetIpByStr()
#统计目标ip访问次数
#param_1 : 目标访问日记文件路径
#param_2 : IP访问次数报警
#param_3 : 目标时间时间到访问记录 (02/jul/2014)
#param_4 : 哈希表 (地址传递)
sub
fun_GetVisitIp
()
4
)
#获取参数
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
"/"
) ;
#开始统计
-
e
$str_param_VisitIp_LogFilePath
)
my
$str_VisitIp_CopyToPath
&
fun_CopyFileToCurrentAppPath
(
$str_param_VisitIp_LogFilePath
) ;
if
(
$str_VisitIp_CopyToPath
eq
#获取日记文件失败
#打开日记文件
my
@
array_VisitIp_Load
;
my
$int_VisitIp_FileLineNums
;
-
e
$str_VisitIp_CopyToPath
)
tie
@
array_VisitIp_Load
'Tie::File'
$str_VisitIp_CopyToPath
mode
=>
O_RDONLY
;
#获取行数
$int_VisitIp_FileLineNums
=
@
array_VisitIp_Load
-
#逆向循环匹配
my
$str_Temp_Line
my
$str_Temp_VisitIp
my
$str_Temp_VisitIp_Count
0
;
%
hash_Temp_VisitIp
=
() ;
while
(
$int_VisitIp_FileLineNums
>=
$str_Temp_Line
=
$array_VisitIp_Load
[
$int_VisitIp_FileLineNums
] ;
if
(
$str_Temp_Line
/$time_param_Get/i
)
/$g_str_grep_Bot/i
)
{
}
$str_Temp_VisitIp
&
fun_GetIpByStr
(
$str_Temp_Line
) ;
if
(
$str_Temp_VisitIp
eq
{
}
#保存ip地址以及访问次数(临时)
if
(
exists
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
})
{
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
}
+=
#判断是否大于报警次数,如果大于则保存
if
(
$hash_Temp_VisitIp
{
$str_Temp_VisitIp
}
>=
$int_param_VisitIp_Count
)
{
exists
$$hash_param_VisitIp
{
$str_Temp_VisitIp
})
{
$$hash_param_VisitIp
{
$str_Temp_VisitIp
}
1
;
}
=
$int_param_VisitIp_Count
;
}
}
last
;
$int_VisitIp_FileLineNums
-=
1
;
#关闭文件
untie
@
array_VisitIp_Load
;
1
;
"fun_GetVisitIp() $str_param_VisitIp_LogFilePath no found."
) ;
# End fun_GetVisitIp()
#把哈希表到内容保存到当前执行目录下
#param_1 : 文件名称
#param_2 : 哈希内容
sub
fun_VisitIpSaveToCurrentPathFile
()
2
)
my
$str_param_SaveToFileName
my
$hash_param_Conent
)
#校验参数
if
((
length
(
$str_param_SaveToFileName
)
==
0
)
or
((
keys
%
$hash_param_Conent
)
0
))
#开始保存
my
$str_SaveToFilePath
$str_param_SaveToFileName
) ;
-
e
$str_SaveToFilePath
)
if
(
$str_SaveToFilePath
"fun_VisitIpSaveToCurrentPathFile() deleteing $str_SaveToFilePath..."
) ;
unlink
(
$str_SaveToFilePath
);
"fun_VisitIpSaveToCurrentPathFile() deleted $str_SaveToFilePath."
) ;
open
(
HANDLE_FILE
">$str_SaveToFilePath"
))
my
$hash_Keys
;
my
$hash_Values
;
foreach
$hash_Keys
(
sort
%
$hash_param_Conent
)
$hash_Values
=
$$hash_param_Conent
{
$hash_Keys
} ;
print
HANDLE_FILE
"$hash_Keys => $hash_Values \n"
;
close
(
HANDLE_FILE
) ;
# End fun_VisitIpSaveToCurrentPathFile()
#判断是否需要更新日记文件(.txt)
sub
fun_IsNeedUpdate
()
my
$str_param_FileName
length
(
$str_param_FileName
)
(
%
$hash_param_Conent
my
$bool_IsNeedUpdate
0
;
#更新标志位
my
$str_Ip
%
hash_Load_Ip
;
my
$str_OpenFilePath
$str_param_FileName
) ;
#打开文件并匹配ip是否有更新
-
e
$str_OpenFilePath
)
open
(
HANDLE_OPEN
$str_OpenFilePath
))
while
(
my
$str_Temp_Line
<
HANDLE_OPEN
>
)
$str_Ip
if
(
$str_Ip
eq
{
}
$hash_Load_Ip
{
$str_Ip
}
1
;
close
(
HANDLE_OPEN
);
#开始循环匹配
my
$str_Temp_Value
;
while
((
$str_Ip
$str_Temp_Value
)
each
#判断ip是否是存在, 如果不存在则表示需要更新, 返回1
exists
$hash_Load_Ip
{
$str_Ip
})
{
}
}
#没有日记记录文件也表示需要更新
$bool_IsNeedUpdate
return
$bool_IsNeedUpdate
;
# End fun_IsNeedUpdate()
#自动发送email进行报警
#param_1 : 标题
#param_2 : 内容
sub
fun_AutoSendEmailToAlarm
()
my
$str_param_Subject
my
$str_param_Data
)
length
(
$str_param_Subject
)
(
length
(
$str_param_Data
)
==
0
))
{
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 表示打印发送时的各种状态记录
# End fun_AutoSendEmailToAlarm()
#从指定文件获取内容当作email内容进行发送
#param_1 : 目标文件名 (默认只在当前目录下获取)
sub
fun_GetSendEmailToData
()
my
$str_param_FileName
)
#开始打开文件
my
$str_Content
chomp
(
$str_Temp_Line
) ;
$str_Content
.=
$str_Temp_Line
.=
"\n"
;
length
(
$str_Content
)
return
$str_Content
;
# End fun_GetSendEmailToData()