ssh暴力破解阻断

暴力破解的自动阻断

项目背景:

只要是暴漏在公网云服务器,每天都会有大量的暴力破解行为

解决方法:

1.更换端口(无济于事)
2.买云防护,waf (贵)

爆破破解的一个自动阻断

re正则表达式

re模块

findall 找到匹配到的所有子串,并返回一个列表,如果没有匹配到就返回空

image-20230905100541279

search函数 扫描整个字符串,并且返回第一个,如果没匹配到就返回空

image-20230905100937856

匹配元字符

1
2
3
4
\d               匹配所有数字0-9
\D 匹配非数字
\w 匹配所有单词字符,包括大小写字母 数字 下划线 中文
\W 匹配剩下的,空格 换行符 特殊字符

image-20230905101528009

匹配字符集

1
2
3
4
字符的集合,用[]表示,字符集内用“^”表示“非”
\d=[0-9]
\D=[ ^ 0-9]
\w!=[a-zA-Z_0-9]??? 还有中文

image-20230905102515659

匹配空白字符

1
2
3
4
空白符包含:
' '空格 \n换行符 \t制表符 \r回车符
使用\s来匹配他们
[^\s] = \S

image-20230905102757945

**{} **表示匹配标定字符数量

image-20230905103436862

特殊数量符号

1
2
3
4
特殊数量符号:
* 匹配前一个字符0or无限次
+ 匹配前一个字符1or无限次
. 匹配除换行符\n意外的任意1个字符

image-20230905104011139

字符组
把字符用()括起来,叫字符组,目的就为将匹配成的字符串分组

image-20230905104828290

匹配参数模式

1
2
3
4
findall方法其实他有第三个参数,他是默认参数,参数模式。
re.I 忽略大小写
re.S 匹配空白符
多个参数用 “|”
image-20230905105310566

subprocess模块

项目需求:执行命令并将输出劫持实现日志的监控

用于启动新的进程的模块,它可以用于执行外部命令,获取进程的输出,向进程发送输入和等待进程结束。

多进程协同,python里大概有这么三种方式
1.os.system函数
阻塞式
2.multiprocessing 模块
使用场景,密集型的计算

3.subprocess模块

1
2
3
4
subprocess.run   执行指定的命令,等待命令执行完成后返回一个对象
subprocess.call 执行指定的命令,返回命令执行的状态 (成功为0 失败为非0
subprocess.check_all 与上面类似,区别是会输出报错
subprocess.getoutput 执行命令,返回结果

参数说明:

1
2
3
4
5
6
7
8
subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, universal_newlines=False)

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

args:要执行的命令,必须是个字符串,字符串参数列表
stdin stdout stderr : 子进程的标准输入 输出 错误 最常用的是subprocess.PIPE
timeout : 设置命令超时
shell : 值为bool,如果参数为ture 将通过操作系统的shell执行命令
image-20230905111931877 image-20230905112026716 image-20230905112414933
1
2
3
4
5
6
7
8
9
10
11
12
13
14
subprocess.Popen (它是上述几个方法的父类)
process = subprocess.Popen(
cmd,
shell=True,
stdout = subprocess.PIPE,
stdin = subprocess.PIPE,
stderr = subprocess.PIPE)

shell: 打开终端,Linux中是终端,Windows中是cmd
stdout=subprocess.PIPE:如果该命令执行成功,那么将该命令的标准输出放入管道

终端输入命令分两种:
1.直接在终端输入
2.进入环境再输入,比如python

在shell中执行命令:

image-20230905141601763

在python终端中执行命令:

image-20230905141623837

命令联动:

image-20230905142517181

image-20230905142454064

Linux日志分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/var/log/cron     记录系统的定时任务
/var/log/cups 打印信息的日志
/var/log/message 记录的系统重要信息的日志
/var/log/btmp 登录失败
/var/log/lastlog 最后一次登录
/var/log/wtmp 成功登录记录
/var/log/secure 登录日志
/var/log/utmp 目前登录用户的信息

登录成功 Accepted password for root from 172.30.230.1 port 59865 ssh2
登录失败 Failed password for root from 172.30.230.1 port 59891 ssh2

常用命令:
find grep awk sed cat tail head

案例:

