Add files via upload

This commit is contained in:
公明
2026-08-06 17:14:06 +08:00
committed by GitHub
parent d3714c9913
commit ed47ee202e
5 changed files with 238 additions and 19 deletions
+12 -12
View File
@@ -291,16 +291,13 @@ func (l *HTTPBeaconListener) handleResult(w http.ResponseWriter, r *http.Request
} }
var report TaskResultReport var report TaskResultReport
plaintext, decErr := DecryptAESGCM(l.rec.EncryptionKey, string(body)) plaintext, decErr := DecryptAESGCM(l.rec.EncryptionKey, string(body))
if decErr == nil { if decErr != nil {
if err := json.Unmarshal(plaintext, &report); err != nil { l.disguisedReject(w)
l.disguisedReject(w) return
return }
} if err := json.Unmarshal(plaintext, &report); err != nil {
} else { l.disguisedReject(w)
if err := json.Unmarshal(body, &report); err != nil { return
l.disguisedReject(w)
return
}
} }
if err := l.manager.IngestTaskResult(report); err != nil { if err := l.manager.IngestTaskResult(report); err != nil {
http.Error(w, "ingest result failed", http.StatusInternalServerError) http.Error(w, "ingest result failed", http.StatusInternalServerError)
@@ -341,12 +338,15 @@ func (l *HTTPBeaconListener) handleUpload(w http.ResponseWriter, r *http.Request
l.disguisedReject(w) l.disguisedReject(w)
return return
} }
dir := filepath.Join(l.manager.StorageDir(), "uploads") dir, dst, err := uploadPathForTask(l.manager.StorageDir(), taskID)
if err != nil {
l.disguisedReject(w)
return
}
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0o755); err != nil {
http.Error(w, "mkdir failed", http.StatusInternalServerError) http.Error(w, "mkdir failed", http.StatusInternalServerError)
return return
} }
dst := filepath.Join(dir, taskID+".bin")
if err := os.WriteFile(dst, plaintext, 0o644); err != nil { if err := os.WriteFile(dst, plaintext, 0o644); err != nil {
http.Error(w, "save failed", http.StatusInternalServerError) http.Error(w, "save failed", http.StatusInternalServerError)
return return
+85
View File
@@ -7,6 +7,7 @@ import (
"io" "io"
"net" "net"
"net/http" "net/http"
"net/http/httptest"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
@@ -227,3 +228,87 @@ func TestHTTPBeaconListener_HandleFileServe(t *testing.T) {
}) })
} }
} }
func TestHTTPBeaconListener_HandleUploadConfinesTaskID(t *testing.T) {
tmp := t.TempDir()
store := filepath.Join(tmp, "c2store")
keyB64, err := GenerateAESKey()
if err != nil {
t.Fatal(err)
}
token := "test-implant-token-upload"
l := &HTTPBeaconListener{
rec: &database.C2Listener{
EncryptionKey: keyB64,
ImplantToken: token,
},
manager: NewManager(nil, zap.NewNop(), store),
logger: zap.NewNop(),
}
encrypted, err := EncryptAESGCM(keyB64, []byte("safe upload"))
if err != nil {
t.Fatal(err)
}
req := httptest.NewRequest(http.MethodPost, "/upload?task_id=t_safe123", strings.NewReader(encrypted))
req.Header.Set("X-Implant-Token", token)
rr := httptest.NewRecorder()
l.handleUpload(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("status=%d body=%q", rr.Code, rr.Body.String())
}
got, err := os.ReadFile(filepath.Join(store, "uploads", "t_safe123.bin"))
if err != nil {
t.Fatal(err)
}
if string(got) != "safe upload" {
t.Fatalf("content=%q", got)
}
evilBody, err := EncryptAESGCM(keyB64, []byte("owned"))
if err != nil {
t.Fatal(err)
}
evilReq := httptest.NewRequest(http.MethodPost, "/upload?task_id=..%2Fowned", strings.NewReader(evilBody))
evilReq.Header.Set("X-Implant-Token", token)
evilRR := httptest.NewRecorder()
l.handleUpload(evilRR, evilReq)
if evilRR.Code != http.StatusNotFound {
t.Fatalf("status=%d body=%q", evilRR.Code, evilRR.Body.String())
}
if _, err := os.Stat(filepath.Join(store, "owned.bin")); !os.IsNotExist(err) {
t.Fatalf("outside file exists or stat failed unexpectedly: %v", err)
}
}
func TestHTTPBeaconListener_HandleResultRejectsPlaintextJSON(t *testing.T) {
keyB64, err := GenerateAESKey()
if err != nil {
t.Fatal(err)
}
l := &HTTPBeaconListener{
rec: &database.C2Listener{
EncryptionKey: keyB64,
ImplantToken: "test-implant-token-result",
},
logger: zap.NewNop(),
}
req := httptest.NewRequest(http.MethodPost, "/result", strings.NewReader(`{"task_id":"t_test","success":true}`))
req.Header.Set("X-Implant-Token", "test-implant-token-result")
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
l.handleResult(rr, req)
if rr.Code != http.StatusNotFound {
t.Fatalf("status=%d body=%q", rr.Code, rr.Body.String())
}
if !strings.Contains(rr.Body.String(), "404 Not Found") {
t.Fatalf("expected disguised 404 body, got %q", rr.Body.String())
}
}
+62 -5
View File
@@ -42,6 +42,11 @@ type Manager struct {
// MCPToolC2Task 与 MCP builtin、c2_task 工具名一致,供 HITL 白名单与 Agent 侧对齐。 // MCPToolC2Task 与 MCP builtin、c2_task 工具名一致,供 HITL 白名单与 Agent 侧对齐。
const MCPToolC2Task = "c2_task" const MCPToolC2Task = "c2_task"
var (
resultBlobSuffixPattern = regexp.MustCompile(`^\.[A-Za-z0-9][A-Za-z0-9_-]{0,31}$`)
uploadTaskIDPattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$`)
)
// HITLBridge 把"危险任务"桥到现有 internal/handler/hitl 审批流的接口。 // HITLBridge 把"危险任务"桥到现有 internal/handler/hitl 审批流的接口。
// internal/app 实例化时传入;空实现表示禁用 HITL 拦截(开发期方便)。 // internal/app 实例化时传入;空实现表示禁用 HITL 拦截(开发期方便)。
type HITLBridge interface { type HITLBridge interface {
@@ -736,18 +741,24 @@ func (m *Manager) IngestTaskResult(report TaskResultReport) error {
} }
func (m *Manager) saveResultBlob(taskID, b64Content, suffix string) (string, error) { func (m *Manager) saveResultBlob(taskID, b64Content, suffix string) (string, error) {
suffix = strings.TrimSpace(suffix) taskID = strings.TrimSpace(taskID)
if suffix == "" { if taskID == "" || taskID == "." || taskID == ".." ||
suffix = ".bin" strings.ContainsAny(taskID, `/\`) {
return "", fmt.Errorf("invalid task_id")
} }
if !strings.HasPrefix(suffix, ".") {
suffix = "." + suffix suffix, err := normalizeResultBlobSuffix(suffix)
if err != nil {
return "", err
} }
dir := filepath.Join(m.storageDir, "results") dir := filepath.Join(m.storageDir, "results")
if err := osMkdirAll(dir, 0o755); err != nil { if err := osMkdirAll(dir, 0o755); err != nil {
return "", err return "", err
} }
path := filepath.Join(dir, taskID+suffix) path := filepath.Join(dir, taskID+suffix)
if err := ensurePathInDir(dir, path); err != nil {
return "", err
}
data, err := base64Decode(b64Content) data, err := base64Decode(b64Content)
if err != nil { if err != nil {
return "", err return "", err
@@ -758,6 +769,52 @@ func (m *Manager) saveResultBlob(taskID, b64Content, suffix string) (string, err
return path, nil return path, nil
} }
func uploadPathForTask(storageDir, taskID string) (dir, path string, err error) {
taskID = strings.TrimSpace(taskID)
if !uploadTaskIDPattern.MatchString(taskID) {
return "", "", fmt.Errorf("invalid task_id")
}
dir = filepath.Join(storageDir, "uploads")
path = filepath.Join(dir, taskID+".bin")
if err := ensurePathInDir(dir, path); err != nil {
return "", "", err
}
return dir, path, nil
}
func normalizeResultBlobSuffix(suffix string) (string, error) {
suffix = strings.TrimSpace(suffix)
if suffix == "" {
return ".bin", nil
}
if !strings.HasPrefix(suffix, ".") {
suffix = "." + suffix
}
if !resultBlobSuffixPattern.MatchString(suffix) {
return "", fmt.Errorf("invalid blob suffix")
}
return suffix, nil
}
func ensurePathInDir(dir, path string) error {
absDir, err := filepath.Abs(dir)
if err != nil {
return err
}
absPath, err := filepath.Abs(path)
if err != nil {
return err
}
rel, err := filepath.Rel(absDir, absPath)
if err != nil {
return err
}
if rel == "." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) || rel == ".." || filepath.IsAbs(rel) {
return fmt.Errorf("path escapes result directory")
}
return nil
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// 事件总线辅助 // 事件总线辅助
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
+75
View File
@@ -0,0 +1,75 @@
package c2
import (
"encoding/base64"
"os"
"path/filepath"
"testing"
"go.uber.org/zap"
)
func TestManagerSaveResultBlobConfinesPath(t *testing.T) {
tmp := t.TempDir()
mgr := NewManager(nil, zap.NewNop(), filepath.Join(tmp, "c2store"))
content := base64.StdEncoding.EncodeToString([]byte("result bytes"))
got, err := mgr.saveResultBlob("t_safe123", content, "txt")
if err != nil {
t.Fatal(err)
}
want := filepath.Join(tmp, "c2store", "results", "t_safe123.txt")
if got != want {
t.Fatalf("path=%q want %q", got, want)
}
raw, err := os.ReadFile(want)
if err != nil {
t.Fatal(err)
}
if string(raw) != "result bytes" {
t.Fatalf("content=%q", raw)
}
outside := filepath.Join(tmp, "owned")
if _, err := mgr.saveResultBlob("t_safe123", content, "./../../owned"); err == nil {
t.Fatal("expected traversal suffix to be rejected")
}
if _, err := os.Stat(outside); !os.IsNotExist(err) {
t.Fatalf("outside file exists or stat failed unexpectedly: %v", err)
}
}
func TestUploadPathForTaskConfinesPath(t *testing.T) {
tmp := t.TempDir()
store := filepath.Join(tmp, "c2store")
dir, got, err := uploadPathForTask(store, "t_safe123")
if err != nil {
t.Fatal(err)
}
if want := filepath.Join(store, "uploads"); dir != want {
t.Fatalf("dir=%q want %q", dir, want)
}
if want := filepath.Join(store, "uploads", "t_safe123.bin"); got != want {
t.Fatalf("path=%q want %q", got, want)
}
for _, taskID := range []string{"", ".", "..", "../owned", `..\owned`, "sub/owned", "sub\\owned", "task.with.dot", "-leading"} {
if _, _, err := uploadPathForTask(store, taskID); err == nil {
t.Fatalf("task_id %q unexpectedly accepted", taskID)
}
}
}
func TestNormalizeResultBlobSuffix(t *testing.T) {
for _, suffix := range []string{"", "png", ".jpg", ".7z", ".safe_name-1"} {
if _, err := normalizeResultBlobSuffix(suffix); err != nil {
t.Fatalf("suffix %q rejected: %v", suffix, err)
}
}
for _, suffix := range []string{".", "..", "../x", "./../../x", "/tmp/x", `..\x`, ".name.with.dot", ".toolong012345678901234567890123456789"} {
if _, err := normalizeResultBlobSuffix(suffix); err == nil {
t.Fatalf("suffix %q unexpectedly accepted", suffix)
}
}
}
+4 -2
View File
@@ -209,11 +209,13 @@ func (l *TCPReverseListener) handleTCPBeaconSession(conn net.Conn, br *bufio.Rea
if err != nil { if err != nil {
return return
} }
dir := filepath.Join(l.manager.StorageDir(), "uploads") dir, dst, err := uploadPathForTask(l.manager.StorageDir(), up.TaskID)
if err != nil {
return
}
if err := os.MkdirAll(dir, 0o755); err != nil { if err := os.MkdirAll(dir, 0o755); err != nil {
return return
} }
dst := filepath.Join(dir, up.TaskID+".bin")
if err := os.WriteFile(dst, plainFile, 0o644); err != nil { if err := os.WriteFile(dst, plainFile, 0o644); err != nil {
return return
} }