From b73996884f5ac32643fd3f602403736f6288d8ae Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Sun, 13 Apr 2025 13:07:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20SSH=E4=BC=98=E5=8C=96=E5=92=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Common/Config.go | 9 +- Plugins/SSH.go | 200 ++++++++++++++++++++++++++------------ TestDocker/SSH/Dockerfile | 2 +- TestDocker/SSH/README.txt | 2 +- 4 files changed, 145 insertions(+), 68 deletions(-) diff --git a/Common/Config.go b/Common/Config.go index 7e7696d..d2173d6 100644 --- a/Common/Config.go +++ b/Common/Config.go @@ -879,10 +879,11 @@ var ( AddPasswords string // 原PassAdd // 扫描配置 - ScanMode string // 原Scantype - ThreadNum int // 原Threads - //UseSynScan bool - Timeout int64 = 3 + ScanMode string // 原Scantype + ThreadNum int // 原Threads + ModuleThreadNum int = 10 + Timeout int64 = 3 + GlobalTimeout int64 = 180 LiveTop int DisablePing bool // 原NoPing UsePing bool // 原Ping diff --git a/Plugins/SSH.go b/Plugins/SSH.go index a7ca07a..67e3d6d 100644 --- a/Plugins/SSH.go +++ b/Plugins/SSH.go @@ -34,23 +34,114 @@ func SshScan(info *Common.HostInfo) error { target := fmt.Sprintf("%v:%v", info.Host, info.Ports) Common.LogDebug(fmt.Sprintf("开始扫描 %s", target)) - // 生成凭据列表 - credentials := generateCredentials(Common.Userdict["ssh"], Common.Passwords) - Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", - len(Common.Userdict["ssh"]), len(Common.Passwords), len(credentials))) + // 创建全局超时上下文 + globalCtx, globalCancel := context.WithTimeout(context.Background(), time.Duration(Common.GlobalTimeout)*time.Second) + defer globalCancel() - // 使用工作池并发扫描 - result := concurrentSshScan(info, credentials, Common.Timeout, Common.MaxRetries) - if result != nil { - // 记录成功结果 - logAndSaveSuccess(info, target, result) - return nil + // 创建结果通道 + resultChan := make(chan *SshScanResult, 1) + + // 启动一个协程进行扫描 + go func() { + // 如果指定了SSH密钥,使用密钥认证而非密码爆破 + if Common.SshKeyPath != "" { + Common.LogDebug(fmt.Sprintf("使用SSH密钥认证: %s", Common.SshKeyPath)) + + // 尝试使用密钥连接各个用户 + for _, user := range Common.Userdict["ssh"] { + select { + case <-globalCtx.Done(): + Common.LogDebug("全局超时,中止密钥认证") + return + default: + Common.LogDebug(fmt.Sprintf("尝试使用密钥认证用户: %s", user)) + + success, err := attemptKeyAuth(info, user, Common.SshKeyPath, Common.Timeout) + if success { + credential := SshCredential{ + Username: user, + Password: "", // 使用密钥,无密码 + } + + resultChan <- &SshScanResult{ + Success: true, + Credential: credential, + } + return + } else { + Common.LogDebug(fmt.Sprintf("密钥认证失败: %s, 错误: %v", user, err)) + } + } + } + + Common.LogDebug("所有用户密钥认证均失败") + resultChan <- nil + return + } + + // 否则使用密码爆破 + credentials := generateCredentials(Common.Userdict["ssh"], Common.Passwords) + Common.LogDebug(fmt.Sprintf("开始尝试用户名密码组合 (总用户数: %d, 总密码数: %d, 总组合数: %d)", + len(Common.Userdict["ssh"]), len(Common.Passwords), len(credentials))) + + // 使用工作池并发扫描 + result := concurrentSshScan(globalCtx, info, credentials, Common.Timeout, Common.MaxRetries, Common.ModuleThreadNum) + resultChan <- result + }() + + // 等待结果或全局超时 + select { + case result := <-resultChan: + if result != nil { + // 记录成功结果 + logAndSaveSuccess(info, target, result) + return nil + } + case <-globalCtx.Done(): + Common.LogDebug(fmt.Sprintf("扫描 %s 全局超时", target)) + return fmt.Errorf("全局超时,扫描未完成") } - Common.LogDebug(fmt.Sprintf("扫描完成,共尝试 %d 个组合", len(credentials))) + Common.LogDebug(fmt.Sprintf("扫描完成,未发现有效凭据")) return nil } +// attemptKeyAuth 尝试使用SSH密钥认证 +func attemptKeyAuth(info *Common.HostInfo, username, keyPath string, timeoutSeconds int64) (bool, error) { + pemBytes, err := ioutil.ReadFile(keyPath) + if err != nil { + return false, fmt.Errorf("读取密钥失败: %v", err) + } + + signer, err := ssh.ParsePrivateKey(pemBytes) + if err != nil { + return false, fmt.Errorf("解析密钥失败: %v", err) + } + + config := &ssh.ClientConfig{ + User: username, + Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + Timeout: time.Duration(timeoutSeconds) * time.Second, + } + + client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", info.Host, info.Ports), config) + if err != nil { + return false, err + } + defer client.Close() + + session, err := client.NewSession() + if err != nil { + return false, err + } + defer session.Close() + + return true, nil +} + // generateCredentials 生成所有用户名密码组合 func generateCredentials(users, passwords []string) []SshCredential { var credentials []SshCredential @@ -67,35 +158,38 @@ func generateCredentials(users, passwords []string) []SshCredential { } // concurrentSshScan 并发扫描SSH服务 -func concurrentSshScan(info *Common.HostInfo, credentials []SshCredential, timeout int64, maxRetries int) *SshScanResult { +func concurrentSshScan(ctx context.Context, info *Common.HostInfo, credentials []SshCredential, timeout int64, maxRetries, maxThreads int) *SshScanResult { // 限制并发数 - maxConcurrent := 10 - if maxConcurrent > len(credentials) { - maxConcurrent = len(credentials) + if maxThreads <= 0 { + maxThreads = 10 // 默认值 + } + + if maxThreads > len(credentials) { + maxThreads = len(credentials) } // 创建工作池 var wg sync.WaitGroup resultChan := make(chan *SshScanResult, 1) - workChan := make(chan SshCredential, maxConcurrent) - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + workChan := make(chan SshCredential, maxThreads) + scanCtx, scanCancel := context.WithCancel(ctx) + defer scanCancel() // 启动工作协程 - for i := 0; i < maxConcurrent; i++ { + for i := 0; i < maxThreads; i++ { wg.Add(1) go func() { defer wg.Done() for credential := range workChan { select { - case <-ctx.Done(): + case <-scanCtx.Done(): return default: result := trySshCredential(info, credential, timeout, maxRetries) if result.Success { select { case resultChan <- result: - cancel() // 找到有效凭据,取消其他工作 + scanCancel() // 找到有效凭据,取消其他工作 default: } return @@ -109,7 +203,7 @@ func concurrentSshScan(info *Common.HostInfo, credentials []SshCredential, timeo go func() { for i, cred := range credentials { select { - case <-ctx.Done(): + case <-scanCtx.Done(): break default: Common.LogDebug(fmt.Sprintf("[%d/%d] 尝试: %s:%s", i+1, len(credentials), cred.Username, cred.Password)) @@ -126,10 +220,15 @@ func concurrentSshScan(info *Common.HostInfo, credentials []SshCredential, timeo }() // 获取结果 - result, ok := <-resultChan - if ok { - return result + select { + case result, ok := <-resultChan: + if ok { + return result + } + case <-ctx.Done(): + Common.LogDebug("父上下文取消,中止所有扫描") } + return nil } @@ -178,7 +277,7 @@ func attemptSshConnection(info *Common.HostInfo, username, password string, time }, 1) go func() { - success, err := sshConnect(info, username, password) + success, err := sshConnect(info, username, password, timeoutSeconds) select { case <-ctx.Done(): case connChan <- struct { @@ -197,22 +296,8 @@ func attemptSshConnection(info *Common.HostInfo, username, password string, time } // sshConnect 建立SSH连接并验证 -func sshConnect(info *Common.HostInfo, username, password string) (bool, error) { - var auth []ssh.AuthMethod - if Common.SshKeyPath != "" { - pemBytes, err := ioutil.ReadFile(Common.SshKeyPath) - if err != nil { - return false, fmt.Errorf("读取密钥失败: %v", err) - } - - signer, err := ssh.ParsePrivateKey(pemBytes) - if err != nil { - return false, fmt.Errorf("解析密钥失败: %v", err) - } - auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} - } else { - auth = []ssh.AuthMethod{ssh.Password(password)} - } +func sshConnect(info *Common.HostInfo, username, password string, timeoutSeconds int64) (bool, error) { + auth := []ssh.AuthMethod{ssh.Password(password)} config := &ssh.ClientConfig{ User: username, @@ -220,7 +305,7 @@ func sshConnect(info *Common.HostInfo, username, password string) (bool, error) HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { return nil }, - Timeout: time.Duration(Common.Timeout) * time.Millisecond, + Timeout: time.Duration(timeoutSeconds) * time.Second, } client, err := ssh.Dial("tcp", fmt.Sprintf("%v:%v", info.Host, info.Ports), config) @@ -235,42 +320,33 @@ func sshConnect(info *Common.HostInfo, username, password string) (bool, error) } defer session.Close() - // 如果需要执行命令 - if Common.Command != "" { - _, err := session.CombinedOutput(Common.Command) - if err != nil { - return true, fmt.Errorf("命令执行失败: %v", err) - } - } - return true, nil } // logAndSaveSuccess 记录并保存成功结果 func logAndSaveSuccess(info *Common.HostInfo, target string, result *SshScanResult) { - successMsg := fmt.Sprintf("SSH认证成功 %s User:%v Pass:%v", - target, result.Credential.Username, result.Credential.Password) - Common.LogSuccess(successMsg) - + var successMsg string details := map[string]interface{}{ "port": info.Ports, "service": "ssh", "username": result.Credential.Username, - "password": result.Credential.Password, "type": "weak-password", } - // 如果使用了密钥认证,添加密钥信息 + // 区分密钥认证和密码认证 if Common.SshKeyPath != "" { + successMsg = fmt.Sprintf("SSH密钥认证成功 %s User:%v KeyPath:%v", + target, result.Credential.Username, Common.SshKeyPath) details["auth_type"] = "key" details["key_path"] = Common.SshKeyPath - details["password"] = nil + } else { + successMsg = fmt.Sprintf("SSH密码认证成功 %s User:%v Pass:%v", + target, result.Credential.Username, result.Credential.Password) + details["auth_type"] = "password" + details["password"] = result.Credential.Password } - // 如果执行了命令,添加命令信息 - if Common.Command != "" { - details["command"] = Common.Command - } + Common.LogSuccess(successMsg) vulnResult := &Common.ScanResult{ Time: time.Now(), diff --git a/TestDocker/SSH/Dockerfile b/TestDocker/SSH/Dockerfile index 9952185..f90937f 100644 --- a/TestDocker/SSH/Dockerfile +++ b/TestDocker/SSH/Dockerfile @@ -11,7 +11,7 @@ RUN mkdir /var/run/sshd # 允许root用户SSH登录并设置密码 RUN sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config -RUN echo 'root:123456' | chpasswd +RUN echo 'root:Aa123456789' | chpasswd # 开放22端口 EXPOSE 22 diff --git a/TestDocker/SSH/README.txt b/TestDocker/SSH/README.txt index 1d17fa9..ee1d95c 100644 --- a/TestDocker/SSH/README.txt +++ b/TestDocker/SSH/README.txt @@ -1,2 +1,2 @@ docker build -t ubuntu-ssh . -docker run -d -p 22:22 ubuntu-ssh \ No newline at end of file +docker run -d -p 2222:22 ubuntu-ssh \ No newline at end of file