手把手教你用python打造redis扫描器,支持多线程批量扫描

Table of Contents

预备知识

一、大端模式与小端模式

二、IP地址的不同表现形式

1.字符串表现形式

2.整数表现形式

3.大小端模式下的IP地址

三、Python的socket库

1.socket.socket(family,type)

2.socket.connect(address)

3.socket.connect_ex(address)

4.socket.settimeout(value)

5.socket.sendall(data)

6.socket.recv(bufsize)

7.socket.close()

四、Python的sys库

五、Redis服务特征识别

1.PING命令

2.AUTH命令

代码编写

六、编程实现Redis服务识别

七、密码字典爆破

优化

八、批量扫描同一网段下的主机

九、多线程扫描,加快速度!!!


预备知识

一、大端模式与小端模式

在内存中,数据的表示模式分为两种:大端模式和小端模式。

大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。

小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

在小端模式表示法下,整数0x78563412在内存中的表示如下图所示:

注意:Intel系列的CPU采用小端模式,而当数据在网络上传输时采用大端模式。

二、IP地址的不同表现形式

IP地址有几种不同的表现形式,理解这些表现形式以及它们之间的相互转换方法,是遍历指定范围内的IP地址的一个重要方法。

1.字符串表现形式

也就是通常所说的点分十进制形式,如192.168.1.1,这是我们最熟悉的一种表现形式。

2.整数表现形式

我们知道IPv4是32位的,而8位可以表示1个字节,也就是说,IPv4地址可以表示为4字节的数据,刚好可以表示为一个无符号int类型的数据。

那么字符串形式的IP地址如何转换为整数数值呢?因为点分十进制的IP中,每个被点分隔的数据占用1字节,可以表示的范围是0~255,所以可以认为这是一个256进制的数,这样转换就非常简单了。以IP地址220.181.111.188为例,其整数值为3702878140,计算过程为:

256^3*220+256^2*181+256^1*111+256^0*188=

256*256*256*220+256*256*181+256*111+188=3702878140

实际上220.181.111.188这个IP地址是pingwww.baidu.com得来的,所以在浏览器中访问http://3702878140/实际上访问的就是百度了。

3.大小端模式下的IP地址

因为涉及到网络传输,所以当IP地址转换为数值形式时,还存在大端和小端两种不同的形式。我们计算出来的3702878140是小端模式表示法下的值,在当做socket参数使用时,需要转换为大端模式。

三、Python的socket库

Python提供了一个socket库用于网络相关的编程,这里对其中几个重要的函数进行介绍。

1.socket.socket(family,type)

用于创建一个socket;family参数指定套接字的家族,在IPv4网络编程中值固定为socket.AF_INET;type参数表明套接字的类型是UDP还是TCP,UDP使用socket.SOCK_DGRAM,TCP使用socket.SOCK_STREAM。

2.socket.connect(address)

与指定的服务器建立通信连接,其中address是一个元组(ip,port),其中IP为字符串,port为数值,如("192.168.1.1",6379)。如果连接失败,该函数会抛出一个异常。

3.socket.connect_ex(address)

与指定的服务器建立通信连接,其中address是一个元组(ip,port),其中IP为字符串,port为数值,如("192.168.1.1",6379)。连接成功时函数返回0,否则返回非0值。

4.socket.settimeout(value)

当使用socket.connect()或socket.connect_ex()连接服务器时,在连通之前会阻塞一段时间,如果无法连通的话可能会阻塞很久,这会浪费许多时间。因此,可以使用settimeout函数设置一个超时时间,value是秒钟数,表示如果在这个时间内无法连接则直接返回。

5.socket.sendall(data)

立即把参数data指定的数据发送给远程服务器,其中data是字符串类型,其中可以存储任意的二进制数据。

6.socket.recv(bufsize)

从远程服务器接收bufsize字节的数据。

7.socket.close()

关闭与远程服务器的socket连接。

四、Python的sys库

在使用C语言编写命令行程序是,main函数提供了两个参数intargc和char**argv,其中argc指定命令行参数的个数,argv则存储具体的命令行参数。其中,argv[0]是命令行程序本身的名字,argv[1]存储第一个命令行参数,argv[2]存储第二个命令行参数,以此类推。

在Python中,可以通过sys库的argv参数获取命令行参数的值,即sys.argv[0]、sys.argv[1]等,通过len(sys.argv)可以获取命令行参数的个数。