1
2
/etc/passwd从第10行开始显示5行
cat /etc/passwd | tail -n +10 | head -n 5

image-20230905145051484

1
2
只显示/etc/passwd的账户名
cat /etc/passwd | awk -F ':' '{print $1}'
image-20230905145301828
1
2
3
4
定位有多少个ip在爆破主机的root账户   从/var/log/secure中分析
登录失败:grep "Failed password for root" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -nr

登录成功:grep "Accepted" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -nr

image-20230905150423999

image-20230905150508377

封禁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hosts黑名单          /etc/hosts.deny
hosts白名单 /etc/hosts.allow
配置格式:
服务:地址:允许/封禁
服务:
ssh ftp smb telnet 关键字(all):禁止或允许运行所有的服务
all:192.168.0.10:deny(全封)
all:192.168.0.10:allow(加白)

地址:
192.168.0.10
192.168.0.10/24(封C段)
192.168.0.*(封C段)
192.168.0. (封C段)
1
2
3
4
5
6
7
8
9
密码爆破 hydra
hydra -l root -P 密码字典.txt -vV -e ns 10.210.100.131 ssh
-l 指定用户
-L 指定用户字典
-P 指定密码字典
-vV 显示提示信息
-e ns 允许为空密码
ip
爆破的服务
image-20230905152954772
1
2
封禁:
sshd:10.210.100.128:deny
image-20230905153708461

攻击机无法连接和爆破

image-20230905153851379

image-20230905153939709

项目实现思路

1.打开安全日志

2.实时监控安全日志

3.解析日志每一行的内容,找出正在爆破的ip

4.设置一个阈值,超过阈值后,封禁ip 添加到hosts.deny中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env python3

import re
import subprocess
import time

#安全日志
logFile = '/var/log/secure'
#黑名单
hostDeny = '/etc/hosts.deny'
#封禁阈值
password_wrong_num = 5

#获取已经加入黑名单的ip,转换为字典
def getDenies():
deniedDict = {}
list = open(hostDeny).readlines()
for ip in list:
group = re.search(r'(\d+\.\d+\.\d+\.\d+)',ip)
if group:
deniedDict[group[1]] = '1' #标记该ip为1
return deniedDict

#监控方法
def monitorLog(Logfile):
#统计密码错误的次数
tempIp = {}
#已经存到黑名单中的ip
deniedDict = getDenies()
#读取安全日志
popen = subprocess.Popen('tail -f '+logFile,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)

#开始监控
while True:
time.sleep(0.1)
#读取日志
line = popen.stdout.readline().strip() #str.strip() 去除字符串两边的空格
if line:
#出现Failed 说明:这个用户存在,但是密码错误
#出现Invalid 说明:这个用户都不存在
group = re.search('Invalid user \w+ from (\d+\.\d+\.\d+\.\d+)',str(line))
#不存在用户直接封
if group and not deniedDict.get(group[1]):
subprocess.getoutput('echo \'sshd:{}\' >> {}'.format(group[1],hostDeny))
deniedDict[group[1]] = '1'
time_str = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
print('{} --- and ip:{} to hosts.deny for invalid usr'.format(time_str,group[1]))
continue
#用户名合法 (存在该用户) 密码错误
group = re.search('Failed password for \w+ from (\d+\.\d+\.\d+\.\d+)',str(line))
if group:
ip = group[1]
#统计ip错误的次数
if not tempIp.get(ip):
tempIp[ip] = 1
else:
tempIp[ip] = tempIp[ip]+1
#如果错误次数大于阈值,封禁该ip
if tempIp[ip] > password_wrong_num and not deniedDict.get(ip):
del tempIp[ip]
subprocess.getoutput('echo \'sshd:{}\' >> {}'.format(ip,hostDeny))
deniedDict[ip]='1'
time_str = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))
print('{} --- and ip:{} to hosts.deny for invalid password'.format(time_str,group[1]))

if __name__ == '__main__':
monitorLog(logFile)

其中group[1]即匹配到的ip

image-20230929155319929

靶机启动脚本:

image-20230905170317267

攻击机执行爆破:

image-20230905170308672

image-20230929160659881


ssh暴力破解阻断
http://example.com/2023/09/29/ssh暴力破解阻断/
作者
r1
发布于
2023年9月29日
许可协议