3 Commits
Author SHA1 Message Date
shadow1ng 2c921c375b Merge pull request #608 from NOPTrace/fix/smtp-deadline
发布 / auto-tag (push) Canceled after 0s
测试构建 / 代码检查 (push) Canceled after 0s
发布 / release (push) Canceled after 0s
测试构建 / 单元测试和构建 (push) Canceled after 0s
测试构建 / 构建验证 (push) Canceled after 0s
fix: set conn deadline in smtp testAnonymousAccess/testOpenRelay to avoid hang on silent servers
2026-09-17 12:19:08 +08:00
NOPTrace 6bb8ccd490 fix: set conn deadline in smtp testAnonymousAccess/testOpenRelay to avoid hang on silent servers
testAnonymousAccess and testOpenRelay dial the target and immediately
hand the connection to smtp.NewClient without setting any deadline.
smtp.NewClient reads the 220 greeting as its first operation, so a
server that accepts TCP but never sends data (honeypot/tarpit) blocks
the goroutine forever. The outer select waits on resultChan or
ctx.Done(); with the default -gt 0 the context has no deadline, so
Scan never returns and RunScan's wg.Wait() freezes the whole process.

Set the same ModuleTimeout deadline used by the other functions in
this file (doSMTPAuth, testVRFYCommand, testEXPNCommand, getServerInfo).

Verified against a live accept-but-silent SMTP endpoint:
- unpatched: process hangs (31 goroutines, stack at smtp.go:276)
- patched: full plugin chain completes in ~92s, no regression on
  normally-responding servers (detection output identical)
