Linux shell 获取SSH暴力破解攻击黑名单列表及iptables使用

SSH是Secure shell的简称,是一种可以用来加密连接的服务器的标准协议,使用SSH远程管理服务器,可以有效的防止信息泄露,目前几乎所有的UNIX服务器都会支持该协议。

虽然SSH属于加密连接,但是如果攻击者使用暴力破解的方式破解远程密码,服务器中的数据依然有被盗取的危险,特别是在使用弱密码的情况下更是如此。暴力破解是攻击者使用密码字典中的密码逐一枚举,分别尝试每个密码是否可以登录服务器,如果字典中的密码足够多,并且不限制时间,理论上一定是可以破解成功的。

作为管理员,我们需要识别这些攻击并能够拦截。Centos系统中有一个独立的登录日志文件/var/log/sercure,该文件中记录了系统的登录日志。

使用SSH远程登录服务器密码

如果使用SSH远程登录服务器密码,日志文件中会包含如下的记录信息,。

Failed password for minger from 192.168.43.229 port 1234 ssh2

远程连接使用的账户名称的minger,但是连接时密码错误。

使用SSH远程登录服务器时账户名错误

如果使用SSH远程登录服务器时账户错误,日志文件就会包含如下记录信息。

Invalid usre test from 192.168.43.229 port 1234

远程连接时使用的是无效的账户名称test,一台IP为 192.168.43.229 的主机使用1234端口连接本机的SSH(22)端口

SSH远程登录服务器成功

如果使用SSH远程登录服务器时成功,日志文件就会包含如下的记录信息。

Accepted password for minger from 192.168.43.229 port 2082 ssh2

有一台192.168.43.229的主机使用2082端口连接本机的SSH端口,登录账户名称minger,密码校验成功。

编写脚本过滤异常登录信息

了解了这些日志内容后,就可以编写脚本过滤异常登录的信息,并提取远程IP地址,然后将提取的IP地址写入黑名单,禁止该IP地址再次攻击服务器。编写脚本时我们可以创建3个函数,分别判断最近1分钟、5分钟、15分钟的日志内容,查看是否能异常记录,如果相同的异常记录超过3 次,则提取产生该异常的IP地址并将其写入黑名单,最终可以根据黑名单文件编写防火墙策略,禁止该IP的再次攻击。像银行的密码系统一样,如果我们输入错误密码大于N次之后,当天账户会被锁定,但是第二天又可以继续测试密码。针对远程登录也是同样的道理,针对失败次数大于等于3次的IP地址,我们可以只需要禁用该IP特定的时间,比如20分钟,这样我们就需要再编写一个函数,用来清理黑名单中的时间大于20分钟的地址。

#!/bin/bash
#功能描述(Description):分析系统登陆日志,过滤异常IP地址,并通过防火墙禁用该IP.

#强制退出时关闭所有后台进程.
trap 'kill $one_pid; kill $five_pid; kill $fifteen_pid; exit' EXIT INT

#日志文件路径.
LOGFILE=/var/log/secure
BLOCKFILE=/tmp/blockip.txt

