awk實現將lastb裡超過失敗登入次數上限的IP地址自動加入黑名單

李蔚發表於2024-11-08

起因:今天有人不斷登入我主機,應該是拿我練習入侵。我也一直就有寫個自動拉黑指令碼的打算了,正好也用這個機會測試一下。
思路:用crontab建立計劃任務每1小時用awk過濾lastb裡顯示的上個小時裡失敗登入的IP地址並做統計,超過設定的次數就自動加入firewalld的黑名單裡並mail發郵件到我郵箱。

指令
[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" xxxxx@xx.com && rm -f /tmp/baninfo
crontab
[root@RHEL9 ~]# crontab -l
0 * * * * lastb | awk -v hourago="$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
shell自動寫入crontab
[root@RHEL9 ~]# cat ban.sh 
#ban.sh
#Date: 2024-11-07
#!/bin/bash

cat << EOF >> /var/spool/cron/root
0 * * * * lastb | awk -v hourago="\$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '\$0~hourago{ip[\$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"\$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
EOF

目前還一直在被登入

[root@RHEL9 ~]# lastb | head
sycdl    ssh:notty    159.89.207.36    Wed Nov  6 23:53 - 23:53  (00:00)
ss97     ssh:notty    159.89.207.36    Wed Nov  6 23:53 - 23:53  (00:00)
smy      ssh:notty    159.89.207.36    Wed Nov  6 23:53 - 23:53  (00:00)
smy      ssh:notty    159.89.207.36    Wed Nov  6 23:53 - 23:53  (00:00)
shuminzh ssh:notty    159.89.207.36    Wed Nov  6 23:53 - 23:53  (00:00)
shuminzh ssh:notty    159.89.207.36    Wed Nov  6 23:53 - 23:53  (00:00)
pyt      ssh:notty    159.89.207.36    Wed Nov  6 23:52 - 23:52  (00:00)
pyt      ssh:notty    159.89.207.36    Wed Nov  6 23:52 - 23:52  (00:00)
omnisky  ssh:notty    159.89.207.36    Wed Nov  6 23:52 - 23:52  (00:00)
mrbai    ssh:notty    159.89.207.36    Wed Nov  6 23:51 - 23:51  (00:00)

先用date指令調出上一個小時的時間

[root@RHEL9 ~]# date
Wed Nov  6 11:59:43 PM CST 2024
[root@RHEL9 ~]# date --date='1 hour ago'
Wed Nov  6 11:00:32 PM CST 2024

lastb的時間日期格式為 縮寫的星期名 縮寫的月份名 空格填充的日期(個位日期前面沒有0) 24小時的時間:分鐘

%a     locale's abbreviated weekday name (e.g., Sun)
%b     locale's abbreviated month name (e.g., Jan)
%e     day of month, space padded; same as %_d
%H     hour (00..23)
%M     minute (00..59)

按小時過濾,分鐘這個引數用不上,但是測試的時候用的上。配置引數將date指令日期格式設定為與lastb相同

[root@RHEL9 ~]# lastb | head -1 
lixiao   ssh:notty    159.89.207.36    Thu Nov  7 00:33 - 00:33  (00:00)
[root@RHEL9 ~]# lastb | head -1 | sed -En 's/^.+\.[0-9]+ +(.+)$/\1/p'
Thu Nov  7 00:33 - 00:33  (00:00)
[root@RHEL9 ~]# date --date='1 hour ago' '+%a %b %e %H:%M'
Wed Nov  6 23:34

測試date指令在awk裡賦值並呼叫

[root@RHEL9 ~]# echo | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:%M')" '{print hourago}'
Wed Nov  6 23:40

用 ~ 匹配,$0 ~ hourago ,檢索 全文裡 包含 hourago 變數裡日期格式的行,目前檢索出的是上個小時按分鐘檢索出的登入我主機的行。

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:%M')" '$0~hourago{print $0}'
fym      ssh:notty    159.89.207.36    Wed Nov  6 23:49 - 23:49  (00:00)
csj20017 ssh:notty    159.89.207.36    Wed Nov  6 23:49 - 23:49  (00:00)
cjw      ssh:notty    159.89.207.36    Wed Nov  6 23:49 - 23:49  (00:00)
[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:%M')" '$0~hourago{print $0}'
lj187153 ssh:notty    159.89.207.36    Wed Nov  6 23:50 - 23:50  (00:00)
lft      ssh:notty    159.89.207.36    Wed Nov  6 23:50 - 23:50  (00:00)
hsy      ssh:notty    159.89.207.36    Wed Nov  6 23:50 - 23:50  (00:00)
hsy      ssh:notty    159.89.207.36    Wed Nov  6 23:50 - 23:50  (00:00)
hsy      ssh:notty    159.89.207.36    Wed Nov  6 23:50 - 23:50  (00:00)

用for迴圈統計連線數,1小時前的這分鐘登入了6次,上個小時登入了300次

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:%M')" '$0~hourago{ip[$3]++}END{for (i in ip) print i,ip[i]}'
159.89.207.36 6
[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip) print i,ip[i]}'
159.89.207.36 300

加入if語句進行條件判斷,連線次數超過200列印出來

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>200)print i,ip[i]}}'
159.89.207.36 300
[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>400)print i,ip[i]}}'
[root@RHEL9 ~]# 

呼叫system{}語句塊,在awk裡執行bash指令並呼叫awk裡的引數,用echo測試下

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>100){system("echo "i" "ip[i]"")}}}'
159.89.207.36 300

測試防火牆指令,後續還要測試crontab,拉進黑名單就沒法測試了,先拉進白名單

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>100){system("firewall-cmd --permanent --add-source="i" --zone=trusted;firewall-cmd --reload")}}}'
success
success
[root@RHEL9 ~]# firewall-cmd --get-active-zones
libvirt
  interfaces: virbr0
public
  interfaces: LANbridge WANbridge enp4s0 DMZbridge
trusted
  sources: 159.89.207.36
[root@RHEL9 ~]# firewall-cmd --permanent --remove-source=159.89.207.36 --zone=trusted;firewall-cmd --reload
success
success

編輯郵件內容測試

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>100){system("firewall-cmd --permanent --remove-source="i" --zone=trusted;firewall-cmd --reload;echo $(hostname) ban "i" for connected "ip[i]" times in 1 hour")}}}'
success
success
RHEL9 ban 159.89.207.36 for connected 300 times in 1 hour

透過mail傳送,echo " 信件內容 "和 mail -s " 信件標題 " 需要用 轉義+引號 " 引起來

[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>100){system("firewall-cmd --permanent --remove-source="i" --zone=block;firewall-cmd --reload;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" | mail -s \"Ban Info\" XXXXX@XX.com")}}}'Warning: NOT_ENABLED: 159.89.207.36
success
success
s-nail: Warning: variable superseded or obsoleted: smtp
s-nail: Warning: variable superseded or obsoleted: smtp-auth-user
s-nail: Warning: variable superseded or obsoleted: smtp-auth-password
s-nail: Warning: variable superseded or obsoleted: ssl-verify
s-nail: Obsoletion warning: please do not use *smtp*, instead assign a smtp:// URL to *mta*!
s-nail: Obsoletion warning: Use of old-style credentials, which will vanish in v15!
s-nail:   Please read the manual section "On URL syntax and credential lookup"

配置檔案提示語法過時,好歹郵件是過去了

最後查一遍語句寫入crontab

lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --permanent --add-source="i" --zone=block;firewall-cmd --reload;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" | mail -s \"Ban Info\" xxxx@xx.com")}}}'

上面寫法執行會報錯,只執行到 + 就停了,需要將日期時間格式裡的 % 轉義
之前在centos7上測試時的報錯提示

Message  3:
From root@centos7.localdomain  Thu Nov  7 00:05:01 2024
Return-Path: <root@centos7.localdomain>
X-Original-To: root
Delivered-To: root@centos7.localdomain
From: "(Cron Daemon)" <root@centos7.localdomain>
To: root@centos7.localdomain
Subject: Cron <root@centos7> lastb | awk -v hourago="$(date --date='1 hour ago' '+
Content-Type: text/plain; charset=UTF-8
Auto-Submitted: auto-generated
Precedence: bulk

crontab裡的語句

[root@RHEL9 ~]# crontab -l
30 * * * * lastb | awk -v hourago="$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --permanent --add-source="i" --zone=block;firewall-cmd --reload;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" | mail -s \"Ban Info\" XXXXX@XX.com")}}}'

防火牆規則

[root@RHEL9 ~]# firewall-cmd --get-active-zones 
block
  sources: 159.89.207.36
libvirt
  interfaces: virbr0
public
  interfaces: LANbridge WANbridge enp4s0 DMZbridge

已經不再登入了