2026-09-17 09:24:05 +08:00
ZacharyZcR 95cc12e753 Merge pull request #604 from shadow1ng/dev
发布 / auto-tag (push) Canceled after 0s
测试构建 / 代码检查 (push) Canceled after 0s
发布 / release (push) Canceled after 0s
测试构建 / 单元测试和构建 (push) Canceled after 0s
测试构建 / 构建验证 (push) Canceled after 0s
release: v2.2.1
2026-08-26 04:43:42 +08:00
6 changed files with 21 additions and 110 deletions
+6
View File
@@ -243,6 +243,9 @@ func (p *SMTPPlugin) testAnonymousAccess(ctx context.Context, info *common.HostI
} }
defer func() { _ = conn.Close() }() defer func() { _ = conn.Close() }()
// 修复: 设置读写超时, 防止对只accept不发送banner的服务器(tarpit)永久阻塞
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
client, err := smtp.NewClient(conn, info.Host) client, err := smtp.NewClient(conn, info.Host)
if err != nil { if err != nil {
resultChan <- nil resultChan <- nil
@@ -295,6 +298,9 @@ func (p *SMTPPlugin) testOpenRelay(ctx context.Context, info *common.HostInfo, s
} }
defer func() { _ = conn.Close() }() defer func() { _ = conn.Close() }()
// 修复: 设置读写超时, 防止对只accept不发送banner的服务器(tarpit)永久阻塞
_ = conn.SetDeadline(time.Now().Add(session.Config.ModuleTimeout()))
client, err := smtp.NewClient(conn, info.Host) client, err := smtp.NewClient(conn, info.Host)
if err != nil { if err != nil {
resultChan <- nil resultChan <- nil
-10
View File
@@ -21,10 +21,6 @@ func registerMiscDeclarations() []*exprpb.Decl {
decls.NewOverload("tongda_date", decls.NewOverload("tongda_date",
[]*exprpb.Type{}, []*exprpb.Type{},
decls.String)), decls.String)),
decls.NewFunction("timestamp_second",
decls.NewOverload("timestamp_second_zero",
[]*exprpb.Type{},
decls.Int)),
} }
} }
@@ -51,11 +47,5 @@ func registerMiscImplementations() []*functions.Overload {
return types.String(time.Now().Format("0601")) return types.String(time.Now().Format("0601"))
}, },
}, },
{
Operator: "timestamp_second_zero",
Function: func(value ...ref.Val) ref.Val {
return types.Int(time.Now().Unix())
},
},
} }
} }
-17
View File
@@ -10,7 +10,6 @@ import (
"net/url" "net/url"
"strings" "strings"
"testing" "testing"
"time"
"github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types"
) )
@@ -1394,19 +1393,3 @@ func TestMakeVarDecl(t *testing.T) {
}) })
} }
} }
func TestTimestampSecond(t *testing.T) {
before := time.Now().Unix()
result, err := Evaluate(GetBaseEnv(), "timestamp_second()", map[string]interface{}{})
if err != nil {
t.Fatal(err)
}
got, ok := result.Value().(int64)
if !ok {
t.Fatalf("timestamp_second() type = %T, want int64", result.Value())
}
after := time.Now().Unix()
if got < before || got > after {
t.Fatalf("timestamp_second() = %d, want [%d, %d]", got, before, after)
}
}
-38
View File
@@ -869,10 +869,6 @@ func cloneMap(tags map[string]string) map[string]string {
// evalset 执行CEL表达式并处理特殊类型结果 // evalset 执行CEL表达式并处理特殊类型结果
func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (string, error) { func evalset(env *cel.Env, variableMap map[string]interface{}, k string, expression string) (string, error) {
if isPlainLiteral(expression, variableMap) {
variableMap[k] = expression
return expression, nil
}
out, err := Evaluate(env, expression, variableMap) out, err := Evaluate(env, expression, variableMap)
if err != nil { if err != nil {
variableMap[k] = "" variableMap[k] = ""
@@ -919,11 +915,6 @@ func isPlainLiteral(expr string, variableMap map[string]interface{}) bool {
if _, exists := variableMap[expr]; exists { if _, exists := variableMap[expr]; exists {
return false return false
} }
// Base64/JWT 常量常包含 +、/、= 或 .,这些字符在 CEL 中也是语法符号。
// 先识别编码值,避免把密钥和令牌误当成表达式编译。
if isEncodedLiteral(expr) {
return true
}
// 含 CEL 语法特征的需要走 CEL 编译 // 含 CEL 语法特征的需要走 CEL 编译
for _, c := range expr { for _, c := range expr {
switch c { switch c {
@@ -934,35 +925,6 @@ func isPlainLiteral(expr string, variableMap map[string]interface{}) bool {
return true return true
} }
func isEncodedLiteral(value string) bool {
if strings.Count(value, ".") == 2 {
parts := strings.Split(value, ".")
for _, part := range parts {
if part == "" || strings.IndexFunc(part, func(r rune) bool {
return !isASCIIAlphaNumeric(r) && r != '-' && r != '_'
}) >= 0 {
return false
}
}
return true
}
if len(value) < 4 || len(value)%4 != 0 {
return false
}
padding := strings.TrimRight(value, "=")
if len(value)-len(padding) > 2 {
return false
}
return strings.IndexFunc(padding, func(r rune) bool {
return !isASCIIAlphaNumeric(r) && r != '+' && r != '/'
}) < 0
}
func isASCIIAlphaNumeric(r rune) bool {
return r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9'
}
// CheckInfoPoc 检查POC信息并返回别名 // CheckInfoPoc 检查POC信息并返回别名
func CheckInfoPoc(infostr string) string { func CheckInfoPoc(infostr string) string {
for _, poc := range fingerprint.PocDatas { for _, poc := range fingerprint.PocDatas {
-30
View File
@@ -865,33 +865,3 @@ func TestCollectVarDeclarations(t *testing.T) {
} }
}) })
} }
func TestEvalSetTreatsEncodedValuesAsLiterals(t *testing.T) {
env := GetBaseEnv()
tests := []string{
"fsHspZw/92PrS3XrPW+vxw==",
"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJuYWNvcyJ9.feetKmWoPnMkAebjkNnyuKo6c21_hzTgu0dfNqbdpZQ",
}
for _, value := range tests {
variables := map[string]interface{}{}
got, err := evalset(env, variables, "token", value)
if err != nil {
t.Fatalf("evalset(%q) error = %v", value, err)
}
if got != value || variables["token"] != value {
t.Fatalf("evalset(%q) = %q, stored %v", value, got, variables["token"])
}
}
}
func TestEvalSetStillEvaluatesExpressions(t *testing.T) {
variables := map[string]interface{}{}
got, err := evalset(GetBaseEnv(), variables, "token", "randomLowercase(6)")
if err != nil {
t.Fatal(err)
}
if len(got) != 6 {
t.Fatalf("randomLowercase result length = %d, want 6", len(got))
}
}
@@ -12,7 +12,7 @@ info:
created: 2025/06/11 created: 2025/06/11
set: set:
randstr: randomLowercase(6) randstr: randLowercase(6)
rules: rules:
r0: r0:
request: request: