mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

- 重构SSH/MySQL/Redis插件超时控制,移除第三方库超时依赖 - 统一使用Go Context超时机制,提升超时控制可靠性和精确度 - 扩展MySQL/Redis/SSH插件默认端口支持,提升扫描覆盖率 - 修复插件系统中ConcurrentScanConfig超时配置缺失问题 - 优化插件检测逻辑,正确识别新架构插件并显示准确状态信息 - 解决插件在错误端口上长时间等待问题,显著提升扫描效率
218 lines
6.5 KiB
Go
218 lines
6.5 KiB
Go
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
|
||
} |