fscan/Plugins/services/ssh/exploiter.go
ZacharyZcR de286026e8 refactor: 统一插件超时机制实现Context-First架构
- 重构SSH/MySQL/Redis插件超时控制,移除第三方库超时依赖
- 统一使用Go Context超时机制,提升超时控制可靠性和精确度
- 扩展MySQL/Redis/SSH插件默认端口支持,提升扫描覆盖率
- 修复插件系统中ConcurrentScanConfig超时配置缺失问题
- 优化插件检测逻辑,正确识别新架构插件并显示准确状态信息
- 解决插件在错误端口上长时间等待问题,显著提升扫描效率
2025-08-07 14:01:50 +08:00

218 lines
6.5 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 ssh
import (
"context"
"fmt"
"github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins/base"
"golang.org/x/crypto/ssh"
"strings"
)
// SSHExploiter SSH利用器实现
type SSHExploiter struct {
*base.BaseExploiter
connector *SSHConnector
}
// NewSSHExploiter 创建SSH利用器
func NewSSHExploiter() *SSHExploiter {
exploiter := &SSHExploiter{
BaseExploiter: base.NewBaseExploiter("ssh"),
connector: NewSSHConnector(),
}
// 添加利用方法
exploiter.setupExploitMethods()
return exploiter
}
// setupExploitMethods 设置利用方法
func (e *SSHExploiter) setupExploitMethods() {
// 1. 系统信息收集
infoMethod := base.NewExploitMethod(base.ExploitDataExtraction, "system_info").
WithDescription("收集系统信息").
WithPriority(8).
WithConditions("has_credentials").
WithHandler(e.exploitSystemInfo).
Build()
e.AddExploitMethod(infoMethod)
// 2. 命令执行测试
cmdMethod := base.NewExploitMethod(base.ExploitCommandExec, "command_test").
WithDescription("测试命令执行能力").
WithPriority(9).
WithConditions("has_credentials").
WithHandler(e.exploitCommandTest).
Build()
e.AddExploitMethod(cmdMethod)
// 3. 用户权限检查
privMethod := base.NewExploitMethod(base.ExploitDataExtraction, "privilege_check").
WithDescription("检查用户权限").
WithPriority(7).
WithConditions("has_credentials").
WithHandler(e.exploitPrivilegeCheck).
Build()
e.AddExploitMethod(privMethod)
}
// exploitSystemInfo 系统信息收集利用
func (e *SSHExploiter) exploitSystemInfo(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
client, err := e.connectSSH(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "system_info", err), nil
}
defer client.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "system_info")
// 收集系统信息的命令
commands := map[string]string{
"hostname": "hostname",
"os": "cat /etc/os-release 2>/dev/null || uname -a",
"whoami": "whoami",
"id": "id",
"uptime": "uptime",
"pwd": "pwd",
}
for name, cmd := range commands {
output, err := e.executeCommand(client, cmd)
if err == nil && output != "" {
base.AddOutputToResult(result, i18n.GetText("ssh_command_result", name, strings.TrimSpace(output)))
result.Extra[name] = strings.TrimSpace(output)
}
}
return result, nil
}
// exploitCommandTest 命令执行测试利用
func (e *SSHExploiter) exploitCommandTest(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
client, err := e.connectSSH(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitCommandExec, "command_test", err), nil
}
defer client.Close()
result := base.CreateSuccessExploitResult(base.ExploitCommandExec, "command_test")
// 测试基本命令执行
testCommands := []string{"echo 'SSH_COMMAND_TEST'", "date", "whoami"}
for _, cmd := range testCommands {
output, err := e.executeCommand(client, cmd)
if err == nil && output != "" {
base.AddOutputToResult(result, i18n.GetText("ssh_test_command", cmd, strings.TrimSpace(output)))
}
}
return result, nil
}
// exploitPrivilegeCheck 权限检查利用
func (e *SSHExploiter) exploitPrivilegeCheck(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*base.ExploitResult, error) {
client, err := e.connectSSH(ctx, info, creds)
if err != nil {
return base.CreateFailedExploitResult(base.ExploitDataExtraction, "privilege_check", err), nil
}
defer client.Close()
result := base.CreateSuccessExploitResult(base.ExploitDataExtraction, "privilege_check")
// 检查sudo权限
sudoOutput, err := e.executeCommand(client, "sudo -l 2>/dev/null")
if err == nil && sudoOutput != "" && !strings.Contains(sudoOutput, "may not run sudo") {
base.AddOutputToResult(result, i18n.GetText("ssh_sudo_check", strings.TrimSpace(sudoOutput)))
result.Extra["sudo_privileges"] = strings.TrimSpace(sudoOutput)
}
// 检查是否为root用户
whoami, err := e.executeCommand(client, "whoami")
if err == nil && strings.TrimSpace(whoami) == "root" {
base.AddOutputToResult(result, i18n.GetText("ssh_root_access"))
result.Extra["is_root"] = true
}
// 检查用户组
groups, err := e.executeCommand(client, "groups")
if err == nil && groups != "" {
base.AddOutputToResult(result, i18n.GetText("ssh_user_groups", strings.TrimSpace(groups)))
result.Extra["groups"] = strings.TrimSpace(groups)
}
return result, nil
}
// =============================================================================
// SSH操作辅助函数
// =============================================================================
// connectSSH 使用凭据连接SSH
func (e *SSHExploiter) connectSSH(ctx context.Context, info *common.HostInfo, creds *base.Credential) (*ssh.Client, error) {
config := &ssh.ClientConfig{
Timeout: 0, // 禁用SSH库超时使用Context控制
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
// 设置认证方法
if creds.KeyData != nil && len(creds.KeyData) > 0 {
// 密钥认证
signer, err := ssh.ParsePrivateKey(creds.KeyData)
if err != nil {
return nil, fmt.Errorf("解析私钥失败: %v", err)
}
config.User = creds.Username
config.Auth = []ssh.AuthMethod{ssh.PublicKeys(signer)}
} else {
// 密码认证
config.User = creds.Username
config.Auth = []ssh.AuthMethod{ssh.Password(creds.Password)}
}
// 直接使用传入的Context它已经包含了正确的超时设置
target := fmt.Sprintf("%s:%s", info.Host, info.Ports)
// 使用Context控制超时
type sshResult struct {
client *ssh.Client
err error
}
resultChan := make(chan sshResult, 1)
go func() {
client, err := ssh.Dial("tcp", target, config)
resultChan <- sshResult{client: client, err: err}
}()
// 等待结果或超时
select {
case result := <-resultChan:
if result.err != nil {
return nil, fmt.Errorf("SSH连接失败: %v", result.err)
}
return result.client, nil
case <-ctx.Done():
return nil, fmt.Errorf("SSH连接超时: %v", ctx.Err())
}
}
// executeCommand 执行SSH命令
func (e *SSHExploiter) executeCommand(client *ssh.Client, command string) (string, error) {
session, err := client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
output, err := session.CombinedOutput(command)
if err != nil {
return "", err
}
return string(output), nil
}