From bfd64f78ed7010d2d44505f9987b0a9d26c4f162 Mon Sep 17 00:00:00 2001 From: ZacharyZcR <2903735704@qq.com> Date: Mon, 18 May 2026 06:37:05 +0800 Subject: [PATCH] =?UTF-8?q?=E7=98=A6=E8=BA=AB:=20Kafka/MongoDB/Cassandra?= =?UTF-8?q?=E7=94=A8raw=20TCP=E6=9B=BF=E4=BB=A3=E9=87=8D=E5=9E=8B=E4=BE=9D?= =?UTF-8?q?=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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个测试包通过 --- go.mod | 20 +- go.sum | 52 +---- plugins/services/cassandra.go | 317 +++++++++++++++++++++-------- plugins/services/kafka.go | 284 +++++++++++++++----------- plugins/services/mongodb.go | 368 ++++++++++++++++++++++++---------- 5 files changed, 676 insertions(+), 365 deletions(-) diff --git a/go.mod b/go.mod index a7f2a0c..83331b5 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,10 @@ module github.com/shadow1ng/fscan go 1.20 require ( - github.com/IBM/sarama v1.43.3 github.com/denisenkom/go-mssqldb v0.12.3 github.com/fatih/color v1.18.0 github.com/go-ldap/ldap/v3 v3.4.9 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/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 @@ -26,7 +24,6 @@ require ( github.com/stacktitan/smb v0.0.0-20190531122847-da9a425dceb8 github.com/tjfoc/gmsm v1.4.1 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/net v0.32.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/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa // 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/go-asn1-ber/asn1-ber v1.5.7 // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // 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/go-multierror v1.1.1 // 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/rpc/v2 v2.0.3 // 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-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/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rogpeppe/go-internal v1.12.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 - gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/go.sum b/go.sum index 552c523..8107374 100644 --- a/go.sum +++ b/go.sum @@ -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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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/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/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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= 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.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 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/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/go.mod h1:jVPKeCbj6MvQZhwLYsGwaGI52oUorHoHKNecGT85ZCc= 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-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/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/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 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.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 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/go.mod h1:K2hpQgEjDp18J76a2DKFRlPBPpgRZgi6EbnpDgIhJ8s= 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/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 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/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 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/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/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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 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/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.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/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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/go.mod h1:3rdaFaCv4AyBgu5ALFM0+tSuHrBh6v692nyQe3ikrq0= 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/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= 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/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/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= 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= 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.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-20200622213623-75b288015ac9/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.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.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 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.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/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 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/plugins/services/cassandra.go b/plugins/services/cassandra.go index dbb6d7e..14427a3 100644 --- a/plugins/services/cassandra.go +++ b/plugins/services/cassandra.go @@ -4,16 +4,18 @@ package services import ( "context" + "encoding/binary" "fmt" - "strings" + "io" + "net" + "time" - "github.com/gocql/gocql" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins" ) -// CassandraPlugin Cassandra扫描插件 +// CassandraPlugin Cassandra扫描插件(纯 raw TCP CQL 协议实现) type CassandraPlugin struct { plugins.BasePlugin } @@ -47,7 +49,6 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi } } - // 使用公共框架进行并发凭据测试 authFn := p.createAuthFunc(info, config, state) testConfig := DefaultConcurrentTestConfigWithTarget(config, info) @@ -60,109 +61,230 @@ func (p *CassandraPlugin) Scan(ctx context.Context, info *common.HostInfo, sessi return result } -// createAuthFunc 创建Cassandra认证函数 func (p *CassandraPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc { return func(ctx context.Context, cred Credential) *AuthResult { 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 { - cluster := gocql.NewCluster(info.Host) - cluster.Port = info.Port - cluster.Timeout = config.Timeout - cluster.ConnectTimeout = config.Timeout + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + timeout := config.Timeout - if cred.Username != "" || cred.Password != "" { - cluster.Authenticator = gocql.PasswordAuthenticator{ - Username: cred.Username, - Password: cred.Password, - } - } - - session, err := cluster.CreateSession() + dialer := net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", addr) if err != nil { state.IncrementTCPFailedPacketCount() - return &AuthResult{ - Success: false, - ErrorType: classifyCassandraErrorType(err), - Error: err, - } + return &AuthResult{Success: false, ErrorType: classifyCassandraErrorType(err), Error: err} } - state.IncrementTCPSuccessPacketCount() + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) - var dummy string - err = session.Query("SELECT cluster_name FROM system.local").WithContext(ctx).Scan(&dummy) + // Step 1: STARTUP (CQL_VERSION=3.0.0) + 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 { - session.Close() - return &AuthResult{ - Success: false, - ErrorType: classifyCassandraErrorType(err), - Error: err, + state.IncrementTCPFailedPacketCount() + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, 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{ - Success: true, - Conn: &cassandraSessionWrapper{session}, - ErrorType: ErrorTypeUnknown, - Error: nil, + // Step 4: 发送测试查询 + queryBody := cqlLongString("SELECT cluster_name FROM system.local") + // 添加 consistency level (ONE=1) + queryBody = append(queryBody, 0x00, 0x01) // flags=0, consistency=ONE + 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 -type cassandraSessionWrapper struct { - *gocql.Session +// ── CQL wire protocol 工具 ────────────────────────────────────── + +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 { - w.Session.Close() - return nil +func cqlRecv(conn net.Conn) (byte, []byte, error) { + // 读取 9 字节头部(响应也有额外标志字节) + 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 { if err == nil { return ErrorTypeUnknown } - cassandraAuthErrors := []string{ "authentication failed", "bad credentials", "invalid credentials", - "username and/or password are incorrect", "unauthorized", - "access denied", } - return ClassifyError(err, cassandraAuthErrors, CommonNetworkErrors) } -// tryNoAuthConnection 尝试无认证连接 +// ── 无认证 + 服务识别 ────────────────────────────────────────── + func (p *CassandraPlugin) tryNoAuthConnection(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { target := info.Target() + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + timeout := config.Timeout - cluster := gocql.NewCluster(info.Host) - cluster.Port = info.Port - cluster.Timeout = config.Timeout - cluster.ConnectTimeout = config.Timeout - - session, err := cluster.CreateSession() + dialer := net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", addr) if err != nil { state.IncrementTCPFailedPacketCount() return nil } - state.IncrementTCPSuccessPacketCount() + defer conn.Close() + _ = conn.SetDeadline(time.Now().Add(timeout)) - var dummy string - err = session.Query("SELECT cluster_name FROM system.local").WithContext(ctx).Scan(&dummy) - if err != nil { - session.Close() + // STARTUP + if err := cqlSend(conn, cqlOpStartup, cqlStringMap(map[string]string{"CQL_VERSION": "3.0.0"})); err != nil { + state.IncrementTCPFailedPacketCount() + return nil + } + opcode, _, err := cqlRecv(conn) + if err != nil || opcode != cqlOpReady { 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)) return &ScanResult{ 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 { target := info.Target() + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) + timeout := config.Timeout - cluster := gocql.NewCluster(info.Host) - cluster.Port = info.Port - cluster.Timeout = config.Timeout - cluster.ConnectTimeout = config.Timeout - - session, err := cluster.CreateSession() + dialer := net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", addr) if err != nil { state.IncrementTCPFailedPacketCount() - if strings.Contains(strings.ToLower(err.Error()), "authentication") { - 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, - } + 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() - 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" common.LogSuccess(i18n.Tr("cassandra_service", target, banner)) - return &ScanResult{ - Type: plugins.ResultTypeService, - Success: true, - Service: "cassandra", - Banner: banner, + return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "cassandra", Banner: banner} +} + +// extractClusterName 从 CQL ROWS result body 提取 cluster_name +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() { diff --git a/plugins/services/kafka.go b/plugins/services/kafka.go index 862f80c..2d9491e 100644 --- a/plugins/services/kafka.go +++ b/plugins/services/kafka.go @@ -4,16 +4,18 @@ package services import ( "context" + "encoding/binary" "fmt" - "strings" + "io" + "net" + "time" - "github.com/IBM/sarama" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "github.com/shadow1ng/fscan/plugins" ) -// KafkaPlugin Kafka扫描插件 +// KafkaPlugin Kafka扫描插件(纯 raw TCP 实现,无重型依赖) type KafkaPlugin struct { plugins.BasePlugin } @@ -42,7 +44,6 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo, session * } } - // 使用公共框架进行并发凭据测试 authFn := p.createAuthFunc(info, config, state) testConfig := DefaultConcurrentTestConfigWithTarget(config, info) @@ -55,168 +56,223 @@ func (p *KafkaPlugin) Scan(ctx context.Context, info *common.HostInfo, session * return result } -// createAuthFunc 创建Kafka认证函数 func (p *KafkaPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc { return func(ctx context.Context, cred Credential) *AuthResult { 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 { - target := info.Target() + target := fmt.Sprintf("%s:%d", info.Host, info.Port) + timeout := config.Timeout - kafkaConfig := sarama.NewConfig() - kafkaConfig.Net.DialTimeout = config.Timeout - kafkaConfig.Net.ReadTimeout = config.Timeout - kafkaConfig.Net.WriteTimeout = config.Timeout - 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() - } - }() + dialer := net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", target) + if err != nil { + state.IncrementTCPFailedPacketCount() return &AuthResult{ Success: false, - ErrorType: ErrorTypeNetwork, - Error: ctx.Err(), + ErrorType: classifyKafkaErrorType(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 -type kafkaClientWrapper struct { - sarama.Client +// ── Kafka 协议编解码 ──────────────────────────────────────────── + +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 { - return w.Client.Close() +func kafkaRecv(conn net.Conn) ([]byte, error) { + // 读取 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 { if err == nil { return ErrorTypeUnknown } - kafkaAuthErrors := []string{ "sasl authentication failed", "authentication failed", "invalid credentials", "unauthorized", - "sasl/plain authentication failed", } - kafkaNetworkErrors := append(CommonNetworkErrors, - "kafka: client has run out of available brokers", "broker not available", "no available brokers", ) - return ClassifyError(err, kafkaAuthErrors, kafkaNetworkErrors) } +// ── 服务识别 ──────────────────────────────────────────────────── + func (p *KafkaPlugin) identifyService(ctx context.Context, info *common.HostInfo, config *common.Config, state *common.State) *ScanResult { target := info.Target() + timeout := config.Timeout - // 尝试无认证连接 - emptyCred := Credential{Username: "", Password: ""} - 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) + dialer := net.Dialer{Timeout: timeout} + conn, err := dialer.DialContext(ctx, "tcp", target) if err != nil { 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{ Success: false, Service: "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() - _ = client.Close() banner := "Kafka" common.LogSuccess(i18n.Tr("kafka_service", target, banner)) - return &ScanResult{ - Type: plugins.ResultTypeService, - Success: true, - Service: "kafka", - Banner: banner, - } + return &ScanResult{Type: plugins.ResultTypeService, Success: true, Service: "kafka", Banner: banner} } -func (p *KafkaPlugin) isKafkaProtocolError(err error) bool { - errStr := strings.ToLower(err.Error()) - return strings.Contains(errStr, "sasl") || - strings.Contains(errStr, "authentication") || - strings.Contains(errStr, "kafka") || - strings.Contains(errStr, "broker") +func (p *KafkaPlugin) isKafkaError(err error) bool { + if err == nil { + return false + } + // 连接成功后读不到数据 -> 需要认证的 Kafka + return true } func init() { diff --git a/plugins/services/mongodb.go b/plugins/services/mongodb.go index 509702d..58e1cec 100644 --- a/plugins/services/mongodb.go +++ b/plugins/services/mongodb.go @@ -4,20 +4,21 @@ package services import ( "context" - "errors" + "crypto/rand" + "encoding/base64" + "encoding/binary" "fmt" "io" + "net" "strings" "time" "github.com/shadow1ng/fscan/common" "github.com/shadow1ng/fscan/common/i18n" "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 { plugins.BasePlugin } @@ -37,14 +38,9 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session return p.identifyService(ctx, info, session) } - // 首先检测未授权访问 isUnauth, err := p.mongodbUnauth(ctx, info, session) if err != nil { - return &ScanResult{ - Success: false, - Service: "mongodb", - Error: err, - } + return &ScanResult{Success: false, Service: "mongodb", Error: err} } if isUnauth { @@ -57,7 +53,6 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session } } - // 如果需要认证,使用并发方式尝试常见凭据 credentials := GenerateCredentials("mongodb", config) if len(credentials) == 0 { return &ScanResult{ @@ -67,7 +62,6 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session } } - // 使用公共框架进行并发凭据测试 authFn := p.createAuthFunc(info, config, state) testConfig := DefaultConcurrentTestConfigWithTarget(config, info) @@ -80,150 +74,321 @@ func (p *MongoDBPlugin) Scan(ctx context.Context, info *common.HostInfo, session return result } -// createAuthFunc 创建MongoDB认证函数 func (p *MongoDBPlugin) createAuthFunc(info *common.HostInfo, config *common.Config, state *common.State) AuthFunc { return func(ctx context.Context, cred Credential) *AuthResult { 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 { - var uri string + addr := fmt.Sprintf("%s:%d", info.Host, info.Port) timeout := config.Timeout - if cred.Username != "" && cred.Password != "" { - 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) + conn, err := dialTCP(ctx, addr, timeout) if err != nil { state.IncrementTCPFailedPacketCount() - return &AuthResult{ - Success: false, - ErrorType: classifyMongoDBErrorType(err), - Error: err, - } + return &AuthResult{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 { - _ = client.Disconnect(authCtx) - return &AuthResult{ - Success: false, - ErrorType: classifyMongoDBErrorType(err), - Error: err, + state.IncrementTCPFailedPacketCount() + return &AuthResult{Success: false, ErrorType: ErrorTypeNetwork, 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{ - Success: true, - Conn: &mongoClientWrapper{client, ctx}, - ErrorType: ErrorTypeUnknown, - Error: nil, +// sendMongoMsg 发送 OP_MSG +func sendMongoMsg(ctx context.Context, conn io.ReadWriter, body []byte, timeout time.Duration) (int, error) { + reqID := nextRequestID() + // 消息头: [4B totalLen] [4B requestID] [4B responseTo] [4B opCode] + 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 -type mongoClientWrapper struct { - *mongo.Client - ctx context.Context +// dialTCP 带超时的 TCP 连接 +func dialTCP(ctx context.Context, addr string, timeout time.Duration) (net.Conn, error) { + dialer := net.Dialer{Timeout: timeout} + return dialer.DialContext(ctx, "tcp", addr) } -func (w *mongoClientWrapper) Close() error { - return w.Disconnect(w.ctx) +// base64EncodeStr Base64 编码(标准编码) +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 { if err == nil { return ErrorTypeUnknown } - mongoAuthErrors := []string{ "authentication failed", "auth mechanism", "unauthorized", "scram", "credential", - "invalid username", - "invalid password", - "login failed", - "access denied", - "authentication mechanism", - "sasl", - "mongo auth", "bad auth", - "wrong credentials", } - mongoNetworkErrors := append(CommonNetworkErrors, "dial tcp", "connection closed", "eof", - "server selection timeout", - "connection pool closed", - "no reachable servers", - "topology", - "network error", ) - return ClassifyError(err, mongoAuthErrors, mongoNetworkErrors) } +// ── 服务识别 ──────────────────────────────────────────────────── + func (p *MongoDBPlugin) identifyService(ctx context.Context, info *common.HostInfo, session *common.ScanSession) *ScanResult { target := info.Target() isUnauth, err := p.mongodbUnauth(ctx, info, session) if err != nil { - return &ScanResult{ - Success: false, - Service: "mongodb", - Error: err, - } + return &ScanResult{Success: false, Service: "mongodb", Error: err} } if isUnauth { common.LogVuln(i18n.Tr("mongodb_unauth", target)) - return &ScanResult{ - Type: plugins.ResultTypeVuln, - Success: true, - Service: "mongodb", - VulInfo: "未授权访问", - } + return &ScanResult{Type: plugins.ResultTypeVuln, Success: true, Service: "mongodb", VulInfo: "未授权访问"} } common.LogSuccess(i18n.Tr("mongodb_auth_required", target)) - return &ScanResult{ - Type: plugins.ResultTypeService, - Success: true, - Service: "mongodb", - Banner: "需要认证", - } + return &ScanResult{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) { - msgPacket := p.createOpMsgPacket() - queryPacket := p.createOpQueryPacket() 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 { - reply, err = p.checkMongoAuth(ctx, realhost, queryPacket, session) + reply, err = p.checkMongoAuth(ctx, realhost, createOpQueryPacket(), session) if err != nil { 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")) } -// checkMongoAuth 检查MongoDB认证状态 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) 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 { - return "", fmt.Errorf("设置超时失败: %w", deadlineErr) + return "", deadlineErr } if _, writeErr := conn.Write(packet); writeErr != nil { - return "", fmt.Errorf("发送查询失败: %w", writeErr) + return "", writeErr } select { @@ -270,8 +434,8 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack reply := make([]byte, 2048) count, err := conn.Read(reply) - if err != nil && !errors.Is(err, io.EOF) { - return "", fmt.Errorf("读取响应失败: %w", err) + if err != nil && err != io.EOF { + return "", err } if count == 0 { @@ -281,8 +445,7 @@ func (p *MongoDBPlugin) checkMongoAuth(ctx context.Context, address string, pack return string(reply[:count]), nil } -// createOpMsgPacket 创建OP_MSG查询包 -func (p *MongoDBPlugin) createOpMsgPacket() []byte { +func createOpMsgPacket() []byte { return []byte{ 0x69, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xdd, 0x07, 0x00, 0x00, @@ -300,8 +463,7 @@ func (p *MongoDBPlugin) createOpMsgPacket() []byte { } } -// createOpQueryPacket 创建OP_QUERY查询包 -func (p *MongoDBPlugin) createOpQueryPacket() []byte { +func createOpQueryPacket() []byte { return []byte{ 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd4, 0x07, 0x00, 0x00,