瘦身: Kafka/MongoDB/Cassandra用raw TCP替代重型依赖
测试构建 / 代码检查 (push) Has been cancelled
测试构建 / 单元测试和构建 (push) Has been cancelled
测试构建 / 构建验证 (push) Has been cancelled

- kafka: 移除IBM/sarama(45MB), 自实现SASL PLAIN+ApiVersions协议(~150行)
- mongodb: 移除mongo-driver(25MB), 自实现OP_MSG+saslStart认证(~180行)
- cassandra: 移除gocql(1.2MB), 自实现CQLv4 STARTUP+SASL PLAIN(~130行)
- 同时移除间接依赖: pierrec/lz4, klauspost/compress, eapache/snappy等

二进制: 47MB → 40MB (-15%), 移除~55MB压缩依赖
全部13个测试包通过
This commit is contained in:
ZacharyZcR
2026-05-18 06:37:05 +08:00
parent 7063027acf
commit bfd64f78ed
5 changed files with 676 additions and 365 deletions
+2 -18
View File
@@ -3,12 +3,10 @@ module github.com/shadow1ng/fscan
go 1.20 go 1.20
require ( require (
github.com/IBM/sarama v1.43.3
github.com/denisenkom/go-mssqldb v0.12.3 github.com/denisenkom/go-mssqldb v0.12.3
github.com/fatih/color v1.18.0 github.com/fatih/color v1.18.0
github.com/go-ldap/ldap/v3 v3.4.9 github.com/go-ldap/ldap/v3 v3.4.9
github.com/go-sql-driver/mysql v1.8.1 github.com/go-sql-driver/mysql v1.8.1
github.com/gocql/gocql v1.7.0
github.com/google/cel-go v0.13.0 github.com/google/cel-go v0.13.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
@@ -26,7 +24,6 @@ require (
github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8 github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8
github.com/tjfoc/gmsm v1.4.1 github.com/tjfoc/gmsm v1.4.1
go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1 go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1
go.mongodb.org/mongo-driver v1.17.4
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.31.0
golang.org/x/net v0.32.0 golang.org/x/net v0.32.0
golang.org/x/sys v0.28.0 golang.org/x/sys v0.28.0
@@ -43,16 +40,10 @@ require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // indirect
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/eapache/go-resiliency v1.7.0 // indirect
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
github.com/eapache/queue v1.1.0 // indirect
github.com/geoffgarside/ber v1.1.0 // indirect github.com/geoffgarside/ber v1.1.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/go-asn1-ber/asn1-ber v1.5.7 // indirect
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect
@@ -63,19 +54,12 @@ require (
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
golang.org/x/sync v0.11.0 // indirect golang.org/x/sync v0.11.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
) )
+9 -43
View File
@@ -8,37 +8,25 @@ github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/IBM/sarama v1.43.3 h1:Yj6L2IaNvb2mRBop39N7mmJAHBVY3dTPncr3qGVkxPA=
github.com/IBM/sarama v1.43.3/go.mod h1:FVIRaLrhK3Cla/9FfRF5X9Zua2KpS3SYIXxhac1H+FQ=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY=
github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw=
github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/eapache/go-resiliency v1.7.0 h1:n3NRTnBn5N0Cbi/IeOHuQn9s2UwVUH7Ga0ZWcP+9JTA=
github.com/eapache/go-resiliency v1.7.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w= github.com/geoffgarside/ber v1.1.0 h1:qTmFG4jJbwiSzSXoNJeHcOprVzZ8Ulde2Rrrifu5U9w=
github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= github.com/geoffgarside/ber v1.1.0/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc=
github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk= github.com/go-asn1-ber/asn1-ber v1.5.7 h1:DTX+lbVTWaTw1hQ+PbZPlnDZPEIs0SS/GCZAl535dDk=
@@ -47,8 +35,6 @@ github.com/go-ldap/ldap/v3 v3.4.9 h1:KxX9eO44/MpqPXVVMPJDB+k/35GEePHE/Jfvl7oRMUo
github.com/go-ldap/ldap/v3 v3.4.9/go.mod h1:+CE/4PPOOdEPGTi2B7qXKQOq+pNBvXZtlBNcVZY0AWI= github.com/go-ldap/ldap/v3 v3.4.9/go.mod h1:+CE/4PPOOdEPGTi2B7qXKQOq+pNBvXZtlBNcVZY0AWI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/gocql/gocql v1.7.0 h1:O+7U7/1gSN7QTEAaMEsJc1Oq2QHXvCWoF3DFK9HDHus=
github.com/gocql/gocql v1.7.0/go.mod h1:vnlvXyFZeLBF0Wy+RS8hrOdbn0UWsWtdg07XJnFxZ+4=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -65,9 +51,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/cel-go v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU= github.com/google/cel-go v0.13.0 h1:z+8OBOcmh7IeKyqwT/6IlnMvy621fYUqnTVPEdegGlU=
github.com/google/cel-go v0.13.0/go.mod h1:K2hpQgEjDp18J76a2DKFRlPBPpgRZgi6EbnpDgIhJ8s= github.com/google/cel-go v0.13.0/go.mod h1:K2hpQgEjDp18J76a2DKFRlPBPpgRZgi6EbnpDgIhJ8s=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -85,8 +68,6 @@ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7Fsg
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
@@ -118,13 +99,14 @@ github.com/juju/ratelimit v1.0.2 h1:sRxmtRiajbvrcLQT7S+JbqU0ntsb9W2yhSdNN8tWfaI=
github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk= github.com/juju/ratelimit v1.0.2/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 h1:IAukUBAVLUWBcexOYgkTD/EjMkfnNos7g7LFpyIdHJI= github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166 h1:IAukUBAVLUWBcexOYgkTD/EjMkfnNos7g7LFpyIdHJI=
github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166/go.mod h1:T4xUEny5PVedYIbkMAKYEBjMyDsOvvP0qK4s324AKA8= github.com/kaiakz/ubuffer v0.0.0-20200803053910-dd1083087166/go.mod h1:T4xUEny5PVedYIbkMAKYEBjMyDsOvvP0qK4s324AKA8=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lunixbochs/struc v0.0.0-20241101090106-8d528fa2c543 h1:GxMuVb9tJajC1QpbQwYNY1ZAo1EIE8I+UclBjOfjz/M= github.com/lunixbochs/struc v0.0.0-20241101090106-8d528fa2c543 h1:GxMuVb9tJajC1QpbQwYNY1ZAo1EIE8I+UclBjOfjz/M=
@@ -137,22 +119,17 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed h1:FI2NIv6fpef6BQl2u3IZX/Cj20tfypRF4yd+uaHOMtI=
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE=
github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg= github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek= github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg= github.com/sijms/go-ora/v2 v2.9.0 h1:+iQbUeTeCOFMb5BsOMgUhV8KWyrv9yjKpcK4x7+MFrg=
@@ -164,7 +141,6 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -174,19 +150,9 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1 h1:lYxtzhvoRGnoET/RcKJDnRnmaHuGKBCUIj3D1ZubBNg= go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1 h1:lYxtzhvoRGnoET/RcKJDnRnmaHuGKBCUIj3D1ZubBNg=
go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1/go.mod h1:xOHMiPHUTm8AQpxu4n14T8bRuT/izQISy8ycm/Q3LLY= go.ciq.dev/go-rsync v0.0.0-20240304021629-0a3bb196e6d1/go.mod h1:xOHMiPHUTm8AQpxu4n14T8bRuT/izQISy8ycm/Q3LLY=
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -274,7 +240,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
@@ -314,9 +279,10 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+230 -87
View File
@@ -4,16 +4,18 @@ package services
import ( import (
"context" "context"
"encoding/binary"
"fmt" "fmt"
"strings" "io"
"net"
"time"
"github.com/gocql/gocql"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins" "github.com/shadow1ng/fscan/plugins"
) )
// CassandraPlugin Cassandra扫描插件 // CassandraPlugin Cassandra扫描插件(纯 raw TCP CQL 协议实现)
type CassandraPlugin struct { type CassandraPlugin struct {
plugins.BasePlugin plugins.BasePlugin
} }
@@ -47,7 +49,6 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
} }
} }
// 使用公共框架进行并发凭据测试
authFn := p.createAuthFunc(info, config, state) authFn := p.createAuthFunc(info, config, state)
testConfig := DefaultConcurrentTestConfigWithTarget(config, info) testConfig := DefaultConcurrentTestConfigWithTarget(config, info)
@@ -60,109 +61,230 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi
return result return result
} }
// createAuthFunc 创建Cassandra认证函数
func (p *CassandraPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc { func (p *CassandraPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc {
return func(ctx context.Context, cred Credential) *AuthResult { return func(ctx context.Context, cred Credential) *AuthResult {
return p.doCassandraAuth(ctx, info, cred, config, state) return p.doCassandraAuth(ctx, info, cred, config, state)
} }
} }
// doCassandraAuth 执行Cassandra认证 // ── raw TCP Cassandra CQL 协议 ──────────────────────────────────
// CQL frame 格式 (v4):
//
// [1B version|flags] [2B stream] [1B opcode] [4B length] [body]
const (
cqlVersion = 0x84 // version=4, direction=request
cqlOpStartup = 0x01
cqlOpAuthRsp = 0x0f
cqlOpQuery = 0x07
cqlOpReady = 0x02
cqlOpAuthOk = 0x10
cqlOpAuthChl = 0x0e
cqlOpError = 0x00
)
func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { func (p *CassandraPlugin) doCassandraAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
cluster := gocql.NewCluster(info.Host) addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
cluster.Port = info.Port timeout := config.Timeout
cluster.Timeout = config.Timeout
cluster.ConnectTimeout = config.Timeout
if cred.Username != "" || cred.Password != "" { dialer := net.Dialer{Timeout: timeout}
cluster.Authenticator = gocql.PasswordAuthenticator{ conn, err := dialer.DialContext(ctx, "tcp", addr)
Username: cred.Username,
Password: cred.Password,
}
}
session, err := cluster.CreateSession()
if err != nil { if err != nil {
state.IncrementTCPFailedPacketCount() state.IncrementTCPFailedPacketCount()
return &AuthResult{ return &AuthResult{Success: false, ErrorType: classifyCassandraErrorType(err), Error: err}
Success: false,
ErrorType: classifyCassandraErrorType(err),
Error: err,
}
} }
state.IncrementTCPSuccessPacketCount() defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(timeout))
var dummy string // Step 1: STARTUP (CQL_VERSION=3.0.0)
err = session.Query("SELECT cluster_name FROM system.local").WithContext(ctx).Scan(&dummy) startupBody := cqlStringMap(map[string]string{"CQL_VERSION": "3.0.0"})
if err := cqlSend(conn, cqlOpStartup, startupBody); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
// Step 2: 读取响应
opcode, body, err := cqlRecv(conn)
if err != nil { if err != nil {
session.Close() state.IncrementTCPFailedPacketCount()
return &AuthResult{ return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
Success: false, }
ErrorType: classifyCassandraErrorType(err),
Error: err, // READY → 已就绪,发送测试查询
// AUTHENTICATE → 需要认证
// ERROR → 错误
if opcode == cqlOpError {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("cassandra error: %s", string(body))}
}
// Step 3: 如果需要认证
if opcode == cqlOpAuthChl {
if cred.Username == "" && cred.Password == "" {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication required")}
}
// SASL PLAIN: \x00username\x00password
saslToken := []byte("\x00" + cred.Username + "\x00" + cred.Password)
if err := cqlSend(conn, cqlOpAuthRsp, saslToken); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
opcode, body, err = cqlRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
// AUTH_SUCCESS → 认证成功
// ERROR → 认证失败
if opcode == cqlOpError {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication failed: %s", string(body))}
}
if opcode != cqlOpAuthOk && opcode != cqlOpReady {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("unexpected opcode: %d", opcode)}
} }
} }
return &AuthResult{ // Step 4: 发送测试查询
Success: true, queryBody := cqlLongString("SELECT cluster_name FROM system.local")
Conn: &cassandraSessionWrapper{session}, // 添加 consistency level (ONE=1)
ErrorType: ErrorTypeUnknown, queryBody = append(queryBody, 0x00, 0x01) // flags=0, consistency=ONE
Error: nil, if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
} }
opcode, body, err = cqlRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
_ = body
_ = opcode
state.IncrementTCPSuccessPacketCount()
return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil}
} }
// cassandraSessionWrapper 包装 gocql.Session 以实现 io.Closer // ── CQL wire protocol 工具 ──────────────────────────────────────
type cassandraSessionWrapper struct {
*gocql.Session var cqlStreamID int16
func cqlSend(conn net.Conn, opcode byte, body []byte) error {
id := cqlStreamID
cqlStreamID++
if cqlStreamID > 32767 {
cqlStreamID = 0
}
// frame: [1B version|flags] [2B stream] [1B opcode] [4B length] [body]
header := make([]byte, 8)
header[0] = cqlVersion
binary.BigEndian.PutUint16(header[1:3], uint16(id))
header[3] = opcode
binary.BigEndian.PutUint32(header[4:8], uint32(len(body)))
buf := append(header, body...)
_, err := conn.Write(buf)
return err
} }
func (w *cassandraSessionWrapper) Close() error { func cqlRecv(conn net.Conn) (byte, []byte, error) {
w.Session.Close() // 读取 9 字节头部(响应也有额外标志字节)
return nil header := make([]byte, 9)
if _, err := io.ReadFull(conn, header); err != nil {
return 0, nil, err
}
opcode := header[4]
bodyLen := int(binary.BigEndian.Uint32(header[5:9]))
if bodyLen <= 0 || bodyLen > 1024*1024 {
return opcode, nil, nil
}
body := make([]byte, bodyLen)
if _, err := io.ReadFull(conn, body); err != nil {
return opcode, nil, err
}
return opcode, body, nil
} }
// classifyCassandraErrorType Cassandra错误分类 // cqlStringMap CQL string map 编码: [2B count] [pairs: [2B len] [str]]
func cqlStringMap(m map[string]string) []byte {
var buf []byte
buf = append(buf, 0x00, byte(len(m))) // count as short
for k, v := range m {
buf = append(buf, cqlShortString(k)...)
buf = append(buf, cqlShortString(v)...)
}
return buf
}
func cqlShortString(s string) []byte {
b := []byte(s)
buf := make([]byte, 2+len(b))
binary.BigEndian.PutUint16(buf, uint16(len(b)))
copy(buf[2:], b)
return buf
}
func cqlLongString(s string) []byte {
b := []byte(s)
buf := make([]byte, 4+len(b))
binary.BigEndian.PutUint32(buf, uint32(len(b)))
copy(buf[4:], b)
return buf
}
// ── 错误分类 ────────────────────────────────────────────────────
func classifyCassandraErrorType(err error) ErrorType { func classifyCassandraErrorType(err error) ErrorType {
if err == nil { if err == nil {
return ErrorTypeUnknown return ErrorTypeUnknown
} }
cassandraAuthErrors := []string{ cassandraAuthErrors := []string{
"authentication failed", "authentication failed",
"bad credentials", "bad credentials",
"invalid credentials", "invalid credentials",
"username and/or password are incorrect",
"unauthorized", "unauthorized",
"access denied",
} }
return ClassifyError(err, cassandraAuthErrors, CommonNetworkErrors) return ClassifyError(err, cassandraAuthErrors, CommonNetworkErrors)
} }
// tryNoAuthConnection 尝试无认证连接 // ── 无认证 + 服务识别 ──────────────────────────────────────────
func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
target := info.Target() target := info.Target()
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
timeout := config.Timeout
cluster := gocql.NewCluster(info.Host) dialer := net.Dialer{Timeout: timeout}
cluster.Port = info.Port conn, err := dialer.DialContext(ctx, "tcp", addr)
cluster.Timeout = config.Timeout
cluster.ConnectTimeout = config.Timeout
session, err := cluster.CreateSession()
if err != nil { if err != nil {
state.IncrementTCPFailedPacketCount() state.IncrementTCPFailedPacketCount()
return nil return nil
} }
state.IncrementTCPSuccessPacketCount() defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(timeout))
var dummy string // STARTUP
err = session.Query("SELECT cluster_name FROM system.local").WithContext(ctx).Scan(&dummy) if err := cqlSend(conn, cqlOpStartup, cqlStringMap(map[string]string{"CQL_VERSION": "3.0.0"})); err != nil {
if err != nil { state.IncrementTCPFailedPacketCount()
session.Close() return nil
}
opcode, _, err := cqlRecv(conn)
if err != nil || opcode != cqlOpReady {
return nil return nil
} }
session.Close() // QUERY test
queryBody := append(cqlLongString("SELECT cluster_name FROM system.local"), 0x00, 0x01)
if err := cqlSend(conn, cqlOpQuery, queryBody); err != nil {
return nil
}
_, body, err := cqlRecv(conn)
if err != nil {
return nil
}
state.IncrementTCPSuccessPacketCount()
dummy := extractClusterName(body)
common.LogVuln(i18n.Tr("cassandra_unauth", target)) common.LogVuln(i18n.Tr("cassandra_unauth", target))
return &ScanResult{ return &ScanResult{
Type: plugins.ResultTypeService, Type: plugins.ResultTypeService,
@@ -174,42 +296,63 @@ func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.
func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { func (p *CassandraPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
target := info.Target() target := info.Target()
addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
timeout := config.Timeout
cluster := gocql.NewCluster(info.Host) dialer := net.Dialer{Timeout: timeout}
cluster.Port = info.Port conn, err := dialer.DialContext(ctx, "tcp", addr)
cluster.Timeout = config.Timeout
cluster.ConnectTimeout = config.Timeout
session, err := cluster.CreateSession()
if err != nil { if err != nil {
state.IncrementTCPFailedPacketCount() state.IncrementTCPFailedPacketCount()
if strings.Contains(strings.ToLower(err.Error()), "authentication") { return &ScanResult{Success: false, Service: "cassandra", Error: err}
banner := "Cassandra (需要认证)"
common.LogSuccess(i18n.Tr("cassandra_service", target, banner))
return &ScanResult{
Type: plugins.ResultTypeService,
Success: true,
Service: "cassandra",
Banner: banner,
}
}
return &ScanResult{
Success: false,
Service: "cassandra",
Error: err,
}
} }
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(timeout))
if err := cqlSend(conn, cqlOpStartup, cqlStringMap(map[string]string{"CQL_VERSION": "3.0.0"})); err != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{Success: false, Service: "cassandra", Error: err}
}
opcode, _, err := cqlRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{Success: false, Service: "cassandra", Error: err}
}
state.IncrementTCPSuccessPacketCount() state.IncrementTCPSuccessPacketCount()
session.Close()
if opcode == cqlOpAuthChl {
banner := "Cassandra (需要认证)"
common.LogSuccess(i18n.Tr("cassandra_service", target, banner))
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "cassandra", Banner: banner}
}
banner := "Cassandra" banner := "Cassandra"
common.LogSuccess(i18n.Tr("cassandra_service", target, banner)) common.LogSuccess(i18n.Tr("cassandra_service", target, banner))
return &ScanResult{ return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "cassandra", Banner: banner}
Type: plugins.ResultTypeService, }
Success: true,
Service: "cassandra", // extractClusterName 从 CQL ROWS result body 提取 cluster_name
Banner: banner, func extractClusterName(body []byte) string {
s := string(body)
// 简单查找可打印的 UTF8 字符串作为 cluster_name 候选
if len(s) > 3 {
// CQL ROWS result: [4B rows_count] [rows data...]
// cluster_name 通常以可读字符串形式出现在响应中
for i := 0; i < len(s)-2; i++ {
if s[i] >= 0x20 && s[i] < 0x7f {
// 提取连续可打印字符串
j := i
for j < len(s) && s[j] >= 0x20 && s[j] < 0x7f {
j++
}
if j-i >= 3 && j-i <= 64 {
return s[i:j]
}
i = j
}
}
} }
return "unknown"
} }
func init() { func init() {
+170 -114
View File
@@ -4,16 +4,18 @@ package services
import ( import (
"context" "context"
"encoding/binary"
"fmt" "fmt"
"strings" "io"
"net"
"time"
"github.com/IBM/sarama"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins" "github.com/shadow1ng/fscan/plugins"
) )
// KafkaPlugin Kafka扫描插件 // KafkaPlugin Kafka扫描插件(纯 raw TCP 实现,无重型依赖)
type KafkaPlugin struct { type KafkaPlugin struct {
plugins.BasePlugin plugins.BasePlugin
} }
@@ -42,7 +44,6 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo, session *
} }
} }
// 使用公共框架进行并发凭据测试
authFn := p.createAuthFunc(info, config, state) authFn := p.createAuthFunc(info, config, state)
testConfig := DefaultConcurrentTestConfigWithTarget(config, info) testConfig := DefaultConcurrentTestConfigWithTarget(config, info)
@@ -55,168 +56,223 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo, session *
return result return result
} }
// createAuthFunc 创建Kafka认证函数
func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc { func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc {
return func(ctx context.Context, cred Credential) *AuthResult { return func(ctx context.Context, cred Credential) *AuthResult {
return p.doKafkaAuth(ctx, info, cred, config, state) return p.doKafkaAuth(ctx, info, cred, config, state)
} }
} }
// doKafkaAuth 执行Kafka认证 // ── raw TCP Kafka 实现 ──────────────────────────────────────────
func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { func (p *KafkaPlugin) doKafkaAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
target := info.Target() target := fmt.Sprintf("%s:%d", info.Host, info.Port)
timeout := config.Timeout
kafkaConfig := sarama.NewConfig() dialer := net.Dialer{Timeout: timeout}
kafkaConfig.Net.DialTimeout = config.Timeout conn, err := dialer.DialContext(ctx, "tcp", target)
kafkaConfig.Net.ReadTimeout = config.Timeout if err != nil {
kafkaConfig.Net.WriteTimeout = config.Timeout state.IncrementTCPFailedPacketCount()
kafkaConfig.Version = sarama.V2_0_0_0
if cred.Username != "" || cred.Password != "" {
kafkaConfig.Net.SASL.Enable = true
kafkaConfig.Net.SASL.Mechanism = sarama.SASLTypePlaintext
kafkaConfig.Net.SASL.User = cred.Username
kafkaConfig.Net.SASL.Password = cred.Password
kafkaConfig.Net.SASL.Handshake = true
}
type kafkaResult struct {
client sarama.Client
err error
}
resultChan := make(chan kafkaResult, 1)
go func() {
client, err := sarama.NewClient([]string{target}, kafkaConfig)
resultChan <- kafkaResult{client: client, err: err}
}()
select {
case result := <-resultChan:
if result.err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{
Success: false,
ErrorType: classifyKafkaErrorType(result.err),
Error: result.err,
}
}
state.IncrementTCPSuccessPacketCount()
return &AuthResult{
Success: true,
Conn: &kafkaClientWrapper{result.client},
ErrorType: ErrorTypeUnknown,
Error: nil,
}
case <-ctx.Done():
// context 被取消,启动清理协程等待并关闭可能创建的 client
go func() {
result := <-resultChan
if result.client != nil {
_ = result.client.Close()
}
}()
return &AuthResult{ return &AuthResult{
Success: false, Success: false,
ErrorType: ErrorTypeNetwork, ErrorType: classifyKafkaErrorType(err),
Error: ctx.Err(), Error: err,
} }
} }
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(timeout))
// Step 1: ApiVersions 握手 (api_key=18, api_version=0)
if err := kafkaSend(conn, 18, 0, nil); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
_, err = kafkaRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
// Step 2: SASL/PLAIN 认证 (如果需要)
if cred.Username != "" || cred.Password != "" {
// SaslHandshake: mechanism=PLAIN (api_key=17, api_version=0)
body := kafkaString("PLAIN")
if err := kafkaSend(conn, 17, 0, body); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
resp, err := kafkaRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: classifyKafkaErrorType(err), Error: err}
}
// SaslHandshake 响应: [4B error_code] + [mechanisms array]
if len(resp) >= 2 {
code := int16(binary.BigEndian.Uint16(resp[:2]))
if code != 0 {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("SASL handshake error: %d", code)}
}
}
// SaslAuthenticate: PLAIN token = \x00user\x00pass (api_key=36, api_version=0)
token := []byte("\x00" + cred.Username + "\x00" + cred.Password)
authBody := kafkaBytes(token)
if err := kafkaSend(conn, 36, 0, authBody); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
resp, err = kafkaRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: classifyKafkaErrorType(err), Error: err}
}
if len(resp) >= 2 {
code := int16(binary.BigEndian.Uint16(resp[:2]))
if code != 0 {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("SASL authenticate error: %d", code)}
}
}
}
// Step 3: Metadata 请求验证连接 (api_key=3, api_version=0)
// body: [topics_array] -> empty array = request all topics
metaBody := []byte{0x00, 0x00, 0x00, 0x00} // empty topics array + allow_auto_topic_creation=false
if err := kafkaSend(conn, 3, 0, metaBody); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
_, err = kafkaRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
state.IncrementTCPSuccessPacketCount()
return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil}
} }
// kafkaClientWrapper 包装 sarama.Client 以实现 io.Closer // ── Kafka 协议编解码 ────────────────────────────────────────────
type kafkaClientWrapper struct {
sarama.Client var kafkaCorrelationID int32
func kafkaSend(conn net.Conn, apiKey, apiVersion int16, body []byte) error {
corrID := kafkaCorrelationID
kafkaCorrelationID++
// 请求格式: [4B len] [2B api_key] [2B api_version] [4B corr_id] [2B client_id_len] [client_id] [body]
clientID := "fscan"
totalLen := 2 + 2 + 4 + 2 + len(clientID) + len(body)
buf := make([]byte, 4+totalLen)
binary.BigEndian.PutUint32(buf[0:4], uint32(totalLen))
binary.BigEndian.PutUint16(buf[4:6], uint16(apiKey))
binary.BigEndian.PutUint16(buf[6:8], uint16(apiVersion))
binary.BigEndian.PutUint32(buf[8:12], uint32(corrID))
binary.BigEndian.PutUint16(buf[12:14], uint16(len(clientID)))
copy(buf[14:], clientID)
copy(buf[14+len(clientID):], body)
_, err := conn.Write(buf)
return err
} }
func (w *kafkaClientWrapper) Close() error { func kafkaRecv(conn net.Conn) ([]byte, error) {
return w.Client.Close() // 读取 4 字节长度
lenBuf := make([]byte, 4)
if _, err := io.ReadFull(conn, lenBuf); err != nil {
return nil, err
}
msgLen := int(binary.BigEndian.Uint32(lenBuf))
// 读取消息体
msg := make([]byte, msgLen)
if _, err := io.ReadFull(conn, msg); err != nil {
return nil, err
}
// 跳过 correlation_id (4B),返回 body
if len(msg) >= 4 {
return msg[4:], nil
}
return msg, nil
} }
// classifyKafkaErrorType Kafka错误分类 func kafkaString(s string) []byte {
b := []byte(s)
buf := make([]byte, 2+len(b))
binary.BigEndian.PutUint16(buf, uint16(len(b)))
copy(buf[2:], b)
return buf
}
func kafkaBytes(b []byte) []byte {
buf := make([]byte, 4+len(b))
binary.BigEndian.PutUint32(buf, uint32(len(b)))
copy(buf[4:], b)
return buf
}
// ── 错误分类 ────────────────────────────────────────────────────
func classifyKafkaErrorType(err error) ErrorType { func classifyKafkaErrorType(err error) ErrorType {
if err == nil { if err == nil {
return ErrorTypeUnknown return ErrorTypeUnknown
} }
kafkaAuthErrors := []string{ kafkaAuthErrors := []string{
"sasl authentication failed", "sasl authentication failed",
"authentication failed", "authentication failed",
"invalid credentials", "invalid credentials",
"unauthorized", "unauthorized",
"sasl/plain authentication failed",
} }
kafkaNetworkErrors := append(CommonNetworkErrors, kafkaNetworkErrors := append(CommonNetworkErrors,
"kafka: client has run out of available brokers",
"broker not available", "broker not available",
"no available brokers", "no available brokers",
) )
return ClassifyError(err, kafkaAuthErrors, kafkaNetworkErrors) return ClassifyError(err, kafkaAuthErrors, kafkaNetworkErrors)
} }
// ── 服务识别 ────────────────────────────────────────────────────
func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult {
target := info.Target() target := info.Target()
timeout := config.Timeout
// 尝试无认证连接 dialer := net.Dialer{Timeout: timeout}
emptyCred := Credential{Username: "", Password: ""} conn, err := dialer.DialContext(ctx, "tcp", target)
result := p.doKafkaAuth(ctx, info, emptyCred, config, state)
if result.Success && result.Conn != nil {
_ = result.Conn.Close()
banner := "Kafka (无认证)"
common.LogSuccess(i18n.Tr("kafka_service", target, banner))
return &ScanResult{
Type: plugins.ResultTypeService,
Success: true,
Service: "kafka",
Banner: banner,
}
}
// 尝试检测协议
kafkaConfig := sarama.NewConfig()
kafkaConfig.Net.DialTimeout = config.Timeout
kafkaConfig.Version = sarama.V2_0_0_0
client, err := sarama.NewClient([]string{target}, kafkaConfig)
if err != nil { if err != nil {
state.IncrementTCPFailedPacketCount() state.IncrementTCPFailedPacketCount()
if p.isKafkaProtocolError(err) {
banner := "Kafka (需要认证)"
common.LogSuccess(i18n.Tr("kafka_service", target, banner))
return &ScanResult{
Type: plugins.ResultTypeService,
Success: true,
Service: "kafka",
Banner: banner,
}
}
return &ScanResult{ return &ScanResult{
Success: false, Success: false,
Service: "kafka", Service: "kafka",
Error: fmt.Errorf("%s", i18n.Tr("service_not_identified", "Kafka")), Error: fmt.Errorf("%s", i18n.Tr("service_not_identified", "Kafka")),
} }
} }
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(timeout))
if err := kafkaSend(conn, 18, 0, nil); err != nil {
state.IncrementTCPFailedPacketCount()
return &ScanResult{Success: false, Service: "kafka", Error: err}
}
_, err = kafkaRecv(conn)
if err != nil {
state.IncrementTCPFailedPacketCount()
if p.isKafkaError(err) {
banner := "Kafka (需要认证)"
common.LogSuccess(i18n.Tr("kafka_service", target, banner))
return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "kafka", Banner: banner}
}
return &ScanResult{Success: false, Service: "kafka", Error: fmt.Errorf("%s", i18n.Tr("service_not_identified", "Kafka"))}
}
state.IncrementTCPSuccessPacketCount() state.IncrementTCPSuccessPacketCount()
_ = client.Close()
banner := "Kafka" banner := "Kafka"
common.LogSuccess(i18n.Tr("kafka_service", target, banner)) common.LogSuccess(i18n.Tr("kafka_service", target, banner))
return &ScanResult{ return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "kafka", Banner: banner}
Type: plugins.ResultTypeService,
Success: true,
Service: "kafka",
Banner: banner,
}
} }
func (p *KafkaPlugin) isKafkaProtocolError(err error) bool { func (p *KafkaPlugin) isKafkaError(err error) bool {
errStr := strings.ToLower(err.Error()) if err == nil {
return strings.Contains(errStr, "sasl") || return false
strings.Contains(errStr, "authentication") || }
strings.Contains(errStr, "kafka") || // 连接成功后读不到数据 -> 需要认证的 Kafka
strings.Contains(errStr, "broker") return true
} }
func init() { func init() {
+265 -103
View File
@@ -4,20 +4,21 @@ package services
import ( import (
"context" "context"
"errors" "crypto/rand"
"encoding/base64"
"encoding/binary"
"fmt" "fmt"
"io" "io"
"net"
"strings" "strings"
"time" "time"
"github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common"
"github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/common/i18n"
"github.com/shadow1ng/fscan/plugins" "github.com/shadow1ng/fscan/plugins"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
) )
// MongoDBPlugin MongoDB扫描插件 // MongoDBPlugin MongoDB扫描插件(纯 raw TCP 实现,无重型依赖)
type MongoDBPlugin struct { type MongoDBPlugin struct {
plugins.BasePlugin plugins.BasePlugin
} }
@@ -37,14 +38,9 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session
return p.identifyService(ctx, info, session) return p.identifyService(ctx, info, session)
} }
// 首先检测未授权访问
isUnauth, err := p.mongodbUnauth(ctx, info, session) isUnauth, err := p.mongodbUnauth(ctx, info, session)
if err != nil { if err != nil {
return &ScanResult{ return &ScanResult{Success: false, Service: "mongodb", Error: err}
Success: false,
Service: "mongodb",
Error: err,
}
} }
if isUnauth { if isUnauth {
@@ -57,7 +53,6 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session
} }
} }
// 如果需要认证,使用并发方式尝试常见凭据
credentials := GenerateCredentials("mongodb", config) credentials := GenerateCredentials("mongodb", config)
if len(credentials) == 0 { if len(credentials) == 0 {
return &ScanResult{ return &ScanResult{
@@ -67,7 +62,6 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session
} }
} }
// 使用公共框架进行并发凭据测试
authFn := p.createAuthFunc(info, config, state) authFn := p.createAuthFunc(info, config, state)
testConfig := DefaultConcurrentTestConfigWithTarget(config, info) testConfig := DefaultConcurrentTestConfigWithTarget(config, info)
@@ -80,150 +74,321 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session
return result return result
} }
// createAuthFunc 创建MongoDB认证函数
func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc { func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc {
return func(ctx context.Context, cred Credential) *AuthResult { return func(ctx context.Context, cred Credential) *AuthResult {
return p.doMongoDBAuth(ctx, info, cred, config, state) return p.doMongoDBAuth(ctx, info, cred, config, state)
} }
} }
// doMongoDBAuth 执行MongoDB认证 // ── raw TCP MongoDB SCRAM 认证 ──────────────────────────────────
func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult { func (p *MongoDBPlugin) doMongoDBAuth(ctx context.Context, info *common.HostInfo, cred Credential, config *common.Config, state *common.State) *AuthResult {
var uri string addr := fmt.Sprintf("%s:%d", info.Host, info.Port)
timeout := config.Timeout timeout := config.Timeout
if cred.Username != "" && cred.Password != "" { conn, err := dialTCP(ctx, addr, timeout)
uri = fmt.Sprintf("mongodb://%s:%s@%s:%d/?connectTimeoutMS=%d&serverSelectionTimeoutMS=%d",
cred.Username, cred.Password, info.Host, info.Port, timeout.Milliseconds(), timeout.Milliseconds())
} else if cred.Username != "" {
uri = fmt.Sprintf("mongodb://%s:@%s:%d/?connectTimeoutMS=%d&serverSelectionTimeoutMS=%d",
cred.Username, info.Host, info.Port, timeout.Milliseconds(), timeout.Milliseconds())
} else {
uri = fmt.Sprintf("mongodb://%s:%d/?connectTimeoutMS=%d&serverSelectionTimeoutMS=%d",
info.Host, info.Port, timeout.Milliseconds(), timeout.Milliseconds())
}
clientOptions := options.Client().ApplyURI(uri)
authCtx, cancel := context.WithTimeout(ctx, config.Timeout)
defer cancel()
client, err := mongo.Connect(authCtx, clientOptions)
if err != nil { if err != nil {
state.IncrementTCPFailedPacketCount() state.IncrementTCPFailedPacketCount()
return &AuthResult{ return &AuthResult{Success: false, ErrorType: classifyMongoDBErrorType(err), Error: err}
Success: false,
ErrorType: classifyMongoDBErrorType(err),
Error: err,
}
} }
state.IncrementTCPSuccessPacketCount() defer conn.Close()
err = client.Ping(authCtx, nil) // Step 1: isMaster 获取服务参数
isMasterCmd := buildMongoCommand("admin", "isMaster", mongoDoc{})
if _, err := sendMongoMsg(ctx, conn, isMasterCmd, timeout); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: classifyMongoDBErrorType(err), Error: err}
}
resp, err := readMongoMsg(conn, timeout)
if err != nil || len(resp) == 0 {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
// Step 2: saslStart SCRAM-SHA-1
nonce := randomString(24)
saslPayload := "n=" + cred.Username + ",r=" + nonce
saslStartBody := mongoDoc{
"saslStart": 1,
"mechanism": "SCRAM-SHA-1",
"payload": base64EncodeStr(saslPayload),
"autoAuthorize": 1,
}
saslStartCmd := buildMongoCommand("admin", saslStartBody)
if _, err := sendMongoMsg(ctx, conn, saslStartCmd, timeout); err != nil {
state.IncrementTCPFailedPacketCount()
return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
}
resp, err = readMongoMsg(conn, timeout)
if err != nil { if err != nil {
_ = client.Disconnect(authCtx) state.IncrementTCPFailedPacketCount()
return &AuthResult{ return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, Error: err}
Success: false, }
ErrorType: classifyMongoDBErrorType(err),
Error: err, // saslStart 响应检查:
// - ok:0 + code:18 → 认证失败
// - ok:1 + conversationId + payload → 认证有效
respStr := string(resp)
if strings.Contains(respStr, "\"ok\":0") || strings.Contains(respStr, "Authentication failed") {
return &AuthResult{Success: false, ErrorType: ErrorTypeAuth, Error: fmt.Errorf("authentication failed")}
}
// 如果在响应中找到 conversationId,说明凭据有效
if strings.Contains(respStr, "conversationId") {
state.IncrementTCPSuccessPacketCount()
return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil}
}
// 无认证失败的明确信号 = 尝试成功
state.IncrementTCPSuccessPacketCount()
return &AuthResult{Success: true, ErrorType: ErrorTypeUnknown, Error: nil}
}
// ── MongoDB wire protocol 工具 ──────────────────────────────────
const (
opMsg uint32 = 2013
opQuery uint32 = 2004
opReply uint32 = 1
)
var mongoRequestID uint32
func nextRequestID() uint32 {
mongoRequestID++
return mongoRequestID
}
// buildMongoCommand 构建 MongoDB 命令的 OP_MSG body (最小 BSON 实现)
// key 为字符串时,构建 {key: value} 作为命令名
// key 为 map 时,展开所有字段
func buildMongoCommand(db string, args ...interface{}) []byte {
var buf []byte
// flags: 0 (ChecksumPresent=0, MoreToCome=0, ExhaustAllowed=0)
buf = append(buf, 0, 0, 0, 0)
// section kind 0: body
buf = append(buf, 0)
// 构建 BSON 文档
if len(db) > 0 {
// {$db: "admin", ...}
docs := mongoDoc{"$db": db}
for i := 0; i < len(args); i++ {
switch v := args[i].(type) {
case string:
if i+1 < len(args) {
docs[v] = args[i+1]
i++
}
case mongoDoc:
for k, val := range v {
docs[k] = val
}
}
}
return append(buf, buildBSON(docs)...)
}
// 简单命令: {commandName: 1, $db: "admin"}
if len(args) >= 1 {
docs := mongoDoc{}
if cmdName, ok := args[0].(string); ok {
docs[cmdName] = 1
}
if len(args) >= 2 {
switch v := args[1].(type) {
case mongoDoc:
for k, val := range v {
docs[k] = val
}
}
}
if db != "" {
docs["$db"] = db
}
return append(buf, buildBSON(docs)...)
}
return buf
}
type mongoDoc map[string]interface{}
// buildBSON 构建最小 BSON 文档(仅支持 string/int32/double/binary/subdocument)
func buildBSON(doc mongoDoc) []byte {
var buf []byte
for k, v := range doc {
switch val := v.(type) {
case string:
buf = append(buf, 0x02) // type string
buf = append(buf, []byte(k)...)
buf = append(buf, 0x00)
b := []byte(val)
buf = append(buf, byte(len(b)+1), 0, 0, 0)
buf = append(buf, b...)
buf = append(buf, 0x00)
case int:
buf = append(buf, 0x10) // type int32
buf = append(buf, []byte(k)...)
buf = append(buf, 0x00)
i32 := make([]byte, 4)
binary.LittleEndian.PutUint32(i32, uint32(val))
buf = append(buf, i32...)
case float64:
buf = append(buf, 0x01) // type double
buf = append(buf, []byte(k)...)
buf = append(buf, 0x00)
f64 := make([]byte, 8)
binary.LittleEndian.PutUint64(f64, uint64(val))
buf = append(buf, f64...)
case mongoDoc:
buf = append(buf, 0x03) // type document
buf = append(buf, []byte(k)...)
buf = append(buf, 0x00)
sub := buildBSON(val)
buf = append(buf, sub...)
case []byte:
buf = append(buf, 0x05) // type binary
buf = append(buf, []byte(k)...)
buf = append(buf, 0x00)
buf = append(buf, byte(len(val)), 0, 0, 0)
buf = append(buf, 0x00) // subtype 0
buf = append(buf, val...)
case bool:
buf = append(buf, 0x08) // type boolean
buf = append(buf, []byte(k)...)
buf = append(buf, 0x00)
if val {
buf = append(buf, 0x01)
} else {
buf = append(buf, 0x00)
}
} }
} }
// 终止符
buf = append(buf, 0x00)
// 总长度前缀
lenBuf := make([]byte, 4)
binary.LittleEndian.PutUint32(lenBuf, uint32(len(buf)+4))
return append(lenBuf, buf...)
}
return &AuthResult{ // sendMongoMsg 发送 OP_MSG
Success: true, func sendMongoMsg(ctx context.Context, conn io.ReadWriter, body []byte, timeout time.Duration) (int, error) {
Conn: &mongoClientWrapper{client, ctx}, reqID := nextRequestID()
ErrorType: ErrorTypeUnknown, // 消息头: [4B totalLen] [4B requestID] [4B responseTo] [4B opCode]
Error: nil, totalLen := uint32(len(body) + 16)
header := make([]byte, 16)
binary.LittleEndian.PutUint32(header[0:4], totalLen)
binary.LittleEndian.PutUint32(header[4:8], reqID)
// responseTo=0, opCode=opMsg
binary.LittleEndian.PutUint32(header[12:16], opMsg)
return conn.Write(append(header, body...))
}
// readMongoMsg 读取 MongoDB 响应
func readMongoMsg(conn io.Reader, timeout time.Duration) ([]byte, error) {
// 读取 16 字节消息头
header := make([]byte, 16)
if _, err := io.ReadFull(conn, header); err != nil {
return nil, err
} }
msgLen := binary.LittleEndian.Uint32(header[0:4])
if msgLen < 16 {
return nil, fmt.Errorf("invalid message length: %d", msgLen)
}
// 读取剩余 body
bodyLen := int(msgLen) - 16
if bodyLen <= 0 || bodyLen > 1024*1024 {
return nil, nil
}
body := make([]byte, bodyLen)
if _, err := io.ReadFull(conn, body); err != nil {
return nil, err
}
// 跳过 OP_MSG 头部 (flags + sections),返回可用部分
// flags: 4 bytes, section kind: 1 byte → skip 5 bytes
if bodyLen > 5 {
return body[5:], nil
}
return body, nil
} }
// mongoClientWrapper 包装 mongo.Client 以实现 io.Closer // dialTCP 带超时的 TCP 连接
type mongoClientWrapper struct { func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn, error) {
*mongo.Client dialer := net.Dialer{Timeout: timeout}
ctx context.Context return dialer.DialContext(ctx, "tcp", addr)
} }
func (w *mongoClientWrapper) Close() error { // base64EncodeStr Base64 编码(标准编码)
return w.Disconnect(w.ctx) func base64EncodeStr(s string) string {
return base64.StdEncoding.EncodeToString([]byte(s))
}
// randomString 生成加密安全的随机字符串
func randomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
// 回退:不安全但不会失败
for i := range b {
b[i] = letters[i%len(letters)]
}
return string(b)
}
for i := range b {
b[i] = letters[int(b[i])%len(letters)]
}
return string(b)
} }
// classifyMongoDBErrorType MongoDB错误分类
func classifyMongoDBErrorType(err error) ErrorType { func classifyMongoDBErrorType(err error) ErrorType {
if err == nil { if err == nil {
return ErrorTypeUnknown return ErrorTypeUnknown
} }
mongoAuthErrors := []string{ mongoAuthErrors := []string{
"authentication failed", "authentication failed",
"auth mechanism", "auth mechanism",
"unauthorized", "unauthorized",
"scram", "scram",
"credential", "credential",
"invalid username",
"invalid password",
"login failed",
"access denied",
"authentication mechanism",
"sasl",
"mongo auth",
"bad auth", "bad auth",
"wrong credentials",
} }
mongoNetworkErrors := append(CommonNetworkErrors, mongoNetworkErrors := append(CommonNetworkErrors,
"dial tcp", "dial tcp",
"connection closed", "connection closed",
"eof", "eof",
"server selection timeout",
"connection pool closed",
"no reachable servers",
"topology",
"network error",
) )
return ClassifyError(err, mongoAuthErrors, mongoNetworkErrors) return ClassifyError(err, mongoAuthErrors, mongoNetworkErrors)
} }
// ── 服务识别 ────────────────────────────────────────────────────
func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult {
target := info.Target() target := info.Target()
isUnauth, err := p.mongodbUnauth(ctx, info, session) isUnauth, err := p.mongodbUnauth(ctx, info, session)
if err != nil { if err != nil {
return &ScanResult{ return &ScanResult{Success: false, Service: "mongodb", Error: err}
Success: false,
Service: "mongodb",
Error: err,
}
} }
if isUnauth { if isUnauth {
common.LogVuln(i18n.Tr("mongodb_unauth", target)) common.LogVuln(i18n.Tr("mongodb_unauth", target))
return &ScanResult{ return &ScanResult{Type: plugins.ResultTypeVuln, Success: true, Service: "mongodb", VulInfo: "未授权访问"}
Type: plugins.ResultTypeVuln,
Success: true,
Service: "mongodb",
VulInfo: "未授权访问",
}
} }
common.LogSuccess(i18n.Tr("mongodb_auth_required", target)) common.LogSuccess(i18n.Tr("mongodb_auth_required", target))
return &ScanResult{ return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "mongodb", Banner: "需要认证"}
Type: plugins.ResultTypeService,
Success: true,
Service: "mongodb",
Banner: "需要认证",
}
} }
// mongodbUnauth 检测MongoDB未授权访问
func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo, session *common.ScanSession) (bool, error) { func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo, session *common.ScanSession) (bool, error) {
msgPacket := p.createOpMsgPacket()
queryPacket := p.createOpQueryPacket()
realhost := fmt.Sprintf("%s:%d", info.Host, info.Port) realhost := fmt.Sprintf("%s:%d", info.Host, info.Port)
reply, err := p.checkMongoAuth(ctx, realhost, msgPacket, session) reply, err := p.checkMongoAuth(ctx, realhost, createOpMsgPacket(), session)
if err != nil { if err != nil {
reply, err = p.checkMongoAuth(ctx, realhost, queryPacket, session) reply, err = p.checkMongoAuth(ctx, realhost, createOpQueryPacket(), session)
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -240,7 +405,6 @@ func (p *MongoDBPlugin) mongodbUnauth(ctx context.Context, info *common.HostInfo
return false, fmt.Errorf("%s", i18n.Tr("service_not_identified", "MongoDB")) return false, fmt.Errorf("%s", i18n.Tr("service_not_identified", "MongoDB"))
} }
// checkMongoAuth 检查MongoDB认证状态
func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, packet []byte, session *common.ScanSession) (string, error) { 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.Timeout)
if err != nil { if err != nil {
@@ -255,11 +419,11 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
} }
if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.Timeout)); deadlineErr != nil { if deadlineErr := conn.SetDeadline(time.Now().Add(session.Config.Timeout)); deadlineErr != nil {
return "", fmt.Errorf("设置超时失败: %w", deadlineErr) return "", deadlineErr
} }
if _, writeErr := conn.Write(packet); writeErr != nil { if _, writeErr := conn.Write(packet); writeErr != nil {
return "", fmt.Errorf("发送查询失败: %w", writeErr) return "", writeErr
} }
select { select {
@@ -270,8 +434,8 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
reply := make([]byte, 2048) reply := make([]byte, 2048)
count, err := conn.Read(reply) count, err := conn.Read(reply)
if err != nil && !errors.Is(err, io.EOF) { if err != nil && err != io.EOF {
return "", fmt.Errorf("读取响应失败: %w", err) return "", err
} }
if count == 0 { if count == 0 {
@@ -281,8 +445,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack
return string(reply[:count]), nil return string(reply[:count]), nil
} }
// createOpMsgPacket 创建OP_MSG查询包 func createOpMsgPacket() []byte {
func (p *MongoDBPlugin) createOpMsgPacket() []byte {
return []byte{ return []byte{
0x69, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xdd, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x07, 0x00, 0x00,
@@ -300,8 +463,7 @@ func (p *MongoDBPlugin) createOpMsgPacket() []byte {
} }
} }
// createOpQueryPacket 创建OP_QUERY查询包 func createOpQueryPacket() []byte {
func (p *MongoDBPlugin) createOpQueryPacket() []byte {
return []byte{ return []byte{
0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00,