fix: 修复实机测试发现的可靠性问题 (v2.2.0-rc.1)

- UDP 插件在 -p 指定端口时被跳过
- Redis exploit 无超时保护 / readReply 吞没非超时错误
- service_probe 连接丢失后静默成功
- SNMP 探测成功但终端无输出
- SSH 爆破不稳定 (并发过高 + 自适应超时过短 + 限流误判)
- 进度条 isActive 竞态

新增 Config.ModuleTimeout() 协议级超时下限 (≥3s)
新增 ErrorTypeThrottle 限流错误分类
This commit is contained in:
ZacharyZcR
2026-06-14 22:14:02 +08:00
parent bc46e90d89
commit 42092d8664
51 changed files with 285 additions and 257 deletions
+3 -3
View File
@@ -72,7 +72,7 @@ func (p *ActiveMQPlugin) createAuthFunc(info *common.HostInfo, session *common.S
func (p *ActiveMQPlugin) doActiveMQAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
target := info.Target()
config := session.Config
timeout := config.Timeout
timeout := config.ModuleTimeout()
resultChan := make(chan *AuthResult, 1)
@@ -157,7 +157,7 @@ func classifyActiveMQErrorType(err error) ErrorType {
// authenticateSTOMP 使用STOMP协议认证ActiveMQ
func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password string, config *common.Config) (bool, error) {
timeout := config.Timeout
timeout := config.ModuleTimeout()
if err := rejectLineBreaks(username, password); err != nil {
return false, err
}
@@ -202,7 +202,7 @@ func (p *ActiveMQPlugin) authenticateSTOMP(conn net.Conn, username, password str
// identifyService ActiveMQ服务识别
func (p *ActiveMQPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
target := info.Target()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
if err != nil {
+1 -1
View File
@@ -22,7 +22,7 @@ func NewBACnetPlugin() *BACnetPlugin {
}
func (p *BACnetPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+5 -4
View File
@@ -88,7 +88,7 @@ const (
func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
addr := info.Target()
timeout := config.Timeout
timeout := config.ModuleTimeout()
dialer := net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", addr)
@@ -270,7 +270,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
state := session.State
target := info.Target()
addr := info.Target()
timeout := config.Timeout
timeout := config.ModuleTimeout()
dialer := net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", addr)
@@ -286,7 +286,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
state.IncrementTCPFailedPacketCount()
return nil
}
opcode, body, err := cqlRecv(conn)
opcode, _, err := cqlRecv(conn)
if err != nil || opcode != cqlOpReady {
return nil
}
@@ -296,6 +296,7 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil {
return nil
}
var body []byte
opcode, body, err = cqlRecv(conn)
if err != nil {
return nil
@@ -321,7 +322,7 @@ func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.Host
state := session.State
target := info.Target()
addr := info.Target()
timeout := config.Timeout
timeout := config.ModuleTimeout()
dialer := net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", addr)
+12 -8
View File
@@ -37,9 +37,10 @@ credential_tester.go - 统一凭据测试框架
type ErrorType int
const (
ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试
ErrorTypeNetwork // 网络错误 - 连接问题,可重试
ErrorTypeUnknown // 未知错误
ErrorTypeAuth ErrorType = iota // 认证错误 - 密码错误,不重试
ErrorTypeNetwork // 网络错误 - 连接不可达,可重试但计入连续失败
ErrorTypeThrottle // 限流错误 - 服务端拒绝连接(MaxStartups等),退避后重试,不计入连续失败
ErrorTypeUnknown // 未知错误
)
// =============================================================================
@@ -321,10 +322,13 @@ func workerTestCredentials(
return
}
// 跟踪连续网络错误
if errType == ErrorTypeNetwork {
// 跟踪连续网络错误(限流错误不计入,只做短暂退避)
switch errType {
case ErrorTypeNetwork:
consecutiveNetErrors++
} else {
case ErrorTypeThrottle:
time.Sleep(500 * time.Millisecond)
default:
consecutiveNetErrors = 0
}
}
@@ -374,8 +378,8 @@ func testCredentialWithRetry(
case ErrorTypeAuth:
// 认证错误(密码错误),不重试
return nil, result.ErrorType
case ErrorTypeNetwork, ErrorTypeUnknown:
// 网络错误或未知错误,可以重试(可能是服务端限流等临时问题)
case ErrorTypeNetwork, ErrorTypeThrottle, ErrorTypeUnknown:
// 网络/限流/未知错误,可以重试
if attempt < testConfig.MaxRetries-1 {
timer := time.NewTimer(testConfig.RetryDelay)
select {
+1 -1
View File
@@ -19,7 +19,7 @@ func NewDNSPlugin() *DNSPlugin {
}
func (p *DNSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+1 -1
View File
@@ -21,7 +21,7 @@ func NewDNSTCPPlugin() *DNSTCPPlugin {
}
func (p *DNSTCPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+1 -1
View File
@@ -76,7 +76,7 @@ func (p *ElasticsearchPlugin) Scan(ctx context.Context, info *common.HostInfo, s
func (p *ElasticsearchPlugin) testCredential(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) bool {
config := session.Config
client := &http.Client{
Timeout: config.Timeout,
Timeout: config.ModuleTimeout(),
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
+2 -2
View File
@@ -50,7 +50,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session
}
}
conn, err := session.DialTCP(ctx, "tcp", target, config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, config.ModuleTimeout())
if err != nil {
return &ScanResult{
Success: false,
@@ -61,7 +61,7 @@ func (p *FindNetPlugin) Scan(ctx context.Context, info *common.HostInfo, session
defer func() { _ = conn.Close() }()
// 设置超时
_ = conn.SetDeadline(time.Now().Add(config.Timeout))
_ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout()))
// 执行RPC网络发现
networkInfo, err := p.performNetworkDiscovery(conn)
+3 -3
View File
@@ -81,7 +81,7 @@ func (p *FTPPlugin) createAuthFunc(info *common.HostInfo, config *common.Config,
func (p *FTPPlugin) doFTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
target := info.Target()
conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.Timeout)...)
conn, err := ftplib.Dial(target, ftpDialOptions(ctx, config.ModuleTimeout())...)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{
@@ -162,7 +162,7 @@ func (p *FTPPlugin) identifyService(info *common.HostInfo, session *common.ScanS
state := session.State
target := info.Target()
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout))
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout()))
if err != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{
@@ -241,7 +241,7 @@ func (p *FTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostIn
func (p *FTPPlugin) getFileListAfterAuth(info *common.HostInfo, username, password string, config *common.Config, state *common.State) []string {
target := info.Target()
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.Timeout))
conn, err := ftplib.Dial(target, ftplib.DialWithTimeout(config.ModuleTimeout()))
if err != nil {
return nil
}
+1 -1
View File
@@ -22,7 +22,7 @@ func NewIMAPPlugin() *IMAPPlugin {
func (p *IMAPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
config := session.Config
timeout := config.Timeout
timeout := config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+1 -65
View File
@@ -20,7 +20,7 @@ func NewIPMIPlugin() *IPMIPlugin {
}
func (p *IPMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
@@ -60,8 +60,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D
}
}
// getChannelAuth 需要独立连接,暂不执行(核心检测已完成)
return &ScanResult{
Success: true,
Type: plugins.ResultTypeVuln,
@@ -71,68 +69,6 @@ func (p *IPMIPlugin) rmcpPing(ctx context.Context, target string, timeout time.D
}
}
func (p *IPMIPlugin) getChannelAuth(conn interface {
Read([]byte) (int, error)
Write([]byte) (int, error)
SetDeadline(time.Time) error
}) string {
_ = conn.SetDeadline(time.Now().Add(2 * time.Second))
// IPMI Get Channel Authentication Capabilities
// RMCP header + IPMI session wrapper + message
pkt := []byte{
0x06, 0x00, 0xff, 0x07, // RMCP: version, reserved, seq=0xff, class=IPMI
0x00, 0x00, 0x00, 0x00, // auth type = none
0x00, 0x00, 0x00, 0x00, // session seq
0x00, 0x00, 0x00, 0x00, // session id
0x09, // message length
0x20, // target = BMC
0x18, // netFn=App(6) << 2 | lun=0
0xc8, // checksum
0x81, // source
0x00, // seq
0x38, // cmd = Get Channel Auth Capabilities
0x8e, // channel=14 (current), IPMI v2.0
0x04, // privilege = Administrator
0xb5, // checksum
}
if _, err := conn.Write(pkt); err != nil {
return ""
}
buf := make([]byte, 512)
n, err := conn.Read(buf)
if err != nil || n < 30 {
return ""
}
// Parse auth capabilities from response
if n >= 27 {
authTypes := buf[22]
var methods []string
if authTypes&0x01 != 0 {
methods = append(methods, "none")
}
if authTypes&0x02 != 0 {
methods = append(methods, "md2")
}
if authTypes&0x04 != 0 {
methods = append(methods, "md5")
}
if authTypes&0x10 != 0 {
methods = append(methods, "password")
}
if authTypes&0x20 != 0 {
methods = append(methods, "oem")
}
if len(methods) > 0 {
return fmt.Sprintf("[auth: %v]", methods)
}
}
return ""
}
func init() {
RegisterUDPPluginWithPorts("ipmi", func() Plugin {
return NewIPMIPlugin()
+1 -1
View File
@@ -23,7 +23,7 @@ func NewJDWPPlugin() *JDWPPlugin {
}
func (p *JDWPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+2 -2
View File
@@ -67,7 +67,7 @@ func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
target := info.Target()
timeout := config.Timeout
timeout := config.ModuleTimeout()
dialer := net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", target)
@@ -242,7 +242,7 @@ func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo
config := session.Config
state := session.State
target := info.Target()
timeout := config.Timeout
timeout := config.ModuleTimeout()
dialer := net.Dialer{Timeout: timeout}
conn, err := dialer.DialContext(ctx, "tcp", target)
+2 -2
View File
@@ -210,7 +210,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses
resultChan := make(chan result, 1)
go func() {
tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
tcpConn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- result{nil, err}
return
@@ -222,7 +222,7 @@ func (p *LDAPPlugin) connectLDAP(ctx context.Context, info *common.HostInfo, ses
} else {
conn = ldaplib.NewConn(tcpConn, false)
}
conn.SetTimeout(session.Config.Timeout)
conn.SetTimeout(session.Config.ModuleTimeout())
conn.Start()
resultChan <- result{conn, nil}
+3 -3
View File
@@ -68,7 +68,7 @@ func (p *MemcachedPlugin) testUnauthorizedAccess(ctx context.Context, info *comm
func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn {
target := info.Target()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
connChan := make(chan net.Conn, 1)
@@ -97,12 +97,12 @@ func (p *MemcachedPlugin) connectToMemcached(ctx context.Context, info *common.H
}
func (p *MemcachedPlugin) testBasicCommand(conn net.Conn, config *common.Config) bool {
_ = conn.SetWriteDeadline(time.Now().Add(config.Timeout))
_ = conn.SetWriteDeadline(time.Now().Add(config.ModuleTimeout()))
if _, err := conn.Write([]byte("version\r\n")); err != nil {
return false
}
_ = conn.SetReadDeadline(time.Now().Add(config.Timeout))
_ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout()))
response := make([]byte, 1024)
n, err := conn.Read(response)
if err != nil {
+1 -1
View File
@@ -21,7 +21,7 @@ func NewModbusPlugin() *ModbusPlugin {
}
func (p *ModbusPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+4 -8
View File
@@ -91,7 +91,7 @@ func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Con
func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
addr := info.Target()
timeout := config.Timeout
timeout := config.ModuleTimeout()
conn, err := dialTCP(ctx, addr, timeout)
if err != nil {
@@ -369,6 +369,7 @@ type mongoCommandReply struct {
errmsg string
}
//nolint:gocyclo
func parseMongoCommandReply(doc []byte) (mongoCommandReply, error) {
var reply mongoCommandReply
if len(doc) < 5 {
@@ -557,11 +558,6 @@ func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn,
return dialer.DialContext(ctx, "tcp", addr)
}
// base64EncodeStr Base64 编码(标准编码)
func base64EncodeStr(s string) string {
return base64.StdEncoding.EncodeToString([]byte(s))
}
// randomString 生成加密安全的随机字符串
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
@@ -641,7 +637,7 @@ func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo
}
func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) {
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout())
if err != nil {
return "", fmt.Errorf(i18n.Tr("service_connection_failed", "%w"), err)
}
@@ -653,7 +649,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
default:
}
if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.Timeout)); deadlineErr != nil {
if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); deadlineErr != nil {
return "", deadlineErr
}
+1 -1
View File
@@ -32,7 +32,7 @@ func NewMQTTPlugin() *MQTTPlugin {
}
func (p *MQTTPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+2 -2
View File
@@ -291,13 +291,13 @@ func (p *MS17010Plugin) checkMS17010Vulnerability(ctx context.Context, ip string
}
func (p *MS17010Plugin) checkMS17010VulnerabilityAt(ctx context.Context, address string, session *common.ScanSession) (bool, string, bool, error) {
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", address, session.Config.ModuleTimeout())
if err != nil {
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_connection_error"), err)
}
defer func() { _ = conn.Close() }()
if err = conn.SetDeadline(time.Now().Add(session.Config.Timeout)); err != nil {
if err = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout())); err != nil {
return false, "", false, fmt.Errorf("%s: %w", i18n.GetText("ms17010_set_timeout_error"), err)
}
+4 -4
View File
@@ -63,10 +63,10 @@ func (p *MSSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
// doMSSQLAuth 执行MSSQL认证
func (p *MSSQLPlugin) doMSSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
authCtx, cancel := context.WithTimeout(ctx, config.Timeout)
authCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
defer cancel()
_, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.Timeout)
_, err := mssqlRawLogin(authCtx, info.Host, info.Port, cred.Username, cred.Password, config.ModuleTimeout())
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{
@@ -129,10 +129,10 @@ func (p *MSSQLPlugin) identifyService(ctx context.Context, info *common.HostInfo
state := session.State
target := info.Target()
identifyCtx, cancel := context.WithTimeout(ctx, config.Timeout)
identifyCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
defer cancel()
result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.Timeout)
result, err := mssqlRawLogin(identifyCtx, info.Host, info.Port, "invalid", "invalid", config.ModuleTimeout())
if err != nil {
state.IncrementTCPFailedPacketCount()
+3 -3
View File
@@ -79,7 +79,7 @@ func (p *MySQLPlugin) createAuthFunc(info *common.HostInfo, config *common.Confi
// doMySQLAuth 执行MySQL认证
func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.Timeout)
connStr, err := mySQLConnString(cred.Username, cred.Password, info, config.ModuleTimeout())
if err != nil {
return &AuthResult{
Success: false,
@@ -98,7 +98,7 @@ func (p *MySQLPlugin) doMySQLAuth(ctx context.Context, info *common.HostInfo, cr
}
}
db.SetConnMaxLifetime(config.Timeout)
db.SetConnMaxLifetime(config.ModuleTimeout())
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
@@ -194,7 +194,7 @@ func (p *MySQLPlugin) identifyService(ctx context.Context, info *common.HostInfo
}
func (p *MySQLPlugin) readMySQLBanner(conn net.Conn, config *common.Config) string {
_ = conn.SetReadDeadline(time.Now().Add(config.Timeout))
_ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout()))
header := make([]byte, 5)
if _, err := io.ReadFull(conn, header); err != nil {
+3 -3
View File
@@ -72,7 +72,7 @@ func (p *Neo4jPlugin) doNeo4jAuth(ctx context.Context, info *common.HostInfo, cr
config := session.Config
baseURL := "http://" + info.Target()
client := &http.Client{Timeout: config.Timeout}
client := &http.Client{Timeout: config.ModuleTimeout()}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/user/neo4j", nil)
if err != nil {
@@ -148,7 +148,7 @@ func (p *Neo4jPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
config := session.Config
baseURL := "http://" + info.Target()
client := &http.Client{Timeout: config.Timeout}
client := &http.Client{Timeout: config.ModuleTimeout()}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/db/data/", nil)
if err != nil {
@@ -193,7 +193,7 @@ func (p *Neo4jPlugin) identifyService(ctx context.Context, info *common.HostInfo
target := info.Target()
baseURL := "http://" + info.Target()
client := &http.Client{Timeout: config.Timeout}
client := &http.Client{Timeout: config.ModuleTimeout()}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
if err != nil {
+4 -4
View File
@@ -164,14 +164,14 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st
target := fmt.Sprintf("%s:137", host)
conn, err := net.DialTimeout("udp", target, config.Timeout)
conn, err := net.DialTimeout("udp", target, config.ModuleTimeout())
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_name_connect_failed"), err)
}
state.IncrementUDPPacketCount()
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(config.Timeout))
_ = conn.SetDeadline(time.Now().Add(config.ModuleTimeout()))
_, err = conn.Write(queryPacket)
if err != nil {
@@ -191,13 +191,13 @@ func (p *NetBIOSPlugin) queryNetBIOSNames(host string, config *common.Config, st
func (p *NetBIOSPlugin) queryNetBIOSSession(ctx context.Context, host string, session *common.ScanSession) (*NetBIOSInfo, error) {
target := fmt.Sprintf("%s:139", host)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
return nil, fmt.Errorf("%s: %w", i18n.GetText("netbios_session_connect_failed"), err)
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
// 发送SMB协商数据包
smbNegotiate1 := []byte{
+1 -1
View File
@@ -22,7 +22,7 @@ func NewNFSPlugin() *NFSPlugin {
}
func (p *NFSPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+3 -3
View File
@@ -72,8 +72,8 @@ func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo,
serviceNames := []string{"ORCL", "XE", "XEPDB1", target}
for _, serviceName := range serviceNames {
connectCtx, cancel := context.WithTimeout(ctx, config.Timeout)
err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.Timeout)
connectCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.ModuleTimeout())
if err != nil {
cancel()
errorType := classifyOracleErrorType(err)
@@ -168,7 +168,7 @@ func (p *OraclePlugin) testUnauthorizedAccess(ctx context.Context, info *common.
func (p *OraclePlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
target := info.Target()
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
return &ScanResult{
Success: false,
+1 -1
View File
@@ -23,7 +23,7 @@ func NewPOP3Plugin() *POP3Plugin {
func (p *POP3Plugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
config := session.Config
timeout := config.Timeout
timeout := config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+9 -9
View File
@@ -73,7 +73,7 @@ func (p *PostgreSQLPlugin) createAuthFunc(info *common.HostInfo, config *common.
// doPostgreSQLAuth 执行PostgreSQL认证
func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.Timeout.Seconds()))
connStr := postgreSQLConnString(cred.Username, cred.Password, info, int64(config.ModuleTimeout().Seconds()))
db, err := sql.Open("postgres", connStr)
if err != nil {
@@ -85,11 +85,11 @@ func (p *PostgreSQLPlugin) doPostgreSQLAuth(ctx context.Context, info *common.Ho
}
}
db.SetConnMaxLifetime(config.Timeout)
db.SetConnMaxLifetime(config.ModuleTimeout())
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
defer cancel()
err = db.PingContext(pingCtx)
@@ -169,7 +169,7 @@ func postgreSQLConnString(username, password string, info *common.HostInfo, time
// testUnauthorizedAccess 测试PostgreSQL未授权访问
func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
connStr := postgreSQLConnString("postgres", "", info, int64(config.Timeout.Seconds()))
connStr := postgreSQLConnString("postgres", "", info, int64(config.ModuleTimeout().Seconds()))
db, err := sql.Open("postgres", connStr)
if err != nil {
@@ -177,11 +177,11 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com
}
defer func() { _ = db.Close() }()
db.SetConnMaxLifetime(config.Timeout)
db.SetConnMaxLifetime(config.ModuleTimeout())
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(0)
pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
defer cancel()
err = db.PingContext(pingCtx)
@@ -192,7 +192,7 @@ func (p *PostgreSQLPlugin) testUnauthorizedAccess(ctx context.Context, info *com
state.IncrementTCPSuccessPacketCount()
queryCtx, queryCancel := context.WithTimeout(ctx, config.Timeout)
queryCtx, queryCancel := context.WithTimeout(ctx, config.ModuleTimeout())
defer queryCancel()
var version string
@@ -222,7 +222,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos
state := session.State
target := info.Target()
connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.Timeout.Seconds()))
connStr := postgreSQLConnString("invalid", "invalid", info, int64(config.ModuleTimeout().Seconds()))
db, err := sql.Open("postgres", connStr)
if err != nil {
@@ -234,7 +234,7 @@ func (p *PostgreSQLPlugin) identifyService(ctx context.Context, info *common.Hos
}
defer func() { _ = db.Close() }()
pingCtx, cancel := context.WithTimeout(ctx, config.Timeout)
pingCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
defer cancel()
err = db.PingContext(pingCtx)
+5 -5
View File
@@ -84,7 +84,7 @@ func (p *RabbitMQPlugin) doRabbitMQAuth(ctx context.Context, info *common.HostIn
}
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
client := &http.Client{Timeout: config.Timeout}
client := &http.Client{Timeout: config.ModuleTimeout()}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
if err != nil {
@@ -165,7 +165,7 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo
}
baseURL := "http://" + net.JoinHostPort(info.Host, strconv.Itoa(port))
client := &http.Client{Timeout: config.Timeout}
client := &http.Client{Timeout: config.ModuleTimeout()}
// 测试无认证访问
req, err := http.NewRequestWithContext(ctx, "GET", baseURL+"/api/overview", nil)
@@ -213,13 +213,13 @@ func (p *RabbitMQPlugin) testUnauthorizedAccess(ctx context.Context, info *commo
func (p *RabbitMQPlugin) testAMQPProtocol(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
target := info.Target()
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
return nil
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
// 发送AMQP协议头
amqpHeader := []byte{0x41, 0x4d, 0x51, 0x50, 0x00, 0x00, 0x09, 0x01}
@@ -280,7 +280,7 @@ func (p *RabbitMQPlugin) testManagementInterface(ctx context.Context, info *comm
target := info.Target()
baseURL := "http://" + info.Target()
client := &http.Client{Timeout: config.Timeout}
client := &http.Client{Timeout: config.ModuleTimeout()}
req, err := http.NewRequestWithContext(ctx, "GET", baseURL, nil)
if err != nil {
+2 -2
View File
@@ -159,7 +159,7 @@ func (p *RDPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
// rdpCrack 使用NLA认证验证凭据,不建立完整会话,不会挤掉已登录用户
func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common.Config, state *common.State) (bool, error) {
timeout := int64(config.Timeout.Seconds())
timeout := int64(config.ModuleTimeout().Seconds())
// 使用NLA仅验证模式:只验证凭据,不建立RDP会话
// 这样不会挤掉目标机器上已登录的用户
@@ -180,7 +180,7 @@ func (p *RDPPlugin) rdpCrack(host, domain, user, password string, config *common
// probeOSInfo 通过NLA协商获取系统信息(无需密码)
func (p *RDPPlugin) probeOSInfo(host string, config *common.Config, state *common.State) map[string]any {
timeout := int64(config.Timeout.Seconds())
timeout := int64(config.ModuleTimeout().Seconds())
client := login.NewClient(host, glog.NONE)
// 使用 PROTOCOL_HYBRID 协议探测系统信息
+18 -8
View File
@@ -86,7 +86,7 @@ func (p *RedisPlugin) createAuthFunc(info *common.HostInfo, session *common.Scan
// doRedisAuth 执行Redis认证
func (p *RedisPlugin) doRedisAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
target := info.Target()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
// 建立TCP连接
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
@@ -223,7 +223,7 @@ func (p *RedisPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.HostInfo, password string, session *common.ScanSession) {
target := info.Target()
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
session.LogError(i18n.Tr("redis_reconnect_failed", err))
return
@@ -232,11 +232,11 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host
// 如果有密码,先认证
if password != "" {
_ = conn.SetWriteDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetWriteDeadline(time.Now().Add(session.Config.ModuleTimeout()))
if _, writeErr := conn.Write(buildRedisAuthCommand(password)); writeErr != nil {
return
}
_ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout()))
response := make([]byte, 512)
if _, readErr := conn.Read(response); readErr != nil {
return
@@ -249,7 +249,7 @@ func (p *RedisPlugin) exploitWithPassword(ctx context.Context, info *common.Host
// identifyService 服务识别
func (p *RedisPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
target := info.Target()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
conn, err := session.DialTCP(ctx, "tcp", target, timeout)
if err != nil {
@@ -325,7 +325,7 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n
return
}
_ = conn.SetDeadline(time.Time{})
_ = conn.SetDeadline(time.Now().Add(30 * time.Second))
dbfilename, dir, err := p.getConfig(conn)
if err != nil {
@@ -397,14 +397,24 @@ func (p *RedisPlugin) exploit(ctx context.Context, info *common.HostInfo, conn n
// =============================================================================
func (p *RedisPlugin) readReply(conn net.Conn) (string, error) {
_ = conn.SetReadDeadline(time.Now().Add(time.Second))
_ = conn.SetReadDeadline(time.Now().Add(3 * time.Second))
bytes, err := io.ReadAll(io.LimitReader(conn, maxRedisReplyBytes))
if len(bytes) > 0 {
if len(bytes) > 0 && isTimeoutError(err) {
err = nil
}
return string(bytes), err
}
func isTimeoutError(err error) bool {
if err == nil {
return false
}
if ne, ok := err.(net.Error); ok && ne.Timeout() {
return true
}
return false
}
// sendCmd 发送Redis命令并检查OK响应
// 返回响应文本、是否成功、错误
func (p *RedisPlugin) sendCmd(conn net.Conn, cmd []byte) (text string, ok bool, err error) {
+1 -1
View File
@@ -24,7 +24,7 @@ func NewRMIPlugin() *RMIPlugin {
}
func (p *RMIPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+3 -3
View File
@@ -242,7 +242,7 @@ func (p *RsyncPlugin) testUnauthorizedAccess(ctx context.Context, info *common.H
// connectToRsync 连接到Rsync服务
func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo, session *common.ScanSession) net.Conn {
target := info.Target()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
connChan := make(chan net.Conn, 1)
@@ -272,7 +272,7 @@ func (p *RsyncPlugin) connectToRsync(ctx context.Context, info *common.HostInfo,
// getModules 获取Rsync模块列表
func (p *RsyncPlugin) getModules(conn net.Conn, config *common.Config) []string {
timeout := config.Timeout
timeout := config.ModuleTimeout()
// 读取服务器版本
_ = conn.SetReadDeadline(time.Now().Add(timeout))
@@ -342,7 +342,7 @@ func (p *RsyncPlugin) identifyService(ctx context.Context, info *common.HostInfo
}
defer func() { _ = conn.Close() }()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
_ = conn.SetWriteDeadline(time.Now().Add(timeout))
if _, err := conn.Write([]byte("\n")); err != nil {
+4 -4
View File
@@ -39,7 +39,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
}
// 1. 协议探测和信息收集
smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.Timeout, session)
smbTarget, err := probeTarget(ctx, info.Host, info.Port, config.ModuleTimeout(), session)
if err != nil {
return &ScanResult{
Success: false,
@@ -53,7 +53,7 @@ func (p *SmbPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
// 2. 漏洞检测 (仅SMBv2+且端口445)
if smbTarget.Protocol == SMBProtocol2 && info.Port == 445 {
if checkSMBGhost(ctx, info.Host, config.Timeout, session) {
if checkSMBGhost(ctx, info.Host, config.ModuleTimeout(), session) {
smbTarget.Vulnerable = &SMBVuln{CVE20200796: true}
session.LogVuln(i18n.Tr("smbghost_vuln", target))
}
@@ -120,7 +120,7 @@ func (p *SmbPlugin) getAuthenticator(protocol SMBProtocol) SMBAuthenticator {
func (p *SmbPlugin) createAuthFunc(info *common.HostInfo, auth SMBAuthenticator, session *common.ScanSession) AuthFunc {
config := session.Config
return func(ctx context.Context, cred Credential) *AuthResult {
result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session)
result, _ := auth.Authenticate(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session)
return result
}
}
@@ -136,7 +136,7 @@ func (p *SmbPlugin) testUnauthorizedAccess(ctx context.Context, info *common.Hos
}
for _, cred := range unauthorizedCreds {
shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.Timeout, session)
shareInfo, err := auth.ListShares(ctx, info.Host, info.Port, cred, config.Credentials.Domain, config.ModuleTimeout(), session)
if err == nil && len(shareInfo) > 0 {
var output strings.Builder
displayUser := cred.Username
+10 -10
View File
@@ -78,7 +78,7 @@ func (p *SMTPPlugin) createAuthFunc(info *common.HostInfo, session *common.ScanS
// doSMTPAuth 执行SMTP认证
func (p *SMTPPlugin) doSMTPAuth(ctx context.Context, info *common.HostInfo, cred Credential, session *common.ScanSession) *AuthResult {
target := info.Target()
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
resultChan := make(chan *AuthResult, 1)
@@ -236,7 +236,7 @@ func (p *SMTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostI
resultChan := make(chan *ScanResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- nil
return
@@ -288,7 +288,7 @@ func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo, s
resultChan := make(chan *ScanResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- nil
return
@@ -340,14 +340,14 @@ func (p *SMTPPlugin) testVRFYCommand(ctx context.Context, info *common.HostInfo,
resultChan := make(chan *ScanResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- nil
return
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil {
resultChan <- nil
@@ -410,14 +410,14 @@ func (p *SMTPPlugin) testEXPNCommand(ctx context.Context, info *common.HostInfo,
resultChan := make(chan *ScanResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- nil
return
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
if _, heloWriteErr := fmt.Fprintf(conn, "HELO fscan.test\r\n"); heloWriteErr != nil {
resultChan <- nil
@@ -480,14 +480,14 @@ func (p *SMTPPlugin) getServerInfo(ctx context.Context, info *common.HostInfo, s
resultChan := make(chan string, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- ""
return
}
defer func() { _ = conn.Close() }()
_ = conn.SetReadDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetReadDeadline(time.Now().Add(session.Config.ModuleTimeout()))
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
if err != nil {
@@ -524,7 +524,7 @@ func (p *SMTPPlugin) identifyService(ctx context.Context, info *common.HostInfo,
if serverInfo != "" {
banner = i18n.Tr("smtp_mail_service_info", serverInfo)
} else {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
return &ScanResult{
Success: false,
+3 -4
View File
@@ -23,10 +23,7 @@ func NewSNMPPlugin() *SNMPPlugin {
func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
config := session.Config
timeout := config.Timeout
if timeout <= 0 {
timeout = 3 * time.Second
}
timeout := config.ModuleTimeout()
target := info.Target()
@@ -35,6 +32,8 @@ func (p *SNMPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *c
return &ScanResult{Success: false, Service: "snmp"}
}
session.LogVuln(fmt.Sprintf("SNMP %s %s", target, result.Banner))
if config.DisableBrute {
return result
}
+45 -11
View File
@@ -64,8 +64,12 @@ func (p *SSHPlugin) Scan(ctx context.Context, info *common.HostInfo, session *co
}
// 使用公共框架进行并发凭据测试
// SSH 并发限制为 3OpenSSH MaxStartups 默认 10:30:60,高并发会被随机丢弃
authFn := p.createAuthFunc(info, session)
testConfig := DefaultConcurrentTestConfigWithTarget(config, info)
if testConfig.Concurrency > 3 {
testConfig.Concurrency = 3
}
result := TestCredentialsConcurrently(ctx, credentials, authFn, "ssh", testConfig)
@@ -90,9 +94,10 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
target := info.Target()
// 创建SSH配置
moduleTimeout := config.ModuleTimeout()
sshConfig := &ssh.ClientConfig{
User: cred.Username,
Timeout: config.Timeout,
Timeout: moduleTimeout,
//nolint:gosec // G106: 扫描工具需要忽略主机密钥验证以连接未知主机
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
@@ -133,6 +138,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
}
}()
// 设置 TCP 级别 deadline 兜底整个握手过程
_ = conn.SetDeadline(time.Now().Add(moduleTimeout))
// 在TCP连接上创建SSH客户端
sshConn, chans, reqs, err := ssh.NewClientConn(conn, target, sshConfig)
if err != nil {
@@ -151,6 +159,9 @@ func (p *SSHPlugin) doSSHAuth(ctx context.Context, info *common.HostInfo, cred C
}
}
// 握手成功,清除 deadline
_ = conn.SetDeadline(time.Time{})
// 创建SSH客户端
client := ssh.NewClient(sshConn, chans, reqs)
@@ -183,16 +194,39 @@ func classifySSHErrorType(err error) ErrorType {
"no supported methods remain",
)
// SSH 特有的网络/临时错误(需要重试)
sshNetworkErrors := append(CommonNetworkErrors,
"handshake failed", // 握手失败,可能是服务端限流
"ssh: disconnect", // SSH 主动断开
"connection closed", // 连接被关闭
"max startups", // SSH MaxStartups 限制
"too many authentication", // 认证次数过多
)
// SSH 限流错误 — 服务端主动拒绝(MaxStartups 等),退避后重试即可
sshThrottleErrors := []string{
"handshake failed",
"ssh: disconnect",
"connection closed",
"max startups",
"too many authentication",
}
return ClassifyError(err, sshAuthErrors, sshNetworkErrors)
return classifySSHError(err, sshAuthErrors, sshThrottleErrors)
}
func classifySSHError(err error, authKeywords, throttleKeywords []string) ErrorType {
if err == nil {
return ErrorTypeUnknown
}
errStr := err.Error()
for _, kw := range authKeywords {
if containsIgnoreCase(errStr, kw) {
return ErrorTypeAuth
}
}
for _, kw := range throttleKeywords {
if containsIgnoreCase(errStr, kw) {
return ErrorTypeThrottle
}
}
for _, kw := range CommonNetworkErrors {
if containsIgnoreCase(errStr, kw) {
return ErrorTypeNetwork
}
}
return ErrorTypeUnknown
}
// scanWithKey 使用SSH私钥扫描
@@ -272,7 +306,7 @@ func (p *SSHPlugin) identifyService(ctx context.Context, info *common.HostInfo,
// readSSHBanner 读取SSH服务器Banner
func (p *SSHPlugin) readSSHBanner(conn net.Conn, config *common.Config) string {
_ = conn.SetReadDeadline(time.Now().Add(config.Timeout))
_ = conn.SetReadDeadline(time.Now().Add(config.ModuleTimeout()))
banner := make([]byte, 256)
n, err := conn.Read(banner)
+9 -9
View File
@@ -121,7 +121,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo,
resultChan := make(chan *AuthResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- &AuthResult{
Success: false,
@@ -131,7 +131,7 @@ func (p *TelnetPlugin) doTelnetAuth(ctx context.Context, info *common.HostInfo,
return
}
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
if p.performTelnetAuth(conn, cred.Username, cred.Password) {
resultChan <- &AuthResult{
@@ -215,14 +215,14 @@ func (p *TelnetPlugin) testUnauthAccess(ctx context.Context, info *common.HostIn
resultChan := make(chan *ScanResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- nil
return
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
buffer := make([]byte, 1024)
attempts := 0
@@ -510,7 +510,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
resultChan := make(chan *ScanResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- &ScanResult{
Success: false,
@@ -521,7 +521,7 @@ func (p *TelnetPlugin) identifyService(ctx context.Context, info *common.HostInf
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
buffer := make([]byte, 2048)
n, err := conn.Read(buffer)
@@ -595,14 +595,14 @@ func (p *TelnetPlugin) verifyCommandExecution(ctx context.Context, info *common.
resultChan := make(chan rceResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- rceResult{}
return
}
defer func() { _ = conn.Close() }()
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout + telnetRCEExtraTimeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout() + telnetRCEExtraTimeout))
// 需要认证时先登录
if username != "" || password != "" {
@@ -808,7 +808,7 @@ func (p *TelnetPlugin) checkCVE202624061Concurrent(ctx context.Context, info *co
// 利用 NEW-ENVIRON (option 39) 子协商注入恶意环境变量,实现认证绕过
// 返回 (是否漏洞, 触发用户名, 证据)
func (p *TelnetPlugin) checkCVE202624061(ctx context.Context, info *common.HostInfo, session *common.ScanSession, user string) (bool, string, string) {
conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", info.Target(), session.Config.ModuleTimeout())
if err != nil {
return false, "", ""
}
+1 -1
View File
@@ -21,7 +21,7 @@ func NewTFTPPlugin() *TFTPPlugin {
}
func (p *TFTPPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}
+2 -2
View File
@@ -74,7 +74,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C
resultChan := make(chan *AuthResult, 1)
go func() {
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.Timeout)
conn, err := session.DialTCP(ctx, "tcp", target, session.Config.ModuleTimeout())
if err != nil {
resultChan <- &AuthResult{
Success: false,
@@ -84,7 +84,7 @@ func (p *VNCPlugin) doVNCAuth(ctx context.Context, info *common.HostInfo, cred C
return
}
_ = conn.SetDeadline(time.Now().Add(session.Config.Timeout))
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
vncConfig := &vnc.ClientConfig{
Auth: []vnc.ClientAuth{
+1 -1
View File
@@ -20,7 +20,7 @@ func NewZooKeeperPlugin() *ZooKeeperPlugin {
}
func (p *ZooKeeperPlugin) Scan(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
timeout := session.Config.Timeout
timeout := session.Config.ModuleTimeout()
if timeout <= 0 {
timeout = 3 * time.Second
}