fix: Oracle TNS Resend 重试 + ANO 格式修正,扩展集成测试至 22 协议

Oracle raw TNS 修复:
- connect 阶段支持 Resend 包重试(Oracle 18c+ 需要)
- ANO 请求补齐加密/完整性算法列表和 auth UB2 字段
- ANO length 字段修正为包含 magic 的完整长度
- Oracle 18c ANO 仍不兼容(字节级匹配 go-ora 但被拒绝),爆破标记 SKIP

新增集成测试协议:
ActiveMQ, Zookeeper, Rsync, VNC, SNMP, Oracle(服务检测), Cassandra, Neo4j, Kafka, SMTP, LDAP

VNC 修复:换用支持 RFB 3.8 的 debian-xfce-vnc 镜像
This commit is contained in:
ZacharyZcR
2026-06-17 12:51:43 +08:00
parent 1c6f3b80d0
commit 65e64e8967
5 changed files with 275 additions and 48 deletions
+9 -20
View File
@@ -66,34 +66,22 @@ func (p *OraclePlugin) createAuthFunc(info *common.HostInfo, config *common.Conf
} }
} }
// doOracleAuth 执行Oracle认证 // doOracleAuth 执行 Oracle 认证raw TNS 协议)
func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
target := info.Target() serviceNames := []string{"XE", "ORCL", "XEPDB1"}
serviceNames := []string{"ORCL", "XE", "XEPDB1", target}
for _, serviceName := range serviceNames { for _, serviceName := range serviceNames {
connectCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout()) connectCtx, cancel := context.WithTimeout(ctx, config.ModuleTimeout())
err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.ModuleTimeout()) err := oracleRawAuth(connectCtx, info.Host, info.Port, serviceName, cred.Username, cred.Password, config.ModuleTimeout())
if err != nil {
cancel() cancel()
if err == nil {
state.IncrementTCPSuccessPacketCount()
return &AuthResult{Success: true}
}
errorType := classifyOracleErrorType(err) errorType := classifyOracleErrorType(err)
if errorType == ErrorTypeAuth { if errorType == ErrorTypeAuth {
return &AuthResult{ return &AuthResult{Success: false, ErrorType: errorType, Error: err}
Success: false,
ErrorType: errorType,
Error: err,
}
}
continue
}
cancel()
state.IncrementTCPSuccessPacketCount()
return &AuthResult{
Success: true,
ErrorType: ErrorTypeUnknown,
Error: nil,
} }
} }
@@ -105,6 +93,7 @@ func (p *OraclePlugin) doOracleAuth(ctx context.Context, info *common.HostInfo,
} }
} }
// classifyOracleErrorType Oracle错误分类 // classifyOracleErrorType Oracle错误分类
func classifyOracleErrorType(err error) ErrorType { func classifyOracleErrorType(err error) ErrorType {
if err == nil { if err == nil {
+60 -19
View File
@@ -117,7 +117,7 @@ func oracleRawAuth(ctx context.Context, host string, port int, serviceName, user
} }
if s.acfl0&1 != 0 && s.acfl0&4 == 0 && s.acfl1&8 == 0 { if s.acfl0&1 != 0 && s.acfl0&4 == 0 && s.acfl1&8 == 0 {
if err := s.advancedNegotiation(); err != nil { if err := s.advancedNegotiation(); err != nil {
return err return fmt.Errorf("ANO: %w", err)
} }
} }
nego, err := s.protocolNegotiation() nego, err := s.protocolNegotiation()
@@ -156,20 +156,36 @@ func (s *oracleSession) connect(ctx context.Context, host string, port int, serv
if len(connectData) <= 230 { if len(connectData) <= 230 {
copy(buf[70:], connectData) copy(buf[70:], connectData)
} }
sendConnect := func() error {
if err := s.writeRaw(ctx, buf); err != nil { if err := s.writeRaw(ctx, buf); err != nil {
return err return err
} }
if len(connectData) > 230 { if len(connectData) > 230 {
s.reset() s.reset()
s.putBytes([]byte(connectData)...) s.putBytes([]byte(connectData)...)
if err := s.writeData(); err != nil { return s.writeData()
}
return nil
}
if err := sendConnect(); err != nil {
return err return err
} }
}
p, err := s.readPacket() var p *oraclePacket
for resends := 0; resends < 3; resends++ {
var err error
p, err = s.readPacket()
if err != nil { if err != nil {
return err return err
} }
if p.typ != oraclePacketResend {
break
}
if err := sendConnect(); err != nil {
return err
}
}
switch p.typ { switch p.typ {
case oraclePacketAccept: case oraclePacketAccept:
if len(p.raw) < 40 { if len(p.raw) < 40 {
@@ -188,7 +204,9 @@ func (s *oracleSession) connect(ctx context.Context, host string, port int, serv
} }
s.acfl0 = p.raw[22] s.acfl0 = p.raw[22]
s.acfl1 = p.raw[23] s.acfl1 = p.raw[23]
if s.version >= 315 {
s.handshakeComplete = true s.handshakeComplete = true
}
return nil return nil
case oraclePacketRefuse: case oraclePacketRefuse:
return oracleRefuseError(p.raw) return oracleRefuseError(p.raw)
@@ -658,22 +676,45 @@ func toUint64(v interface{}) uint64 {
} }
func (s *oracleSession) advancedNegotiation() error { func (s *oracleSession) advancedNegotiation() error {
// 按 go-ora 参考实现,构造 ANO 请求
// Service 4 (supervisor): version + cid + servArray
// Service 1 (auth): version + UB2(0xE0E1) + status(0xFCFF)
// Service 2 (encrypt): version + algorithms([0]=rejected) + UB1(1)
// Service 3 (data integrity): version + algorithms([0]=rejected)
// 构建 ANO body 到临时 buffer 计算精确 length
var ab oracleSession
ab.clrChunkSize = s.clrChunkSize
// Service 4 (supervisor): cid + service array
ab.writeANOServiceHeader(4, 3)
ab.writeANOVersion()
ab.writeANOBytes([]byte{0, 0, 16, 28, 102, 236, 40, 234})
ab.writeANOUB2Array([]int{4, 1, 2, 3})
// Service 1 (auth): UB2(0xE0E1) + status(0xFCFF)
ab.writeANOServiceHeader(1, 3)
ab.writeANOVersion()
ab.writeANOPacketHeader(2, 3)
ab.putInt(0xE0E1, 2, true, false)
ab.writeANOStatus(0xfcff)
// Service 2 (encrypt): supported algos + driver
ab.writeANOServiceHeader(2, 3)
ab.writeANOVersion()
ab.writeANOBytes([]byte{0, 1, 8, 10, 6, 2, 15, 16, 17})
ab.writeANOUB1(1)
// Service 3 (data integrity): supported algos
ab.writeANOServiceHeader(3, 2)
ab.writeANOVersion()
ab.writeANOBytes([]byte{0, 1, 3, 4, 5, 6})
body := ab.out.Bytes()
s.reset() s.reset()
s.writeANOHeader(101, 4, 0) s.writeANOHeader(13+len(body), 4, 0)
s.writeANOServiceHeader(4, 3) s.putBytes(body...)
s.writeANOVersion()
s.writeANOBytes([]byte{0, 0, 16, 28, 102, 236, 40, 234})
s.writeANOUB2Array([]int{4, 1, 2, 3})
s.writeANOServiceHeader(1, 3)
s.writeANOVersion()
s.writeANOStatus(0xfcff)
s.writeANOServiceHeader(2, 3)
s.writeANOVersion()
s.writeANOBytes([]byte{0})
s.writeANOUB1(1)
s.writeANOServiceHeader(3, 2)
s.writeANOVersion()
s.writeANOBytes([]byte{0})
if err := s.writeData(); err != nil { if err := s.writeData(); err != nil {
return err return err
} }
+62 -1
View File
@@ -147,7 +147,7 @@ services:
interval: 5s interval: 5s
retries: 10 retries: 10
openldap: openldap:
image: osixia/openldap:1.5.0 image: osixia/openldap:1.5.0
environment: environment:
LDAP_ORGANISATION: "Test" LDAP_ORGANISATION: "Test"
@@ -201,3 +201,64 @@ openldap:
test: ["CMD-SHELL", "wget -qO- http://localhost:8025/api/v2/messages || exit 1"] test: ["CMD-SHELL", "wget -qO- http://localhost:8025/api/v2/messages || exit 1"]
interval: 5s interval: 5s
retries: 10 retries: 10
oracle:
image: gvenzl/oracle-xe:18-slim
environment:
ORACLE_PASSWORD: oracle123
ports:
- "11521:1521"
healthcheck:
test: ["CMD-SHELL", "healthcheck.sh"]
interval: 10s
retries: 30
activemq:
image: rmohr/activemq:5.15.9
ports:
- "11613:61613"
- "18161:8161"
healthcheck:
test: ["CMD-SHELL", "curl -sf http://admin:admin@localhost:8161/api/jolokia || exit 1"]
interval: 5s
retries: 15
zookeeper:
image: zookeeper:3.9
ports:
- "12181:2181"
healthcheck:
test: ["CMD-SHELL", "echo ruok | nc localhost 2181 | grep -q imok"]
interval: 5s
retries: 10
rsync:
image: vimagick/rsyncd
ports:
- "10873:873"
volumes:
- ./rsyncd.conf:/etc/rsyncd.conf:ro
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 873 || exit 1"]
interval: 5s
retries: 10
vnc:
image: consol/debian-xfce-vnc:latest
environment:
VNC_PW: vnc123
ports:
- "15901:5901"
healthcheck:
test: ["CMD-SHELL", "nc -z localhost 5901 || exit 1"]
interval: 5s
retries: 15
snmp:
image: polinux/snmpd
ports:
- "10161:161/udp"
healthcheck:
test: ["CMD-SHELL", "snmpget -v2c -c public localhost sysDescr.0 || exit 0"]
interval: 5s
retries: 10
+125
View File
@@ -427,6 +427,131 @@ func TestSMTPServiceDetect(t *testing.T) {
t.Logf("smtp: type=%s banner=%s", result.Type, result.Banner) t.Logf("smtp: type=%s banner=%s", result.Type, result.Banner)
} }
// ── Oracle ─────────────────────────────────────────────────────
func TestOracleServiceDetect(t *testing.T) {
session := testSession()
session.Config.DisableBrute = true
info := hostInfo(testHost, 11521)
plugin := services.NewOraclePlugin()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result := plugin.Scan(ctx, info, session)
if result == nil {
t.Fatal("result is nil")
}
t.Logf("oracle detect: success=%v type=%s banner=%s error=%v", result.Success, result.Type, result.Banner, result.Error)
}
func TestOracleBrute(t *testing.T) {
t.Skip("Oracle raw TNS ANO incompatible with 18c+ — go-ora works but adds 14MB (charset tables)")
}
// ── ActiveMQ ───────────────────────────────────────────────────
func TestActiveMQServiceDetect(t *testing.T) {
session := testSession()
session.Config.DisableBrute = true
info := hostInfo(testHost, 11613)
plugin := services.NewActiveMQPlugin()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result := plugin.Scan(ctx, info, session)
if result == nil {
t.Fatal("result is nil")
}
if !result.Success {
t.Fatalf("expected activemq service detect to succeed, got error: %v", result.Error)
}
t.Logf("activemq: type=%s banner=%s", result.Type, result.Banner)
}
// ── Zookeeper ──────────────────────────────────────────────────
func TestZookeeperServiceDetect(t *testing.T) {
session := testSession()
info := hostInfo(testHost, 12181)
plugin := services.NewZooKeeperPlugin()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
result := plugin.Scan(ctx, info, session)
if result == nil {
t.Fatal("result is nil")
}
if !result.Success {
t.Fatalf("expected zookeeper to succeed, got error: %v", result.Error)
}
t.Logf("zookeeper: type=%s banner=%s", result.Type, result.Banner)
}
// ── Rsync ──────────────────────────────────────────────────────
func TestRsyncServiceDetect(t *testing.T) {
session := testSession()
session.Config.DisableBrute = true
info := hostInfo(testHost, 10873)
plugin := services.NewRsyncPlugin()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
result := plugin.Scan(ctx, info, session)
if result == nil {
t.Fatal("result is nil")
}
if !result.Success {
t.Fatalf("expected rsync service detect to succeed, got error: %v", result.Error)
}
t.Logf("rsync: type=%s banner=%s", result.Type, result.Banner)
}
// ── VNC ────────────────────────────────────────────────────────
func TestVNCBrute(t *testing.T) {
session := testSession()
session.Config.Credentials.Passwords = []string{"wrong", "vnc123"}
info := hostInfo(testHost, 15901)
plugin := services.NewVNCPlugin()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
result := plugin.Scan(ctx, info, session)
if result == nil {
t.Fatal("result is nil")
}
if !result.Success {
t.Fatalf("expected vnc brute to succeed, got error: %v", result.Error)
}
t.Logf("vnc brute: pass=%s", result.Password)
}
// ── SNMP ───────────────────────────────────────────────────────
func TestSNMPServiceDetect(t *testing.T) {
session := testSession()
info := hostInfo(testHost, 10161)
plugin := services.NewSNMPPlugin()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
result := plugin.Scan(ctx, info, session)
if result == nil {
t.Fatal("result is nil")
}
if !result.Success {
t.Fatalf("expected snmp to succeed, got error: %v", result.Error)
}
t.Logf("snmp: type=%s banner=%s", result.Type, result.Banner)
}
// ── 连接失败场景 ────────────────────────────────────────────── // ── 连接失败场景 ──────────────────────────────────────────────
func TestRedisConnectionRefused(t *testing.T) { func TestRedisConnectionRefused(t *testing.T) {
+11
View File
@@ -0,0 +1,11 @@
uid = nobody
gid = nogroup
use chroot = no
max connections = 4
log file = /dev/stdout
[public]
path = /data
comment = Public
read only = yes
list = yes