one_minute(){
    while :
    do
        #获取计算机当前时间,以及1分钟前的时间,时间格式:
        #%b(月份简写,Jan)  %e(日期,1) %T(时间 18:00:00)
        #LANG=C的作用是否防止输出中文.
        #使用local定义局部变量的好处是多个函数使用相同的变量名也不会冲突.
        local curtime_month=$(LANG=C date  +"%b")
        local curtime_day=$(LANG=C date  +"%e")
        local curtime_time=$(LANG=C date  +"%T")
        local one_minus_ago=$(LANG=C date -d "1 minutes ago" +"%T")
        #将当前时间转换为距离1970-01-01 00:00:00的秒数,方便后期计算.
        local curtime_seconds=$(LANG=C date +"%s")
        #分析1分钟内所有的日志,如果密码失败则过滤倒数第4列的IP地址.
        #通过管道对过滤的IP进行计数统计,提取密码失败次数大于等于3次的IP地址.
        #awk调用外部Shell的变量时,双引号在外面表示字符串("''"),单引号在外边表示数字('""').
        pass_fail_ip=$(awk '
                       $1=="'$curtime_month'" &&   \
                       $2=='"$curtime_day"'   &&   \
                       $3>="'$one_minus_ago'" &&   \
                       $3<="'$curtime_time'"       \
                       { if($6=="Failed" && $9!="invalid") {print $(NF-3)}}' $LOGFILE | \
                       awk '{IP[$1]++} END{ for(i in IP){ if(IP[i]>=3) {print i} } }')
        #将密码失败次数大于3次的IP写入黑名单文件,
        #每次写入前都需要判断黑名单中是否已经存在该IP.
        #写入黑名单时附加时间标记,实现仅将IP放入黑名单特定的时间,
        #如:密码失败3次后,禁止该IP在20分钟内再次访问服务器.
        for i in $pass_fail_ip
        do
           if ! grep -q "$i" $BLOCKFILE ;then
               echo "$curtime_seconds $i" >> $BLOCKFILE
           fi
        done
        #提取无效账户登陆服务器3次的IP地址,并将其加入黑名单.
        user_invalid_ip=$(awk '
                       $1=="'$curtime_month'" &&   \
                       $2=='"$curtime_day"'   &&   \
                       $3>="'$one_minus_ago'" &&   \
                       $3<="'$curtime_time'"       \
                       { if($6=="Invalid") {print $(NF-2)}}' $LOGFILE | \
                       awk '{IP[$1]++} END{ for(i in IP){ if(IP[i]>=3) {print i} } }')
        for j in $user_invalid_ip
        do
           if ! grep -q "$j" $BLOCKFILE ;then
               echo "$curtime_seconds $j" >> $BLOCKFILE
           fi
        done
        sleep 60
    done
}

five_minutes(){
    while :
    do
        #获取计算机当前时间,以及5分钟前的时间,时间格式:
        #%b(月份简写,Jan)  %e(日期,1) %T(时间 18:00:00)
        #使用local定义局部变量的好处是多个函数使用相同的变量名也不会冲突.
        local curtime_month=$(LANG=C date  +"%b")
        local curtime_day=$(LANG=C date  +"%e")
        local curtime_time=$(LANG=C date  +"%T")
        local one_minus_ago=$(LANG=C date -d "5 minutes ago" +"%T")
        #将当前时间转换为距离1970-01-01 00:00:00的秒数,方便后期计算.
        local curtime_seconds=$(LANG=C date +"%s")
        #分析5分钟内所有的日志,提取3次密码错误的IP地址并加入黑名单.
        pass_fail_ip=$(awk '
                       $1=="'$curtime_month'" &&   \
                       $2=='"$curtime_day"'   &&   \
                       $3>="'$one_minus_ago'" &&   \
                       $3<="'$curtime_time'"       \
                       { if($6=="Failed" && $9!="invalid") {print $(NF-3)}}' $LOGFILE | \
                       awk '{IP[$1]++} END{ for(i in IP){ if(IP[i]>=3) {print i} } }')
        for i in $pass_fail_ip
        do
           if ! grep -q "$i" $BLOCKFILE ;then
               echo "$curtime_seconds $i" >> $BLOCKFILE
           fi
        done
        #提取错误用户名登陆服务器3次的IP地址,并将其加入黑名单.
        user_invalid_ip=$(awk '
                       $1=="'$curtime_month'" &&   \
                       $2=='"$curtime_day"'   &&   \
                       $3>="'$one_minus_ago'" &&   \
                       $3<="'$curtime_time'"       \
                       { if($6=="Invalid") {print $(NF-2)}}' $LOGFILE | \
                       awk '{IP[$1]++} END{ for(i in IP){ if(IP[i]>=3) {print i} } }')
        for j in $user_invalid_ip
        do
           if ! grep -q "$j" $BLOCKFILE ;then
               echo "$curtime_seconds $j" >> $BLOCKFILE
           fi
        done
        sleep 300
    done
}

fifteen_minutes(){
    while :
    do
        #获取计算机当前时间,以及15分钟前的时间,时间格式:
        #%b(月份简写,Jan)  %e(日期,1) %T(时间 18:00:00)
        #使用local定义局部变量的好处是多个函数使用相同的变量名也不会冲突.
        local curtime_month=$(LANG=C date  +"%b")
        local curtime_day=$(LANG=C date  +"%e")
        local curtime_time=$(LANG=C date  +"%T")
        local one_minus_ago=$(LANG=C date -d "15 minutes ago" +"%T")
        #将当前时间转换为距离1970-01-01 00:00:00的秒数,方便后期计算.
        local curtime_seconds=$(LANG=C date +"%s")
        #分析15分钟内所有的日志,提取3次密码错误的IP地址并加入黑名单.
        pass_fail_ip=$(awk '
                       $1=="'$curtime_month'" &&   \
                       $2=='"$curtime_day"'   &&   \
                       $3>="'$one_minus_ago'" &&   \
                       $3<="'$curtime_time'"       \
                       { if($6=="Failed" && $9!="invalid") {print $(NF-3)}}' $LOGFILE | \
                       awk '{IP[$1]++} END{ for(i in IP){ if(IP[i]>=3) {print i} } }')
        for i in $pass_fail_ip
        do
           if ! grep -q "$i" $BLOCKFILE ;then
               echo "$curtime_seconds $i" >> $BLOCKFILE
           fi
        done
        #提取错误用户名登陆服务器3次的IP地址,并将其加入黑名单.
        user_invalid_ip=$(awk '
                       $1=="'$curtime_month'" &&   \
                       $2=='"$curtime_day"'   &&   \
                       $3>="'$one_minus_ago'" &&   \
                       $3<="'$curtime_time'"       \
                       { if($6=="Invalid") {print $(NF-2)}}' $LOGFILE | \
                       awk '{IP[$1]++} END{ for(i in IP){ if(IP[i]>=3) {print i} } }')
        for j in $user_invalid_ip
        do
           if ! grep -q "$j" $BLOCKFILE ;then
               echo "$curtime_seconds $j" >> $BLOCKFILE
           fi
        done
        sleep 1200
    done
}

#每隔20分钟检查一次黑名单,清理大于20分钟的黑名单IP.
clear_blockip(){
    while :
    do
        sleep 1200
        #将当前时间转换为距离1970-01-01 00:00:00的秒数,方便后期计算.
        local curtime_seconds=$(LANG=C date +"%s")
        #awk调用外部shell变量的另一种方式是使用-v选项.
        #当前时间减去黑名单中的时间标记,大于等于1200秒(20分钟)则将其从黑名单中删除.
        tmp=$(awk -v now=$curtime_seconds '(now-$1)>=1200 {print $2}' $BLOCKFILE)
        for i in $tmp
        do
            sed -i "/$i/d" $BLOCKFILE
        done
    done
}

> $BLOCKFILE
one_minute  &
one_pid="$!"
five_minutes &
five_pid="$!"
fifteen_minutes &
fifteen_pid="$!"
clear_blockip 

该脚本最终仅生成黑名单文件,如果需要拒绝黑名单ip地址的访问,还可以编写脚本读取黑名单名单文件并结合iptable防火墙规则,即可实现拒绝特定的IP访问本机。

iptables防火墙配置详解

Linux具有许多内置的能力,使开发人员可以根据自己的需要定制其工具、行为和外观,而无需昂贵的第三方工具。

如果Linux系统连接到因特网或LAN、服务器或连接LAN和因特网的代理服务器,所要用到的一种内置能力就是针对网络上Linux系统的防火墙配置。可以在netfiter/iptablesP信息包过滤系统的帮助下运用这种能力。对于Linux系统管理员、网络管理员以及家庭用户,他们想要根据自己特定的需求来配置防火墙、在防火墙解决方案上节省费用和对IP信息包过滤具有完全控制权来说, netilter/iptables系统十分理想。

netfilter/iptables(简称为iptables)组成Linux平台下的包过滤防火墙,与大多数的Linux软件一样,这个包过滤防火墙是免费的,它可以代替昂贵的商业防火墙解决方案,完成封包过滤、封包重定向和网络地址转换(NAT)等功能。

如果您的发行版本没有安装iptables ,可以使用命令进行安装:

yum install iptables

语法

iptables(选项)(参数)

常用选项:

-t<>:指定要操纵的表;
-A:向规则链中添加条目;
-D:从规则链中删除条目;
-i:向规则链中插入条目;
-R:替换规则链中的条目;
-L:显示规则链中已有的条目;
-F:清楚规则链中已有的条目;
-Z:清空规则链中的数据包计算器和字节计数器;
-N:创建新的用户自定义规则链;
-P:定义规则链中的默认目标;
-h:显示帮助信息;
-p:指定要匹配的数据包协议类型;
-s:指定要匹配的数据包源ip地址;
-j<目标>:指定要跳转的目标;
-i<网络接口>:指定数据包进入本机的网络接口;
-o<网络接口>:指定数据包要离开本机所使用的网络接口。

iptables常用命令

开放指定的端口

允许本地回环接口(即运行本机访问本机):

#INPUT——进来的数据包应用此规则链中的策略
iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT       

允许已建立的或相关连的通行:

#ACCEPT 允许数据包通过
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

允许所有本机向外的访问:

#OUTPUT——外出的数据包应用此规则链中的策略
iptables -A OUTPUT -j ACCEPT 

允许访问22端口:

iptables -A INPUT -p tcp --dport 22 -j ACCEPT

允许访问80端口

iptables -A INPUT -p tcp --dport 80 -j ACCEPT 

允许ftp服务的21端口:

iptables -A INPUT -p tcp --dport 21 -j ACCEPT

允许FTP服务的20端口:

iptables -A INPUT -p tcp --dport 20 -j ACCEPT

禁止其他未允许的规则访问:

iptables -A INPUT -j reject 

禁止其他未允许的规则访问:

iptables -A FORWARD -j REJECT     

拒绝访问防火墙的新数据包,但允许响应连接或与已有连接相关的数据包:

iptables -A INPUT -p tcp -m state --state NEW -j DROP
iptables -A INPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT

“ESTABLISHED”表示已经响应请求或者已经建立连接的数据包,“RELATED”表示与已建立的连接有相关性的,比如FTP数据连接等。

屏蔽IP

拒绝进入防火墙的所有ICMP协议数据包:

iptables -I INPUT -p icmp -j REJECT

允许防火墙转发除ICMP协议以外的所有数据包:

iptables -A FORWARD -p ! icmp -j ACCEPT

注意:使用“!”可以将条件取反。

屏蔽单个IP的命令:

#DROP 直接丢弃数据包,不给任何回应信息
iptables -I INPUT -s 192.45.6.7 -j DROP  

封整个段即从192.0.0.1到192.255.255.254的命令:

iptables -I INPUT -s 192.0.0.0/8 -j DROP

封IP段即从192.45.0.1到192.168.255.254的命令:

iptables -I INPUT -s 192.168.0.0/16 -j DROP

封IP段即从192.168.6.1到192.168.6.254的命令是:

 
iptables -I INPUT -s 192.168.6.0/24 -j DROP

禁止其他主机ping防火墙主机,但是允许从防火墙上ping其他主机:

iptables -I INPUT -p icmp --icmp-type Echo-Request -j DROP
iptables -I INPUT -p icmp --icmp-type Echo-Reply -j ACCEPT
iptables -I INPUT -p icmp --icmp-type destination-Unreachable -j ACCEPT

查看已添加的iptables规则

iptables -L -n -v
在这里插入图片描述

常用的 Linux iptables 规则

删除所有现有规则:

iptables -F

防止 DoS 攻击:

iptables -A INPUT -p tcp --dport 80 -m limit --limit 25/minute --limit-burst 100 -j ACCEPT

阻止某个特定的 IP 地址:

BLOCK_THIS_IP="x.x.x.x"
iptables -A INPUT -s "$BLOCK_THIS_IP" -j DROP

仅允许来自某个特定网络的 MySQL 的链接:

iptables -A INPUT -i eth0 -p tcp -s 192.168.200.0/24 --dport 3306 -m state --state NEW,ESTABLISHED -j ACCEPT

允许全部进来的SSH:

iptables -A INPUT -i eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

只允许某个特定网络进来的 SSH:

iptables -A INPUT -i eth0 -p tcp -s 192.168.3.0/24 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

允许出去的SSH:

iptables -A OUTPUT -o eth0 -p tcp --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

允许外出的SSH,但仅访问某个特定的网络:

iptables -A OUTPUT -o eth0 -p tcp -d 192.168.101.0/24 --dport 22 -m state --state NEW,ESTABLISHED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT

多端口(允许进来的 SSH、HTTP 和 HTTPS):

iptables -A INPUT -i eth0 -p tcp -m multiport --dports 22,80,443 -m state --state NEW,ESTABLISHED -j ACCEPT

iptables -A OUTPUT -o eth0 -p tcp -m multiport --sports 22,80,443 -m state --state ESTABLISHED -j ACCEPT

总结

对于连接到网络上的Linux系统来说,防火墙是必不可少的防御机制,它只允许合法的网络流量进出系统,而禁止其它任何网络流量。为了确定网络流量是否合法,防火墙依靠它所包含的由网络或系统管理员预定义的一组规则。

出于各种因素和原因,需要根据特定需求来配置防火墙。或许,最重要的原因是安全性。

参考:http://netfilter.org/ iptables官方网站
http://www.linux.gov.cn/netweb/iptables.htm iptables配置手册

在这里插入图片描述
欢迎关注微信公众号【程序猿编码】,添加本人微信号(17865354792),回复:领取学习资料。或者回复:进入技术交流群。网盘资料有如下:

在这里插入图片描述


版权声明:本文为chen1415886044原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。