五、Redis服务特征识别

在编写安全扫描器之前,我们遇到的第一个问题是:如何识别指定的端口上运行的服务是否是Redis服务?首先,Redis服务并不一定只能在6379端口上进行监听,这个选项可以在Redis配置文件redis.conf里面进行修改;其次,即使6379端口处于开放状态,我们也需要对其进行判断是否是Redis服务。扫描器一般都通过端口返回的交互数据来判别端口上运行的具体服务,识别Redis服务也不例外。

首先,介绍一下Redis的PING和AUTH命令。

1.PING命令

在成功连接上Redis服务器之后,客户端往服务器发送PING命令,服务器会给客户端返回PONG这个字符串。

2.AUTH命令

如果Redis服务设置了连接密码,那么首先需要通过AUTH命令确认登陆密码。

 

从上面的操作步骤,我们已经可以总结出识别Redis服务的方法了:

1.指定的端口是否开放TCP服务;

2.执行PING命令:

a)如果提示

(error)NOAUTHAuthenticationrequired.表明是Redis服务

且需要登录密码;

b)如果提示PONG,表明是Redis服务,且无需登录密码;

c)提示其他结果,表明不是Redis服务;

3.如果需要登录密码,执行AUTH命令:

a)如果提示(error)ERRinvalidpassword,表明密码错误;

  b)如果提示OK,表明密码正确;

代码编写

六、编程实现Redis服务识别

从上面的相关知识我们已经知道了鉴定Redis服务的方法,那么编写代码实现就很简单了,在这里我们使用Python语言来实现。

在编写代码之前,我们还需要知道的一点是:客户端通过socket往服务器发送命令时,需要在后面加上回车换行,即\r\n;我们在使用redis-cli发送PING命令时,redis-cli会自动加上\r\n,拼接成PING\r\n。在编程实现扫描器时,我们需要自己加上\r\n。

  识别Redis服务的代码如下所示

#判断是否为redis服务def is_redis_server(ip, port):    """      参数ip:字符串形式IP地址      参数port:数值形式端口号,如6379      返回值:-1 端口未开放,或者开放但不是Redis服务              0   为Redis服务,但需要密码              1   为Redis服务,不需要密码    """    # 创建一个TCP类型的socket    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    s.settimeout(2.0)    # 尝试连接端口,如果返回值不为0,表示端口没有开放    if s.connect_ex((ip, port)) != 0:        return -1    s.sendall("PING\r\n")    msg = s.recv(1024)    res = -1    # 如果返回值包含PONG,成功且无密码    if msg.find("PONG") != -1:        res = 1    # 如果返回值如下,则表示需要密码    elif msg.find("NOAUTH") != -1:        res = 0    # 否则不是Redis服务,res = -1    # 关闭socket链接    s.close()    return res

代码封装在is_redis_server函数中,传入参数为IP地址以及端口号,返回-1表明端口未开放或者开放但不是Redis服务,返回0表明是Redis服务但是需要访问密码;返回1表明是Redis服务且不需要访问密码。

在上面的代码中,我们没有使用connect函数连接到目标服务器,而是使用了connect_ex函数。因为前者在连接失败的情况下会抛出一个异常,我们需要在代码中加入异常处理的代码;而使用connect_ex直接判断返回值即可,可以使得代码更加的简洁。

加入主函数:

if __name__ =="__main__":    ip = "127.0.0.1"    port = 6379    res = is_redis_server(ip,port)    print res

这样我们就可以简单的扫描目标主机的6379端口是否为Redis服务

七、密码字典爆破

扫描到有密码怎么办呢???

作为一个攻击者,有密码就放弃了嘛?NONONO~

当然是淦!它!啊!

当我们扫描到一台需要密码的Redis服务器时,是否可以进行密码猜解呢?答案显然是可以的,因为Redis并没有限制客户端输入登录密码的次数。出于安全检测的目的,我们只对其进行弱口令检查。

