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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.