[root@RHEL9 ~]# date;lastb | head
Thu Nov  7 02:34:50 AM CST 2024
gaoyuan  ssh:notty    159.89.207.36    Thu Nov  7 02:29 - 02:29  (00:00)
gaoyuan  ssh:notty    159.89.207.36    Thu Nov  7 02:29 - 02:29  (00:00)
gaojialu ssh:notty    159.89.207.36    Thu Nov  7 02:29 - 02:29  (00:00)
gaojialu ssh:notty    159.89.207.36    Thu Nov  7 02:29 - 02:29  (00:00)
fuyanjie ssh:notty    159.89.207.36    Thu Nov  7 02:29 - 02:29  (00:00)
fuyanjie ssh:notty    159.89.207.36    Thu Nov  7 02:29 - 02:29  (00:00)
fuyahui  ssh:notty    159.89.207.36    Thu Nov  7 02:28 - 02:28  (00:00)
cuilingh ssh:notty    159.89.207.36    Thu Nov  7 02:28 - 02:28  (00:00)
cuilingh ssh:notty    159.89.207.36    Thu Nov  7 02:28 - 02:28  (00:00)
cuilingh ssh:notty    159.89.207.36    Thu Nov  7 02:28 - 02:28  (00:00)

郵件

  • 最佳化為多IP版:
    目前的測試環境是每小時僅檢測出1條IP超出連線次數並拉黑,如果有多條IP滿足拉黑條件,每拉黑1個IP,就要reload1次,然後發1次郵件。例如awk檢索到3條滿足條件的IP,在很短時間內就要reload防火牆3次並連續發3封郵件,簡單粗暴不夠優雅但是也湊合用。考慮上述原因又做了兩點最佳化。
  1. 修改firewall語句
    原先為--permanent寫入配置檔案儲存再--reload讀取並生效
firewall-cmd --permanent --add-source="i" --zone=block;firewall-cmd --reload;

  改為先修改規則直接生效然後--runtime-to-permanent再寫入配置檔案儲存,避免了短時間反覆讀取配置檔案

firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;
  1. 將{system}語句塊裡的echo指令顯示的資訊寫入檔案/tmp/baninfo 將mail指令移出{system}語句塊
[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='22 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';
Warning: ZONE_ALREADY_SET: '159.89.207.36' already bound to 'block'
success
success
[root@RHEL9 ~]# cat /tmp/baninfo
RHEL9 ban 159.89.207.36 for connected 300 times in 1 hour

  判斷如果 /tmp/baninfo 存在,傳送郵件,成功後刪除/tmp/baninfo

[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
  • 完整指令
[root@RHEL9 ~]# lastb | awk -v hourago="$(date --date='1 hour ago' '+%a %b %e %H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" xxxxx@xx.com && rm -f /tmp/baninfo
  • crontab裡的指令
[root@RHEL9 ~]# crontab -l
0 * * * * lastb | awk -v hourago="$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
  • 實現指令碼化自動寫入crontab
[root@RHEL9 ~]# cat ban.sh 
#ban.sh
#Date: 2024-11-07
#!/bin/bash

cat << EOF >> /var/spool/cron/root
0 * * * * lastb | awk -v hourago="\$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '\$0~hourago{ip[\$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"\$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
EOF
[root@RHEL9 ~]# sh ./ban.sh
[root@RHEL9 ~]# crontab -l
0 * * * * lastb | awk -v hourago="$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
0 * * * * lastb | awk -v hourago="$(date --date='1 hour ago' '+\%a \%b \%e \%H:')" '$0~hourago{ip[$3]++}END{for (i in ip){if(ip[i]>11){system("firewall-cmd --add-source="i" --zone=block;firewall-cmd --runtime-to-permanent;echo \"$(hostname) ban "i" for connected "ip[i]" times in 1 hour\" >> /tmp/baninfo")}}}';[ -e /tmp/baninfo ] && cat /tmp/baninfo | mail -s "Ban Info" XXXXX@XX.com && rm -f /tmp/baninfo
  • 注意事項
    RHEL9最小化安裝沒有mail指令,發件前請確認主機安裝mail
  • 不足之處
    透過mail傳送需要設定.mailrc檔案,QQ郵箱需要加密我沒設定好,163郵箱不需加密目前我在用,但是認證碼半年到期就要換一次。我用的.mailrc配置檔案在CentOS7上正常,在RHEL9上提示快過時了,需要用新的語法,設定.mailrc的方法暫時不寫了,有時間再好好研究。

相關文章