package axscript import ( "AdaptixServer/core/utils/logs" "encoding/base64" "encoding/json" "fmt" "os" "path/filepath" "strings" "sync" "github.com/dop251/goja" ) type TeamserverBridge interface { TsAgentCommand(agentName string, agentId string, clientName string, hookId string, handlerId string, cmdline string, ui bool, args map[string]any) error TsAgentConsoleOutput(agentId string, messageType int, message string, clearText string, store bool) TsAgentConsoleErrorCommand(agentId string, client string, cmdline string, message string, HookId string, HandlerId string) AxGetAgentContext(agentId string) (agentName string, listenerRegName string, osType int, err error) AxGetAgents() map[string]interface{} AxGetAgentInfo(agentId string, property string) interface{} AxGetAgentIds() []string AxGetCredentials() []interface{} AxGetTargets() []interface{} AxCredentialsAdd(creds []map[string]interface{}) error AxTargetsAdd(targets []map[string]interface{}) error AxAgentRemove(agentIds []string) error AxAgentSetTag(agentIds []string, tag string) error AxAgentSetMark(agentIds []string, mark string) error AxAgentSetColor(agentIds []string, background string, foreground string, reset bool) error AxAgentUpdateData(agentId string, updateData map[string]interface{}) error AxGetDownloads() []interface{} AxGetScreenshots() []interface{} AxGetTunnels() []interface{} AxGetInterfaces() []string AxGetAgentMark(agentId string) string AxUnloadAxScript(name string) error } type ScriptManager struct { mu sync.RWMutex teamserver TeamserverBridge CommandStore *CommandStore HookStore *HookStore agentEngines map[string]*ScriptEngine userEngines map[string]*ScriptEngine axscriptEngines map[string]*ScriptEngine scriptInfos []ScriptInfo globalAllowedRoots []string } func NewScriptManager(ts TeamserverBridge) *ScriptManager { return &ScriptManager{ teamserver: ts, CommandStore: NewCommandStore(), HookStore: NewHookStore(), agentEngines: make(map[string]*ScriptEngine), userEngines: make(map[string]*ScriptEngine), axscriptEngines: make(map[string]*ScriptEngine), } } /// LOAD SCRIPTS func (sm *ScriptManager) LoadAgentScript(agentName string, axScript string, listeners []string) error { engine := NewScriptEngine("agent:"+agentName, sm) registerFormStubs(engine) registerMenuStubs(engine) registerEventStubs(engine) registerAxBridge(engine) err := engine.Execute(axScript) if err != nil { return fmt.Errorf("failed to execute agent script for '%s': %w", agentName, err) } sm.mu.Lock() sm.agentEngines[agentName] = engine sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ Name: agentName, ScriptType: "agent", AgentName: agentName, }) sm.mu.Unlock() for _, listenerType := range listeners { sm.executeRegisterCommands(engine, agentName, listenerType) } if len(listeners) == 0 { sm.executeRegisterCommands(engine, agentName, "") } return nil } func (sm *ScriptManager) LoadAxScript(scriptPath string) error { abs, err := filepath.Abs(scriptPath) if err != nil { return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) } _, err = os.Stat(abs) if err != nil { return fmt.Errorf("script file not found: %s", abs) } content, err := os.ReadFile(abs) if err != nil { return fmt.Errorf("failed to read script '%s': %w", abs, err) } engine, err := NewScriptEngineFromPath(abs, sm) if err != nil { return fmt.Errorf("failed to create engine for '%s': %w", abs, err) } registerFormStubs(engine) registerMenuStubs(engine) registerEventStubs(engine) registerAxBridge(engine) err = engine.Execute(string(content)) if err != nil { return fmt.Errorf("failed to execute script '%s': %w", abs, err) } if engine.GetMetadataNoSave() { logs.Success("", "Executed axscript '%s' (nosave)", scriptPath) return nil } scriptName := engine.GetMetadataName() if scriptName == "" { scriptName = filepath.Base(abs) } sm.mu.Lock() sm.axscriptEngines[abs] = engine sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ Name: scriptName, ScriptType: "axscript", Path: abs, }) sm.mu.Unlock() logs.Success("", "Loaded axscript '%s'", scriptPath) return nil } func (sm *ScriptManager) LoadAxScriptChild(parentEngine *ScriptEngine, scriptPath string) error { abs, err := filepath.Abs(scriptPath) if err != nil { return fmt.Errorf("invalid script path '%s': %w", scriptPath, err) } if _, err := os.Stat(abs); err != nil { return fmt.Errorf("script file not found: %s", abs) } content, err := os.ReadFile(abs) if err != nil { return fmt.Errorf("failed to read script '%s': %w", abs, err) } engine, err := NewScriptEngineFromPath(abs, sm) if err != nil { return fmt.Errorf("failed to create engine for '%s': %w", abs, err) } for _, root := range parentEngine.allowedRoots { found := false for _, r := range engine.allowedRoots { if r == root { found = true break } } if !found { engine.allowedRoots = append(engine.allowedRoots, root) } } registerFormStubs(engine) registerMenuStubs(engine) registerEventStubs(engine) registerAxBridge(engine) err = engine.Execute(string(content)) if err != nil { return fmt.Errorf("failed to execute script '%s': %w", abs, err) } if engine.GetMetadataNoSave() { logs.Success("", "Executed axscript '%s' (nosave)", abs) return nil } scriptName := engine.GetMetadataName() if scriptName == "" { scriptName = filepath.Base(abs) } sm.mu.Lock() sm.axscriptEngines[abs] = engine sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ Name: scriptName, ScriptType: "axscript", Path: abs, }) sm.mu.Unlock() logs.Success("", "Loaded axscript '%s'", abs) return nil } func (sm *ScriptManager) ImportAxScript(engine *ScriptEngine, scriptPath string) (string, error) { abs, err := filepath.Abs(scriptPath) if err != nil { return "", fmt.Errorf("invalid script path '%s': %w", scriptPath, err) } _, err = engine.ValidatePath(abs) if err != nil { return "", err } content, err := os.ReadFile(abs) if err != nil { return "", fmt.Errorf("failed to read script '%s': %w", abs, err) } _, execErr := engine.runtime.RunString(string(content)) if execErr != nil { return "", fmt.Errorf("failed to import script '%s': %w", abs, execErr) } return abs, nil } /// func (sm *ScriptManager) executeRegisterCommands(engine *ScriptEngine, agentName string, listenerType string) { engine.mu.Lock() rt := engine.runtime fn := rt.Get("RegisterCommands") if fn == nil || goja.IsUndefined(fn) { engine.mu.Unlock() logs.Warn("", "No RegisterCommands function found in script for '%s'", agentName) return } registerFn, ok := goja.AssertFunction(fn) if !ok { engine.mu.Unlock() logs.Error("", "RegisterCommands is not a function in script for '%s'", agentName) return } result, err := registerFn(goja.Undefined(), rt.ToValue(listenerType)) engine.mu.Unlock() if err != nil { logs.Error("", "Error calling RegisterCommands for '%s': %v", agentName, err) return } if result == nil || goja.IsUndefined(result) || goja.IsNull(result) { logs.Warn("", "RegisterCommands returned nil for '%s'", agentName) return } obj := result.ToObject(rt) sm.extractCommandsFromResult(engine, agentName, listenerType, obj, "commands_windows", OsWindows) sm.extractCommandsFromResult(engine, agentName, listenerType, obj, "commands_linux", OsLinux) sm.extractCommandsFromResult(engine, agentName, listenerType, obj, "commands_macos", OsMac) } func (sm *ScriptManager) extractCommandsFromResult(engine *ScriptEngine, agentName string, listenerType string, obj *goja.Object, propName string, osType int) { prop := obj.Get(propName) if prop == nil || goja.IsUndefined(prop) || goja.IsNull(prop) { return } var groupBuilder *jsCommandGroupBuilder exported := prop.Export() if gb, ok := exported.(*jsCommandGroupBuilder); ok { groupBuilder = gb } if groupBuilder == nil { if m, ok := exported.(map[string]interface{}); ok { if gv, exists := m["__group"]; exists { if gb, ok2 := gv.(*jsCommandGroupBuilder); ok2 { groupBuilder = gb } } } } if groupBuilder == nil { propObj := prop.ToObject(engine.runtime) groupVal := propObj.Get("__group") if groupVal != nil && !goja.IsUndefined(groupVal) && !goja.IsNull(groupVal) { if gb, ok := groupVal.Export().(*jsCommandGroupBuilder); ok { groupBuilder = gb } } } if groupBuilder == nil { logs.Warn("", "Property '%s' for agent '%s' is not a CommandGroup", propName, agentName) return } group := groupBuilder.ToCommandGroup(agentName) group.Source = "agent" sm.CommandStore.RegisterGroups(SourceAgent, agentName, listenerType, osType, []CommandGroup{group}, engine) } //////////////////// // /--- func (sm *ScriptManager) LoadUserScript(name string, script string) error { engine := NewScriptEngine("user:"+name, sm) registerFormStubs(engine) registerMenuStubs(engine) registerEventStubs(engine) registerAxBridge(engine) err := engine.Execute(script) if err != nil { return fmt.Errorf("failed to execute user script '%s': %w", name, err) } sm.mu.Lock() sm.userEngines[name] = engine sm.scriptInfos = append(sm.scriptInfos, ScriptInfo{ Name: name, ScriptType: "user", }) sm.mu.Unlock() return nil } // /--- func (sm *ScriptManager) UnloadUserScript(name string) error { sm.mu.Lock() defer sm.mu.Unlock() if _, ok := sm.userEngines[name]; !ok { return fmt.Errorf("user script '%s' not found", name) } delete(sm.userEngines, name) sm.CommandStore.UnregisterByScriptName(SourceUser, name) for i, info := range sm.scriptInfos { if info.Name == name && info.ScriptType == "user" { sm.scriptInfos = append(sm.scriptInfos[:i], sm.scriptInfos[i+1:]...) break } } return nil } // /--- func (sm *ScriptManager) ListScripts() []ScriptInfo { sm.mu.RLock() defer sm.mu.RUnlock() result := make([]ScriptInfo, len(sm.scriptInfos)) copy(result, sm.scriptInfos) return result } func (sm *ScriptManager) ListProfileScriptsWithContent() []ScriptWithContent { sm.mu.RLock() defer sm.mu.RUnlock() var result []ScriptWithContent for _, info := range sm.scriptInfos { if info.ScriptType == "axscript" && info.Path != "" { engine, ok := sm.axscriptEngines[info.Path] if !ok { continue } var combined strings.Builder importedFiles := engine.GetImportedFiles() for _, importPath := range importedFiles { importContent, err := os.ReadFile(importPath) if err != nil { combined.WriteString(fmt.Sprintf("/* import error: %s */\n", importPath)) continue } combined.WriteString(fmt.Sprintf("/* inlined: %s */\n", filepath.Base(importPath))) combined.Write(importContent) combined.WriteString(fmt.Sprintf("\n/* end: %s */\n\n", filepath.Base(importPath))) } mainContent, err := os.ReadFile(info.Path) if err != nil { continue } combined.Write(mainContent) result = append(result, ScriptWithContent{ Name: info.Name, Script: combined.String(), }) } } return result } func (sm *ScriptManager) ResolveAndExecutePreHook(agentName string, agentId string, listenerRegName string, os int, cmdline string, args map[string]interface{}) (hookId string, handlerId string, preHookHandled bool, err error) { resolved, resolveErr := sm.CommandStore.ResolveFromCmdline(agentName, listenerRegName, os, cmdline) if resolveErr != nil { return "", "", false, nil } cmdDef := resolved.GetEffectiveCommand() if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && resolved.Engine != nil { preHookErr := sm.executePreHook(resolved.Engine, cmdDef.PreHookFunc, agentId, cmdline, args) if preHookErr != nil { return "", "", true, preHookErr } return "", "", true, nil } if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && resolved.Engine != nil { hookId = sm.HookStore.RegisterPostHook(resolved.Engine, cmdDef.PostHookFunc, agentId, "server") } if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && resolved.Engine != nil { handlerId = sm.HookStore.RegisterHandler(resolved.Engine, cmdDef.HandlerFunc, agentId, "server") } return hookId, handlerId, false, nil } // /--- func (sm *ScriptManager) ParseCommandPublic(cmdline string, resolved *ResolvedCommand) (*ParsedCommand, error) { return ParseCommand(cmdline, resolved) } // /--- func (sm *ScriptManager) ExecutePreHookPublic(engine *ScriptEngine, fn goja.Callable, agentId string, cmdline string, args map[string]interface{}) error { return sm.executePreHook(engine, fn, agentId, cmdline, args) } // /--- func (sm *ScriptManager) ResolveFileArgsPublic(engine *ScriptEngine, parsed *ParsedCommand) error { return sm.resolveFileArgs(engine, parsed) } func (sm *ScriptManager) executePreHook(engine *ScriptEngine, fn goja.Callable, agentId string, cmdline string, args map[string]interface{}) error { argsCopy := make(map[string]interface{}, len(args)) for k, v := range args { argsCopy[k] = v } _, err := engine.CallCallable(fn, engine.ToValue(agentId), engine.ToValue(cmdline), engine.ToValue(argsCopy), ) return err } func (sm *ScriptManager) resolveFileArgs(engine *ScriptEngine, parsed *ParsedCommand) error { if len(parsed.FileArgs) == 0 { return nil } for _, fa := range parsed.FileArgs { if fa.OriginalPath == "" { if fa.Required { return fmt.Errorf("missing required file argument: %s", fa.ArgName) } continue } data, err := sm.ReadFileSandboxed(engine, fa.OriginalPath) if err != nil { return fmt.Errorf("cannot read file for argument '%s': %w", fa.ArgName, err) } parsed.Args[fa.ArgName] = base64.StdEncoding.EncodeToString(data) } return nil } type agentCommandContext struct { agentName string resolved *ResolvedCommand parsed *ParsedCommand } func (sm *ScriptManager) resolveAgentCommand(agentId string, cmdline string) (*agentCommandContext, error) { if sm.teamserver == nil { return nil, fmt.Errorf("teamserver not available") } agentName, listenerRegName, os, err := sm.teamserver.AxGetAgentContext(agentId) if err != nil { return nil, err } resolved, resolveErr := sm.CommandStore.ResolveFromCmdline(agentName, listenerRegName, os, cmdline) if resolveErr != nil { return nil, resolveErr } parsed, parseErr := ParseCommand(cmdline, resolved) if parseErr != nil { return nil, parseErr } return &agentCommandContext{ agentName: agentName, resolved: resolved, parsed: parsed, }, nil } // /--- func (sm *ScriptManager) ExecuteCommand(fromEngine *ScriptEngine, agentId string, cmdline string, postHookFn goja.Callable, handlerFn goja.Callable) error { ctx, err := sm.resolveAgentCommand(agentId, cmdline) if err != nil { return err } if fromEngine != nil { if fileErr := sm.resolveFileArgs(fromEngine, ctx.parsed); fileErr != nil { return fileErr } } hookId := "" handlerId := "" if postHookFn != nil { hookId = sm.HookStore.RegisterPostHook(fromEngine, postHookFn, agentId, "server") } if handlerFn != nil { handlerId = sm.HookStore.RegisterHandler(fromEngine, handlerFn, agentId, "server") } return sm.teamserver.TsAgentCommand(ctx.agentName, agentId, "server", hookId, handlerId, cmdline, false, ctx.parsed.Args) } // /--- func (sm *ScriptManager) ExecuteAlias(fromEngine *ScriptEngine, agentId string, aliasCmdline string) error { ctx, err := sm.resolveAgentCommand(agentId, aliasCmdline) if err != nil { return err } if fromEngine != nil { if fileErr := sm.resolveFileArgs(fromEngine, ctx.parsed); fileErr != nil { return fileErr } } cmdDef := ctx.resolved.GetEffectiveCommand() if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && ctx.resolved.Engine != nil { preHookErr := sm.executePreHook(ctx.resolved.Engine, cmdDef.PreHookFunc, agentId, aliasCmdline, ctx.parsed.Args) if preHookErr != nil { return preHookErr } return nil } hookId := "" handlerId := "" if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && ctx.resolved.Engine != nil { hookId = sm.HookStore.RegisterPostHook(ctx.resolved.Engine, cmdDef.PostHookFunc, agentId, "server") } if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && ctx.resolved.Engine != nil { handlerId = sm.HookStore.RegisterHandler(ctx.resolved.Engine, cmdDef.HandlerFunc, agentId, "server") } return sm.teamserver.TsAgentCommand(ctx.agentName, agentId, "server", hookId, handlerId, aliasCmdline, false, ctx.parsed.Args) } func (sm *ScriptManager) ExecuteAliasWithHooks(fromEngine *ScriptEngine, agentId string, displayCmdline string, aliasCmdline string, message string, postHookFn goja.Callable, handlerFn goja.Callable) error { ctx, err := sm.resolveAgentCommand(agentId, aliasCmdline) if err != nil { return err } if fromEngine != nil { if fileErr := sm.resolveFileArgs(fromEngine, ctx.parsed); fileErr != nil { return fileErr } } cmdDef := ctx.resolved.GetEffectiveCommand() if cmdDef.HasPreHook && cmdDef.PreHookFunc != nil && ctx.resolved.Engine != nil { preHookErr := sm.executePreHook(ctx.resolved.Engine, cmdDef.PreHookFunc, agentId, aliasCmdline, ctx.parsed.Args) if preHookErr != nil { return preHookErr } return nil } hookId := "" handlerId := "" if postHookFn != nil { hookId = sm.HookStore.RegisterPostHook(fromEngine, postHookFn, agentId, "server") } else if cmdDef.HasPostHook && cmdDef.PostHookFunc != nil && ctx.resolved.Engine != nil { hookId = sm.HookStore.RegisterPostHook(ctx.resolved.Engine, cmdDef.PostHookFunc, agentId, "server") } if handlerFn != nil { handlerId = sm.HookStore.RegisterHandler(fromEngine, handlerFn, agentId, "server") } else if cmdDef.HasHandler && cmdDef.HandlerFunc != nil && ctx.resolved.Engine != nil { handlerId = sm.HookStore.RegisterHandler(ctx.resolved.Engine, cmdDef.HandlerFunc, agentId, "server") } cmdlineForDisplay := displayCmdline if cmdlineForDisplay == "" { cmdlineForDisplay = aliasCmdline } if message != "" { ctx.parsed.Args["message"] = message } return sm.teamserver.TsAgentCommand(ctx.agentName, agentId, "server", hookId, handlerId, cmdlineForDisplay, false, ctx.parsed.Args) } func (sm *ScriptManager) GetAgents() map[string]interface{} { if sm.teamserver == nil { return map[string]interface{}{} } return sm.teamserver.AxGetAgents() } func (sm *ScriptManager) GetAgentInfo(agentId string, property string) interface{} { if sm.teamserver == nil { return nil } return sm.teamserver.AxGetAgentInfo(agentId, property) } // /--- func (sm *ScriptManager) GetAgentIds() []string { if sm.teamserver == nil { return []string{} } return sm.teamserver.AxGetAgentIds() } // /--- func (sm *ScriptManager) GetCredentials() []interface{} { if sm.teamserver == nil { return []interface{}{} } return sm.teamserver.AxGetCredentials() } // /--- func (sm *ScriptManager) GetTargets() []interface{} { if sm.teamserver == nil { return []interface{}{} } return sm.teamserver.AxGetTargets() } func (sm *ScriptManager) ConsoleMessage(agentId string, msgType int, message string, clearText string) { if sm.teamserver == nil { return } sm.teamserver.TsAgentConsoleOutput(agentId, msgType, message, clearText, false) // todo underlaycopy } // /--- func (sm *ScriptManager) GetCommandsJSON() (string, error) { allCommands := sm.CommandStore.GetAllCommandsOrdered() data, err := json.Marshal(allCommands) if err != nil { return "", err } return string(data), nil } // /--- func (sm *ScriptManager) SetGlobalAllowedRoots(roots []string) { sm.globalAllowedRoots = roots } func (sm *ScriptManager) ReadFileSandboxed(engine *ScriptEngine, path string) ([]byte, error) { validated, err := engine.ValidatePath(path) if err != nil { return nil, err } return os.ReadFile(validated) } // /--- func (sm *ScriptManager) WriteFileSandboxed(engine *ScriptEngine, path string, data []byte, append_ bool) error { validated, err := engine.ValidatePath(path) if err != nil { return err } flag := os.O_WRONLY | os.O_CREATE if append_ { flag |= os.O_APPEND } else { flag |= os.O_TRUNC } dir := filepath.Dir(validated) if err := os.MkdirAll(dir, 0755); err != nil { return err } f, err := os.OpenFile(validated, flag, 0644) if err != nil { return err } defer f.Close() _, err = f.Write(data) return err } // /--- func (sm *ScriptManager) GetDownloads() []interface{} { if sm.teamserver == nil { return []interface{}{} } return sm.teamserver.AxGetDownloads() } // /--- func (sm *ScriptManager) GetScreenshots() []interface{} { if sm.teamserver == nil { return []interface{}{} } return sm.teamserver.AxGetScreenshots() } // /--- func (sm *ScriptManager) GetTunnels() []interface{} { if sm.teamserver == nil { return []interface{}{} } return sm.teamserver.AxGetTunnels() } // /--- func (sm *ScriptManager) GetInterfaces() []string { if sm.teamserver == nil { return []string{} } return sm.teamserver.AxGetInterfaces() } // /--- func (sm *ScriptManager) GetAgentMark(agentId string) string { if sm.teamserver == nil { return "" } return sm.teamserver.AxGetAgentMark(agentId) } // /--- func (sm *ScriptManager) UnloadAxScript(name string) error { if sm.teamserver == nil { return fmt.Errorf("teamserver not available") } return sm.teamserver.AxUnloadAxScript(name) } // /--- func (sm *ScriptManager) ValidateCommand(agentId string, cmdline string) (map[string]interface{}, error) { if sm.teamserver == nil { return nil, fmt.Errorf("teamserver not available") } agentName, listenerRegName, osType, err := sm.teamserver.AxGetAgentContext(agentId) if err != nil { return map[string]interface{}{"valid": false, "message": "Agent not found"}, nil } resolved, resolveErr := sm.CommandStore.ResolveFromCmdline(agentName, listenerRegName, osType, cmdline) if resolveErr != nil { return map[string]interface{}{"valid": false, "message": resolveErr.Error()}, nil } parsed, parseErr := ParseCommand(cmdline, resolved) if parseErr != nil { return map[string]interface{}{"valid": false, "message": parseErr.Error()}, nil } cmdDef := resolved.GetEffectiveCommand() result := map[string]interface{}{ "valid": true, "message": "", "is_pre_hook": cmdDef.HasPreHook, "has_post_hook": cmdDef.HasPostHook, "has_handler": cmdDef.HasHandler, "parsed": parsed.Args, } return result, nil } // /--- func (sm *ScriptManager) GetCommandNames(agentId string) ([]string, error) { if sm.teamserver == nil { return nil, fmt.Errorf("teamserver not available") } agentName, listenerRegName, osType, err := sm.teamserver.AxGetAgentContext(agentId) if err != nil { return nil, err } groups := sm.CommandStore.GetCommandsForAgent(agentName, listenerRegName, osType) var names []string for _, g := range groups { for _, cmd := range g.Commands { names = append(names, cmd.Name) } } return names, nil }