实现弱口令爆破的思路很简单,我们不断的往Redis服务器发送AUTH命令即可,如果返回结果包含字符串invalidpassword,表明密码错误,如果返回结果包含字符串OK,则表明密码正确。那么弱口令字典哪里来呢?这里我们以CSDN泄露的数据库中最常用的100个密码建立一个弱口令字典(来自文章http://coolshell.cn/articles/6193.html)当然你可以找自己得专属字典。

我们将爆破密码的代码封装在check_password函数中,函数首先读取dict.txt文件的密码列表,随后遍历列表中的密码并生成AUTH命令,将生成的AUTH命令发送到服务器,根据服务器的返回信息判断密码是否正确:如果返回的信息包含OK则表明密码正确。具体的代码如下所示:

def Check_password(s):    """    s:已连接redis服务器的socket    返回值:密码字符串,失败返回None    """    fp = open("dict.txt")  #打开密码字典    passwords = fp.readline()    fp.close()    for pwd in passwords:        #删除末尾的“\r”,"\n","\r\n"        pwd = pwd.strip()        s.sendall("AUTH %s \r\n" %pwd)        msg = s.recv(1024)        if msg.find("OK") != -1:            return pwd    return None

现在,我们只需要稍微修改is_redis_server函数即可,在其中加入对

check_password的调用。具体的改动如下图中的红框所示:

这就好了?NONONO

只扫一台可不是我的风格~

优化

八、批量扫描同一网段下的主机

网段扫描功能,即可以指定要扫描的IP范围。这里扫描IP范围直接通过命令行参数指定,如10.1.1.110.1.1.255表明共有255台主机需要扫描,那么如何遍历这255个IP地址呢?运用预备知识中介绍的内容,可以十分方便的进行遍历:

1.将字符串形式的点分十进制IP地址转换为数值,首先使用split将IP地址进行分离,比如"10.1.1.47".split("."),这样各个点之间的数据就分离了,得到列表["10","1","1","47"],随后将列表中的元素从字符串转换为int,并乘以相应的系数后累加,代码如下:

def ip_str2int(ip):     tmp = ip.split(".")    a1 = int(tmp[0])*256*256*256    a2 = int(tmp[1])*256*256    a3 = int(tmp[2])*256    a4 = int(tmp[3])    ip = a1 + a2 + a3 + a4    return ip

2.通过步骤1,我们就可以计算出字符串IP地址对应的数值范围了,通过for循环遍历这个范围即可。遍历得到的数值IP还需要转换为字符串,这里通过位运算中的“与操作”以及“移位操作”实现。例如IP地址17.34.51.68对应的数值形式为0x11223344(16进制),那么0x11223344&0xFF000000得到0x11000000,再向右移动24位就可以得到0x11,即10进制的17。对应的代码如下:

def ip_int2str(ip):    a1 = (ip&0xFF000000)>>24    a2 = (ip&0x00FF0000)>>16    a3 = (ip&0x0000FF00)>>8    a4 = ip&0x0000000FF    ip = "%d.%d.%d.%d" %(a1,a2,a3,a4)    return ip

网段范围扫描的代码封装在scan函数中,其中beg_ip通过sys.argv[1]获取,end_ip通过sys.argv[2]获取,具体的代码如下所示:

def scan(beg_ip,end_ip):    """对指定ip返回内的主机进行检测"""    #将点分十进制ip,转化成数值    beg_ip = ip_str2int(beg_ip)    end_ip = ip_str2int(end_ip)    #遍历数值ip返回    for ip in range(beg_ip,end_ip+1):        ip = ip_int2str(ip)        res,pwd = is_redis_server(ip,6379)        if res ==1:            print(ip)        elif res==0 and pwd!=None:            print ("%s  -> %s"%(ip,pwd))    print("Scan Done!")

 然后在添加主函数

if __name__ =="__main__":    if len(sys.argv)== 3:        scan(sys.argv[1],sys.argv[2])

运行效果

扫描了10台主机,发现一台是redis服务器,并且密码为foobared

九、多线程扫描,加快速度!!!

作为一个做梦都想成为黑客的小伙(cai)子(ji),这就够了??

我们还要速度,一台一台扫描,如果网段下主机很多的话,无疑会浪费很长时间,所以~~~尝试将其改造成一款多线程扫描器

实现一个多线程的扫描器并不困难,我们将目前单线程的代码进行封装,然后使用python的threading库,便可以轻松实现多线程任务。

运行效果:

完整代码:https://github.com/kinnisoy/Redis_serv_scanner

基础相关知识来源:合天网安实验室

欢迎大家关注我的微信公众号,分享一些简单有趣的东西。

原创文章 43 获赞 63 访问量 3万+

猜你喜欢

转载自blog.csdn.net/kinnisoy/article/details/105489422