mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-18 21:47:17 +02:00
fix: verify listener ownership before accepting task file uploads
This commit is contained in:
1 parent
062fd30cd7
commit
d9a111ff50
4 files changed
+88
-4
No files matched your search
@@ -144,3 +144,7 @@
|
||||
### 单资产有限核验范围和写回提示(表格第 48 行)
|
||||
|
||||
默认单 Agent 系统提示中的持续深挖、穷尽攻击面和赏金门槛要求,与只读连通性/指纹任务完成条件冲突。现以本轮目标限制执行深度,403/认证失败等按核验目标如实报告,达到目标后结束,仍保留明确全面评估任务的验证要求。自定义资产模板此前替换掉默认模板中 complete_asset_scan 指令,现保留用户模板内容并追加完成后的现有记录动作,不增加探测范围;已有写回指令不重复。新增前端测试验证限定核验模板、资产 ID、写回提示及无额外漏洞/弱口令探测提示。Agent 包测试、资产工具和数据库测试通过,后者覆盖实际 complete_asset_scan 回写时间、会话及漏洞数量。未运行真实模型探测,提示修复不能证明所有模型均会及时结束;历史长时间行为仍保留验证限制。
|
||||
|
||||
### HTTP 文件上传归属检查(表格第 36 行扩展路径)
|
||||
|
||||
HTTP 上传端点此前只验证共享监听器凭据与任务 ID 路径,未校验任务会话归属。现上传前查询任务及其会话,拒绝其他监听器任务。回归测试验证外部监听器不能覆盖已有文件、所属监听器正常上传可用;原路径安全测试增加真实任务/会话夹具。C2 包测试通过。文件下载端点使用独立 file_id,不可直接当 task_id 校验,未以错误映射限制下载;同监听器会话隔离及下载文件归属继续核对。
|
||||
@@ -0,0 +1,67 @@
|
||||
package c2
|
||||
|
||||
import (
|
||||
"cyberstrike-ai/internal/database"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHTTPUploadEnforcesListenerOwnership(t *testing.T) {
|
||||
manager, db := terminalTestManager(t)
|
||||
task := &database.C2Task{ID: "test-only-file-task", SessionID: "session", TaskType: "download", Status: "sent", CreatedAt: time.Now()}
|
||||
if err := db.CreateC2Task(task); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key, err := GenerateAESKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
body, err := EncryptAESGCM(key, []byte("test-only-upload"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dir, path, err := uploadPathForTask(manager.StorageDir(), task.ID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, []byte("original"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
listener := &HTTPBeaconListener{rec: &database.C2Listener{ID: "foreign-listener", EncryptionKey: key, ImplantToken: "test-only-token"}, manager: manager, cfg: &ListenerConfig{BeaconFilePath: "/file/"}, logger: zap.NewNop()}
|
||||
for _, owned := range []bool{false, true} {
|
||||
if owned {
|
||||
listener.rec.ID = "listener"
|
||||
}
|
||||
upload := httptest.NewRequest(http.MethodPost, "/upload?task_id="+task.ID, strings.NewReader(body))
|
||||
upload.Header.Set("X-Implant-Token", "test-only-token")
|
||||
rr := httptest.NewRecorder()
|
||||
listener.handleUpload(rr, upload)
|
||||
expected := http.StatusNotFound
|
||||
if owned {
|
||||
expected = http.StatusOK
|
||||
}
|
||||
if rr.Code != expected {
|
||||
t.Fatalf("upload owned=%v: status=%d", owned, rr.Code)
|
||||
}
|
||||
saved, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
expectedContent := "original"
|
||||
if owned {
|
||||
expectedContent = "test-only-upload"
|
||||
}
|
||||
if string(saved) != expectedContent {
|
||||
t.Fatalf("unexpected file mutation owned=%v: %q", owned, saved)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ func (l *HTTPBeaconListener) handleUpload(w http.ResponseWriter, r *http.Request
|
||||
return
|
||||
}
|
||||
taskID := r.URL.Query().Get("task_id")
|
||||
if taskID == "" {
|
||||
if taskID == "" || !l.ownsTask(taskID) {
|
||||
l.disguisedReject(w)
|
||||
return
|
||||
}
|
||||
@@ -397,6 +397,15 @@ func (l *HTTPBeaconListener) handleFileServe(w http.ResponseWriter, r *http.Requ
|
||||
// 鉴权 / 输出辅助
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
func (l *HTTPBeaconListener) ownsTask(taskID string) bool {
|
||||
task, err := l.manager.DB().GetC2Task(taskID)
|
||||
if err != nil || task == nil {
|
||||
return false
|
||||
}
|
||||
session, err := l.manager.DB().GetC2Session(task.SessionID)
|
||||
return err == nil && session != nil && session.ListenerID == l.rec.ID
|
||||
}
|
||||
|
||||
// checkImplantToken 校验 X-Implant-Token header(恒定时间比较防止时序攻击)
|
||||
func (l *HTTPBeaconListener) checkImplantToken(r *http.Request) bool {
|
||||
got := r.Header.Get("X-Implant-Token")
|
||||
|
||||
@@ -230,8 +230,11 @@ func TestHTTPBeaconListener_HandleFileServe(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPBeaconListener_HandleUploadConfinesTaskID(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
store := filepath.Join(tmp, "c2store")
|
||||
m, db := terminalTestManager(t)
|
||||
store := m.StorageDir()
|
||||
if err := db.CreateC2Task(&database.C2Task{ID: "t_safe123", SessionID: "session", TaskType: "download", Status: "sent", CreatedAt: time.Now()}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keyB64, err := GenerateAESKey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -239,10 +242,11 @@ func TestHTTPBeaconListener_HandleUploadConfinesTaskID(t *testing.T) {
|
||||
token := "test-implant-token-upload"
|
||||
l := &HTTPBeaconListener{
|
||||
rec: &database.C2Listener{
|
||||
ID: "listener",
|
||||
EncryptionKey: keyB64,
|
||||
ImplantToken: token,
|
||||
},
|
||||
manager: NewManager(nil, zap.NewNop(), store),
|
||||
manager: m,
|
||||
logger: zap.NewNop(),
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user