network-utility 1.1.62 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b52e37176f28650bea03c4d996018c516d5c780775725db49a3fde963f4a0e5f
4
- data.tar.gz: 8f138c019e14c0bf9f24b24e50108dac4eea7cab22e7274d83babad0dbaec96e
3
+ metadata.gz: c8d6c9de02a10fbfb742fe2ec10d5b98fab8ce8398f635012c53ccb5a3e12a58
4
+ data.tar.gz: d869326b07a6023b9bbecd4ea14866e6e92201ac566d100a45d4eaa6fe7b9ce4
5
5
  SHA512:
6
- metadata.gz: d04392372b1e23d4c9157c9ab5ff47799c1ffbb1b3dcc045b3062629632d5b5fb0af13005d63e6ed42bfb28ec35cc300daf9046d18bb80621955c21359ff2e2e
7
- data.tar.gz: abed1d881a283f5f722b5ed0f3b19324bdc805008b3e3fe050cdc5e36e40681e679a6d8aef044268d5961b254fb2bf97deb875eb8556ce84bbb51ab36086f6a0
6
+ metadata.gz: 97930037e680adcf70a59706305e1f71c5a9633095c094080e28a7bd26e8b837fb5a9040d4f0f143fe32201e090192401df905780851d9d854979168490558e3
7
+ data.tar.gz: 85108233ddc36ca6ad171c6560e12c4595261f97495d0fab4aa35471cbef25cdb4d3416a5714b2c28b8cba990a8d1f2950df69e9d68aed83427b91371fbebdac
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ # SSH协议检测器 (简化实用版)
3
+ # 功能: 检测SSH服务器支持的KEX/Cipher/MAC/HostKey算法
4
+ # 协议: 实现SSH-TRANS传输层协议 (RFC 4253)
5
+ require 'cc'
6
+ require 'network'
7
+
8
+ if :main
9
+ if ARGV.empty?
10
+ puts "SSH协议检测器 (Ruby实现)"
11
+ puts "=" * 50
12
+ puts "用法: ruby ssh_detector.rb <主机> [端口]"
13
+ puts ""
14
+ puts "示例:"
15
+ puts " ruby ssh_detector.rb github.com"
16
+ puts " ruby ssh_detector.rb 192.168.1.100 2222"
17
+ puts ""
18
+ puts "说明:"
19
+ puts " • 实现SSH-TRANS传输层协议 (RFC 4253)"
20
+ puts " • 探测KEX/Cipher/MAC/HostKey算法支持"
21
+ puts " • 识别弱算法和安全风险"
22
+ puts " • 无需外部gem依赖"
23
+ exit 1
24
+ end
25
+
26
+ host = ARGV[0]
27
+ port = (ARGV[1] || 22).to_i
28
+
29
+ SSHDetector.new(host, port).detect
30
+ end
data/network.rb CHANGED
@@ -11,7 +11,10 @@
11
11
  ].each{|mod|require "utility/#{mod}"}
12
12
 
13
13
  [
14
- 'snmp'
14
+ 'ssh/2310/detector/ssh_native_detector',
15
+ 'ssh/2310/detector/ssh_detector_final',
16
+ 'ssh/2310/detector/ssh_detector',
17
+ 'snmp/snmp'
15
18
  ].each{|mod|require "support/#{mod}"}
16
19
 
17
20
  [
@@ -23,5 +26,5 @@
23
26
  ].each{|mod|require mod}
24
27
 
25
28
  module Network
26
- VERSION = '1.1.62'
29
+ VERSION = '2.0.0'
27
30
  end
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env ruby
2
+ # SSH协议检测器 - 探测对端支持的算法套件
3
+ # 依赖: gem install net-ssh
4
+
5
+ require 'net/ssh'
6
+ require 'net/ssh/transport/session'
7
+ require 'socket'
8
+ require 'timeout'
9
+
10
+ class SSHProtocolDetector
11
+ attr_reader :host, :port, :results
12
+
13
+ # 已知的算法分类
14
+ KEX_ALGORITHMS = [
15
+ "curve25519-sha256", "curve25519-sha256@libssh.org",
16
+ "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
17
+ "diffie-hellman-group-exchange-sha256", "diffie-hellman-group16-sha512",
18
+ "diffie-hellman-group18-sha512", "diffie-hellman-group14-sha256",
19
+ "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1",
20
+ "kex-strict-s-v00@openssh.com"
21
+ ].freeze
22
+
23
+ CIPHER_ALGORITHMS = [
24
+ "chacha20-poly1305@openssh.com",
25
+ "aes256-gcm@openssh.com", "aes128-gcm@openssh.com",
26
+ "aes256-ctr", "aes192-ctr", "aes128-ctr",
27
+ "aes256-cbc", "aes192-cbc", "aes128-cbc",
28
+ "3des-cbc", "blowfish-cbc", "cast128-cbc",
29
+ "arcfour", "arcfour128", "arcfour256"
30
+ ].freeze
31
+
32
+ MAC_ALGORITHMS = [
33
+ "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com",
34
+ "hmac-sha1-etm@openssh.com",
35
+ "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1",
36
+ "hmac-md5", "hmac-md5-96", "hmac-sha1-96",
37
+ "umac-64-etm@openssh.com", "umac-128-etm@openssh.com",
38
+ "umac-64@openssh.com", "umac-128@openssh.com"
39
+ ].freeze
40
+
41
+ HOST_KEY_ALGORITHMS = [
42
+ "ssh-ed25519", "ssh-ed25519-cert-v01@openssh.com",
43
+ "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521",
44
+ "rsa-sha2-512", "rsa-sha2-256",
45
+ "ssh-rsa", "ssh-dss"
46
+ ].freeze
47
+
48
+ COMPRESSION_ALGORITHMS = [
49
+ "none", "zlib@openssh.com", "zlib"
50
+ ].freeze
51
+
52
+ def initialize(host, port = 22, timeout = 10)
53
+ @host = host
54
+ @port = port
55
+ @timeout = timeout
56
+ @results = {}
57
+ end
58
+
59
+ # 主检测方法
60
+ def detect
61
+ puts "🔍 正在检测 SSH 协议支持: #{@host}:#{@port}"
62
+ puts "=" * 60
63
+
64
+ # 1. 基础连通性检测
65
+ return false unless check_connectivity
66
+
67
+ # 2. 获取Banner信息
68
+ fetch_banner
69
+
70
+ # 3. 使用Net::SSH探测算法
71
+ probe_algorithms
72
+
73
+ # 4. 详细握手分析
74
+ analyze_handshake
75
+
76
+ display_results
77
+ true
78
+ rescue => e
79
+ puts "❌ 检测失败: #{e.message}"
80
+ false
81
+ end
82
+
83
+ private
84
+
85
+ # 检查TCP连通性
86
+ def check_connectivity
87
+ Timeout::timeout(@timeout) do
88
+ TCPSocket.new(@host, @port).close
89
+ end
90
+ puts "✅ TCP连接正常"
91
+ true
92
+ rescue => e
93
+ puts "❌ 无法连接到 #{@host}:#{@port} - #{e.message}"
94
+ false
95
+ end
96
+
97
+ # 获取SSH Banner
98
+ def fetch_banner
99
+ socket = TCPSocket.new(@host, @port)
100
+ banner = socket.gets&.chomp
101
+ socket.close
102
+
103
+ @results[:banner] = banner
104
+ puts "📢 SSH Banner: #{banner}"
105
+
106
+ # 解析版本
107
+ if banner =~ /SSH-(\d+\.\d+)-(.+)/
108
+ @results[:protocol_version] = $1
109
+ @results[:software_version] = $2
110
+ end
111
+ rescue => e
112
+ puts "⚠️ 获取Banner失败: #{e.message}"
113
+ end
114
+
115
+ # 探测支持的算法
116
+ def probe_algorithms
117
+ puts "\n🔬 正在探测算法支持..."
118
+
119
+ begin
120
+ # 创建临时SSH会话获取算法列表
121
+ session = Net::SSH::Transport::Session.new(
122
+ @host,
123
+ port: @port,
124
+ timeout: @timeout,
125
+ # 提供所有可能的算法进行协商
126
+ kex: KEX_ALGORITHMS,
127
+ host_key: HOST_KEY_ALGORITHMS,
128
+ encryption: CIPHER_ALGORITHMS,
129
+ hmac: MAC_ALGORITHMS,
130
+ compression: COMPRESSION_ALGORITHMS,
131
+ # 不验证主机密钥,仅探测
132
+ paranoid: false,
133
+ verify_host_key: :never,
134
+ # 使用空认证,仅完成KEX
135
+ auth_methods: ['none']
136
+ )
137
+
138
+ # 获取协商后的算法
139
+ algorithms = session.algorithms
140
+
141
+ @results[:negotiated] = {
142
+ kex: algorithms.kex,
143
+ host_key: algorithms.host_key,
144
+ encryption_client: algorithms.encryption[:client],
145
+ encryption_server: algorithms.encryption[:server],
146
+ mac_client: algorithms.hmac[:client],
147
+ mac_server: algorithms.hmac[:server],
148
+ compression_client: algorithms.compression[:client],
149
+ compression_server: algorithms.compression[:server]
150
+ }
151
+
152
+ session.close
153
+ rescue Net::SSH::AuthenticationFailed
154
+ # 认证失败是正常的,说明KEX已完成
155
+ puts "✅ 密钥交换成功 (认证阶段被拒绝)"
156
+ rescue => e
157
+ puts "⚠️ 算法探测警告: #{e.message}"
158
+ end
159
+ end
160
+
161
+ # 深度握手分析
162
+ def analyze_handshake
163
+ puts "\n🔍 分析密钥交换详情..."
164
+
165
+ socket = TCPSocket.new(@host, @port)
166
+
167
+ # 发送我们的Banner
168
+ socket.write("SSH-2.0-RubyDetector_1.0\r\n")
169
+
170
+ # 读取服务器Banner
171
+ banner = socket.gets
172
+ puts " 服务器回应: #{banner&.chomp}"
173
+
174
+ socket.close
175
+ rescue => e
176
+ puts "⚠️ 握手分析受限: #{e.message}"
177
+ end
178
+
179
+ # 显示结果
180
+ def display_results
181
+ puts "\n" + "=" * 60
182
+ puts "📊 检测结果汇总"
183
+ puts "=" * 60
184
+
185
+ if @results[:protocol_version]
186
+ puts "\n🌐 协议版本: SSH-#{@results[:protocol_version]}"
187
+ puts "🖥️ 软件版本: #{@results[:software_version]}"
188
+ end
189
+
190
+ if @results[:negotiated]
191
+ neg = @results[:negotiated]
192
+ puts "\n🤝 协商成功的算法组合:"
193
+ puts " 密钥交换: #{neg[:kex]}"
194
+ puts " 主机密钥: #{neg[:host_key]}"
195
+ puts " 加密算法(客户端→服务端): #{neg[:encryption_client]}"
196
+ puts " 加密算法(服务端→客户端): #{neg[:encryption_server]}"
197
+ puts " MAC算法(客户端→服务端): #{neg[:mac_client]}"
198
+ puts " MAC算法(服务端→客户端): #{neg[:mac_server]}"
199
+ puts " 压缩(客户端→服务端): #{neg[:compression_client]}"
200
+ puts " 压缩(服务端→客户端): #{neg[:compression_server]}"
201
+ end
202
+
203
+ puts "\n🔐 安全建议:"
204
+ check_security
205
+ end
206
+
207
+ # 安全检查
208
+ def check_security
209
+ neg = @results[:negotiated]
210
+ return unless neg
211
+
212
+ # 检查弱算法
213
+ weak_kex = ["diffie-hellman-group1-sha1", "diffie-hellman-group14-sha1"]
214
+ weak_cipher = ["3des-cbc", "blowfish-cbc", "cast128-cbc", "arcfour", "arcfour128", "arcfour256", "aes256-cbc", "aes192-cbc", "aes128-cbc"]
215
+ weak_mac = ["hmac-md5", "hmac-md5-96", "hmac-sha1-96"]
216
+
217
+ warnings = []
218
+
219
+ if weak_kex.include?(neg[:kex])
220
+ warnings << "⚠️ 使用弱密钥交换算法: #{neg[:kex]}"
221
+ end
222
+
223
+ if weak_cipher.include?(neg[:encryption_client]) || weak_cipher.include?(neg[:encryption_server])
224
+ warnings << "⚠️ 使用弱加密算法"
225
+ end
226
+
227
+ if weak_mac.include?(neg[:mac_client]) || weak_mac.include?(neg[:mac_server])
228
+ warnings << "⚠️ 使用弱MAC算法"
229
+ end
230
+
231
+ if warnings.empty?
232
+ puts " ✅ 算法配置安全"
233
+ else
234
+ warnings.each { |w| puts " #{w}" }
235
+ end
236
+
237
+ # 推荐配置
238
+ puts "\n💡 推荐算法:"
239
+ puts " KEX: curve25519-sha256, ecdh-sha2-nistp521"
240
+ puts " Cipher: chacha20-poly1305@openssh.com, aes256-gcm@openssh.com"
241
+ puts " MAC: hmac-sha2-256-etm@openssh.com"
242
+ end
243
+ end
244
+
245
+ # 命令行接口
246
+ if __FILE__ == $0
247
+ if ARGV.empty?
248
+ puts "用法: ruby ssh_detector.rb <host> [port]"
249
+ puts "示例: ruby ssh_detector.rb github.com"
250
+ puts " ruby ssh_detector.rb 192.168.1.1 2222"
251
+ exit 1
252
+ end
253
+
254
+ host = ARGV[0]
255
+ port = (ARGV[1] || 22).to_i
256
+
257
+ detector = SSHProtocolDetector.new(host, port)
258
+ detector.detect
259
+ end
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/env ruby
2
+ # SSH协议检测器 (简化实用版)
3
+ # 功能: 检测SSH服务器支持的KEX/Cipher/MAC/HostKey算法
4
+ # 协议: 实现SSH-TRANS传输层协议 (RFC 4253)
5
+
6
+ require 'socket'
7
+ require 'timeout'
8
+
9
+ class SSHDetector
10
+ # SSH消息类型
11
+ SSH_MSG_KEXINIT = 20
12
+
13
+ # 测试用的算法列表
14
+ TEST_ALGORITHMS = {
15
+ kex: "curve25519-sha256,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group14-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512",
16
+ host_key: "ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,rsa-sha2-512,rsa-sha2-256,ssh-rsa",
17
+ cipher: "chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr",
18
+ mac: "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512",
19
+ compression: "none,zlib@openssh.com"
20
+ }
21
+
22
+ def initialize(host, port = 22)
23
+ @host = host
24
+ @port = port
25
+ @socket = nil
26
+ end
27
+
28
+ def detect
29
+ puts "\n🔍 SSH协议检测: #{@host}:#{@port}"
30
+ puts "=" * 60
31
+
32
+ connect
33
+ banner = exchange_banner
34
+ kex_data = perform_kex_init
35
+
36
+ if kex_data
37
+ display_results(banner, kex_data)
38
+ else
39
+ puts "❌ 无法完成密钥交换"
40
+ end
41
+ rescue => e
42
+ puts "❌ 错误: #{e.message}"
43
+ ensure
44
+ @socket&.close
45
+ end
46
+
47
+ private
48
+
49
+ def connect
50
+ @socket = TCPSocket.new(@host, @port)
51
+ puts "✅ TCP连接成功"
52
+ end
53
+
54
+ def exchange_banner
55
+ # 发送客户端Banner
56
+ @socket.write("SSH-2.0-RubyDetector_1.0\r\n")
57
+
58
+ # 读取服务器Banner
59
+ banner = @socket.gets&.chomp
60
+ puts "📢 服务器Banner: #{banner}"
61
+
62
+ # 检查版本
63
+ if banner =~ /SSH-(\d+\.\d+)-(.+)/
64
+ version = $1
65
+ software = $2
66
+ puts " 协议版本: SSH-#{version}"
67
+ puts " 软件版本: #{software}"
68
+
69
+ if version.start_with?("1.")
70
+ puts "⚠️ 警告: 服务器支持SSH v1(不安全)"
71
+ end
72
+ end
73
+
74
+ banner
75
+ end
76
+
77
+ def perform_kex_init
78
+ puts "\n🔬 执行密钥交换探测..."
79
+
80
+ # 构造KEX_INIT包
81
+ send_kex_init
82
+
83
+ # 接收服务器KEX_INIT
84
+ payload = recv_packet
85
+ return nil unless payload
86
+
87
+ # 解析算法列表
88
+ parse_kex_init(payload)
89
+ end
90
+
91
+ def send_kex_init
92
+ # 构造KEXINIT payload
93
+ payload = [SSH_MSG_KEXINIT].pack("C") # 消息类型
94
+ payload += "\x00" * 16 # cookie (16字节随机,这里简化)
95
+
96
+ # 添加算法列表 (name-list: uint32长度 + 字符串)
97
+ TEST_ALGORITHMS.each do |_, algos|
98
+ payload += [algos.length].pack("N") + algos
99
+ end
100
+
101
+ # 语言列表 (空)
102
+ payload += [0].pack("N") # languages_client_to_server
103
+ payload += [0].pack("N") # languages_server_to_client
104
+ payload += [0].pack("C") # first_kex_packet_follows = false
105
+ payload += [0].pack("N") # 保留
106
+
107
+ # 包装成SSH包
108
+ send_packet(payload)
109
+ end
110
+
111
+ def send_packet(payload)
112
+ block_size = 8
113
+ min_padding = 4
114
+
115
+ payload_len = payload.length
116
+ padding_len = block_size - ((payload_len + 5) % block_size)
117
+ padding_len += block_size if padding_len < min_padding
118
+
119
+ packet_len = payload_len + padding_len + 1
120
+ packet = [packet_len].pack("N")
121
+ packet += [padding_len].pack("C")
122
+ packet += payload
123
+ packet += "\x00" * padding_len
124
+
125
+ @socket.write(packet)
126
+ end
127
+
128
+ def recv_packet
129
+ # 读取包长度
130
+ len_bytes = @socket.read(4)
131
+ return nil unless len_bytes && len_bytes.length == 4
132
+
133
+ packet_len = len_bytes.unpack1("N")
134
+ return nil if packet_len > 35000
135
+
136
+ # 读取包内容
137
+ packet = @socket.read(packet_len)
138
+ return nil unless packet && packet.length == packet_len
139
+
140
+ # 解析: padding_len(1) + payload + padding
141
+ padding_len = packet[0].unpack1("C")
142
+ packet[1..-(padding_len+1)]
143
+ end
144
+
145
+ def parse_kex_init(payload)
146
+ return nil if payload.nil? || payload.empty?
147
+
148
+ msg_type = payload[0].unpack1("C")
149
+ return nil unless msg_type == SSH_MSG_KEXINIT
150
+
151
+ # 跳过16字节cookie
152
+ offset = 17
153
+
154
+ # 读取name-list的辅助函数
155
+ read_list = lambda {
156
+ return nil if offset + 4 > payload.length
157
+ len = payload[offset, 4].unpack1("N")
158
+ offset += 4
159
+ return nil if offset + len > payload.length
160
+ str = payload[offset, len]
161
+ offset += len
162
+ str.split(",")
163
+ }
164
+
165
+ {
166
+ kex: read_list.call,
167
+ host_key: read_list.call,
168
+ cipher_c2s: read_list.call,
169
+ cipher_s2c: read_list.call,
170
+ mac_c2s: read_list.call,
171
+ mac_s2c: read_list.call,
172
+ compress_c2s: read_list.call,
173
+ compress_s2c: read_list.call
174
+ }
175
+ end
176
+
177
+ def display_results(banner, data)
178
+ puts "\n" + "=" * 60
179
+ puts "📊 检测结果"
180
+ puts "=" * 60
181
+
182
+ puts "\n🔑 密钥交换算法 (KEX) - #{data[:kex]&.length || 0}种:"
183
+ data[:kex]&.each_slice(4) { |slice| puts " • #{slice.join(' • ')}" }
184
+
185
+ puts "\n🔐 主机密钥算法 (Host Key) - #{data[:host_key]&.length || 0}种:"
186
+ data[:host_key]&.each_slice(4) { |slice| puts " • #{slice.join(' • ')}" }
187
+
188
+ puts "\n🔒 加密算法 (Cipher) - #{data[:cipher_c2s]&.length || 0}种:"
189
+ data[:cipher_c2s]&.each_slice(3) { |slice| puts " • #{slice.join(' • ')}" }
190
+
191
+ puts "\n📝 MAC算法 - #{data[:mac_c2s]&.length || 0}种:"
192
+ data[:mac_c2s]&.each_slice(3) { |slice| puts " • #{slice.join(' • ')}" }
193
+
194
+ puts "\n📦 压缩算法 - #{data[:compress_c2s]&.length || 0}种:"
195
+ puts " • #{data[:compress_c2s]&.join(' • ')}"
196
+
197
+ # 安全分析
198
+ analyze_security(data)
199
+
200
+ puts "\n📋 SSH协议栈结构:"
201
+ puts " ┌─────────────────────────────────────┐"
202
+ puts " │ SSH-CONN (连接层) - 端口转发/会话 │"
203
+ puts " ├─────────────────────────────────────┤"
204
+ puts " │ SSH-AUTH (认证层) - 用户认证 │"
205
+ puts " ├─────────────────────────────────────┤"
206
+ puts " │ SSH-TRANS (传输层) │"
207
+ puts " │ ├── KEX: #{data[:kex]&.first || 'N/A'}"[0..50]
208
+ puts " │ ├── Cipher: #{data[:cipher_c2s]&.first || 'N/A'}"[0..50]
209
+ puts " │ ├── MAC: #{data[:mac_c2s]&.first || 'N/A'}"[0..50]
210
+ puts " │ └── Compression: #{data[:compress_c2s]&.first || 'none'}"[0..50]
211
+ puts " └─────────────────────────────────────┘"
212
+ end
213
+
214
+ def analyze_security(data)
215
+ puts "\n🔐 安全分析:"
216
+
217
+ weak_algos = {
218
+ "diffie-hellman-group1-sha1" => "使用SHA1的DH组1(非常弱)",
219
+ "diffie-hellman-group14-sha1" => "使用SHA1的DH组14",
220
+ "3des-cbc" => "3DES加密(慢且不安全)",
221
+ "blowfish-cbc" => "Blowfish(已废弃)",
222
+ "arcfour" => "RC4(严重不安全)",
223
+ "hmac-md5" => "MD5哈希(不安全)",
224
+ "hmac-sha1" => "SHA1(考虑升级)"
225
+ }
226
+
227
+ issues = []
228
+ all_algos = (data[:kex] || []) + (data[:cipher_c2s] || []) + (data[:mac_c2s] || [])
229
+
230
+ weak_algos.each do |algo, desc|
231
+ if all_algos.any? { |a| a.include?(algo) }
232
+ issues << "⚠️ 发现弱算法 #{algo}: #{desc}"
233
+ end
234
+ end
235
+
236
+ if issues.empty?
237
+ puts " ✅ 未发现明显的弱算法"
238
+ else
239
+ issues.each { |i| puts " #{i}" }
240
+ end
241
+
242
+ # 推荐
243
+ puts "\n💡 推荐配置:"
244
+ puts " KEX: curve25519-sha256, ecdh-sha2-nistp521"
245
+ puts " Cipher: chacha20-poly1305@openssh.com, aes256-gcm@openssh.com"
246
+ puts " MAC: hmac-sha2-256-etm@openssh.com"
247
+ end
248
+ end
249
+
250
+ # 命令行入口
251
+ if __FILE__ == $0
252
+ if ARGV.empty?
253
+ puts "SSH协议检测器 (Ruby实现)"
254
+ puts "=" * 50
255
+ puts "用法: ruby ssh_detector.rb <主机> [端口]"
256
+ puts ""
257
+ puts "示例:"
258
+ puts " ruby ssh_detector.rb github.com"
259
+ puts " ruby ssh_detector.rb 192.168.1.100 2222"
260
+ puts ""
261
+ puts "说明:"
262
+ puts " • 实现SSH-TRANS传输层协议 (RFC 4253)"
263
+ puts " • 探测KEX/Cipher/MAC/HostKey算法支持"
264
+ puts " • 识别弱算法和安全风险"
265
+ puts " • 无需外部gem依赖"
266
+ exit 1
267
+ end
268
+
269
+ host = ARGV[0]
270
+ port = (ARGV[1] || 22).to_i
271
+
272
+ SSHDetector.new(host, port).detect
273
+ end
@@ -0,0 +1,348 @@
1
+ #!/usr/bin/env ruby
2
+ # SSH协议检测器 - 原生实现(无需外部gem)
3
+ # 实现SSH传输层协议RFC 4253,直接解析二进制协议
4
+
5
+ require 'socket'
6
+ require 'timeout'
7
+ require 'digest'
8
+
9
+ class SSHNativeDetector
10
+ SSH_MSG_KEXINIT = 20
11
+ SSH_MSG_NEWKEYS = 21
12
+
13
+ # 常用算法列表
14
+ ALGORITHMS = {
15
+ kex: [
16
+ "curve25519-sha256", "curve25519-sha256@libssh.org",
17
+ "ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521",
18
+ "diffie-hellman-group-exchange-sha256", "diffie-hellman-group16-sha512",
19
+ "diffie-hellman-group18-sha512", "diffie-hellman-group14-sha256",
20
+ "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1",
21
+ "diffie-hellman-group1-sha1"
22
+ ],
23
+ host_key: [
24
+ "ssh-ed25519", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384",
25
+ "ecdsa-sha2-nistp521", "rsa-sha2-512", "rsa-sha2-256", "ssh-rsa", "ssh-dss"
26
+ ],
27
+ cipher: [
28
+ "chacha20-poly1305@openssh.com",
29
+ "aes256-gcm@openssh.com", "aes128-gcm@openssh.com",
30
+ "aes256-ctr", "aes192-ctr", "aes128-ctr",
31
+ "aes256-cbc", "aes192-cbc", "aes128-cbc",
32
+ "3des-cbc", "blowfish-cbc", "cast128-cbc"
33
+ ],
34
+ mac: [
35
+ "hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com",
36
+ "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1",
37
+ "hmac-md5", "umac-64@openssh.com", "umac-128@openssh.com"
38
+ ],
39
+ compression: ["none", "zlib@openssh.com", "zlib"]
40
+ }
41
+
42
+ def initialize(host, port = 22, timeout = 10)
43
+ @host = host
44
+ @port = port
45
+ @timeout = timeout
46
+ @results = {}
47
+ @socket = nil
48
+ end
49
+
50
+ def detect
51
+ puts "🔍 SSH协议深度检测: #{@host}:#{@port}"
52
+ puts "=" * 70
53
+
54
+ begin
55
+ connect
56
+ exchange_banner
57
+ perform_kex_init
58
+ analyze_security
59
+ display_report
60
+ rescue => e
61
+ puts "❌ 错误: #{e.message}"
62
+ puts e.backtrace.first(3).join("\n")
63
+ ensure
64
+ @socket&.close
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def connect
71
+ @socket = Timeout::timeout(@timeout) { TCPSocket.new(@host, @port) }
72
+ puts "✅ TCP连接建立成功"
73
+ end
74
+
75
+ def exchange_banner
76
+ # 发送客户端Banner
77
+ @socket.write("SSH-2.0-RubyDetector_1.0\r\n")
78
+
79
+ # 读取服务器Banner
80
+ server_banner = @socket.gets&.chomp
81
+ raise "未收到SSH Banner" unless server_banner
82
+
83
+ @results[:server_banner] = server_banner
84
+ puts "📢 服务器Banner: #{server_banner}"
85
+
86
+ # 解析版本
87
+ if server_banner =~ /SSH-(\d+\.\d+)-(.+)/
88
+ @results[:ssh_version] = $1
89
+ @results[:software] = $2
90
+ else
91
+ raise "无效的SSH Banner格式"
92
+ end
93
+
94
+ # 检查旧版本
95
+ if @results[:ssh_version] == "1.99" || @results[:ssh_version]&.start_with?("1.")
96
+ puts "⚠️ 警告: 服务器支持SSH v1(不安全)"
97
+ end
98
+ end
99
+
100
+ def perform_kex_init
101
+ puts "\n🔬 执行密钥交换初始化..."
102
+
103
+ # 构造KEX_INIT包
104
+ kex_packet = build_kex_init_packet
105
+ send_packet(kex_packet)
106
+
107
+ # 接收服务器的KEX_INIT
108
+ server_packet = recv_packet
109
+ raise "未收到KEX_INIT" unless server_packet
110
+
111
+ # 解析服务器的算法列表
112
+ parse_server_kex(server_packet)
113
+
114
+ puts "✅ 密钥交换初始化完成"
115
+ end
116
+
117
+ def build_kex_init_packet
118
+ # SSH KEX_INIT 包结构:
119
+ # byte SSH_MSG_KEXINIT (20)
120
+ # byte[16] cookie (随机)
121
+ # name-list kex_algorithms
122
+ # name-list server_host_key_algorithms
123
+ # name-list encryption_algorithms_client_to_server
124
+ # name-list encryption_algorithms_server_to_client
125
+ # name-list mac_algorithms_client_to_server
126
+ # name-list mac_algorithms_server_to_client
127
+ # name-list compression_algorithms_client_to_server
128
+ # name-list compression_algorithms_server_to_client
129
+ # name-list languages_client_to_server (空)
130
+ # name-list languages_server_to_client (空)
131
+ # boolean first_kex_packet_follows (false)
132
+ # uint32 0 (保留)
133
+
134
+ payload = [SSH_MSG_KEXINIT].pack("C") # 消息类型
135
+ payload += SecureRandom.random_bytes(16) if defined?(SecureRandom) # cookie
136
+ payload += "0" * 16 unless defined?(SecureRandom) # 简化cookie
137
+
138
+ # 添加算法列表(name-list格式: uint32长度 + 逗号分隔字符串)
139
+ payload += encode_name_list(ALGORITHMS[:kex])
140
+ payload += encode_name_list(ALGORITHMS[:host_key])
141
+ payload += encode_name_list(ALGORITHMS[:cipher])
142
+ payload += encode_name_list(ALGORITHMS[:cipher]) # 服务器到客户端
143
+ payload += encode_name_list(ALGORITHMS[:mac])
144
+ payload += encode_name_list(ALGORITHMS[:mac])
145
+ payload += encode_name_list(ALGORITHMS[:compression])
146
+ payload += encode_name_list(ALGORITHMS[:compression])
147
+ payload += encode_name_list([]) # languages
148
+ payload += encode_name_list([])
149
+ payload += [0].pack("C") # first_kex_packet_follows = false
150
+ payload += [0].pack("N") # 保留
151
+
152
+ wrap_packet(payload)
153
+ end
154
+
155
+ def encode_name_list(list)
156
+ str = list.join(",")
157
+ [str.length].pack("N") + str
158
+ end
159
+
160
+ def wrap_packet(payload)
161
+ # SSH包格式: uint32长度 + byte填充长度 + payload + 随机填充
162
+ block_size = 8
163
+ min_padding = 4
164
+
165
+ payload_len = payload.length
166
+ padding_len = block_size - ((payload_len + 5) % block_size)
167
+ padding_len += block_size if padding_len < min_padding
168
+
169
+ packet_len = payload_len + padding_len + 1
170
+ packet = [packet_len].pack("N")
171
+ packet += [padding_len].pack("C")
172
+ packet += payload
173
+ packet += "\x00" * padding_len # 简化填充
174
+
175
+ packet
176
+ end
177
+
178
+ def send_packet(data)
179
+ @socket.write(data)
180
+ end
181
+
182
+ def recv_packet
183
+ # 读取包长度
184
+ len_bytes = @socket.read(4)
185
+ return nil unless len_bytes && len_bytes.length == 4
186
+
187
+ packet_len = len_bytes.unpack1("N")
188
+ return nil if packet_len > 35000 # 安全检查
189
+
190
+ # 读取剩余数据
191
+ packet = @socket.read(packet_len)
192
+ return nil unless packet && packet.length == packet_len
193
+
194
+ # 解析: padding_len(1) + payload + padding
195
+ padding_len = packet[0].unpack1("C")
196
+ payload = packet[1..-(padding_len+1)]
197
+
198
+ payload
199
+ end
200
+
201
+ def parse_server_kex(payload)
202
+ return if payload.nil? || payload.empty?
203
+
204
+ msg_type = payload[0].unpack1("C")
205
+ return unless msg_type == SSH_MSG_KEXINIT
206
+
207
+ # 跳过cookie(16字节),开始解析name-lists
208
+ offset = 17
209
+
210
+ # 辅助方法:读取name-list
211
+ read_list = ->() {
212
+ return nil if offset + 4 > payload.length
213
+ len = payload[offset, 4].unpack1("N")
214
+ offset += 4
215
+ return nil if offset + len > payload.length
216
+ str = payload[offset, len]
217
+ offset += len
218
+ str.split(",")
219
+ }
220
+
221
+ @results[:server_kex] = read_list.call
222
+ @results[:server_host_key] = read_list.call
223
+ @results[:server_cipher_c2s] = read_list.call
224
+ @results[:server_cipher_s2c] = read_list.call
225
+ @results[:server_mac_c2s] = read_list.call
226
+ @results[:server_mac_s2c] = read_list.call
227
+ @results[:server_compress_c2s] = read_list.call
228
+ @results[:server_compress_s2c] = read_list.call
229
+
230
+ # 计算协商结果(取交集的第一个)
231
+ @results[:negotiated_kex] = negotiate(ALGORITHMS[:kex], @results[:server_kex])
232
+ @results[:negotiated_cipher] = negotiate(ALGORITHMS[:cipher], @results[:server_cipher_c2s])
233
+ @results[:negotiated_mac] = negotiate(ALGORITHMS[:mac], @results[:server_mac_c2s])
234
+ end
235
+
236
+ def negotiate(client_prefs, server_list)
237
+ return nil unless server_list
238
+ server_set = server_list.to_set
239
+ client_prefs.find { |alg| server_set.include?(alg) }
240
+ end
241
+
242
+ def analyze_security
243
+ puts "\n🔐 安全分析..."
244
+
245
+ @results[:security_issues] = []
246
+
247
+ # 检查弱算法
248
+ weak_algorithms = {
249
+ "diffie-hellman-group1-sha1" => "使用SHA1的DH组1(非常弱)",
250
+ "diffie-hellman-group14-sha1" => "使用SHA1的DH组14",
251
+ "3des-cbc" => "3DES加密(慢且不安全)",
252
+ "blowfish-cbc" => "Blowfish(已废弃)",
253
+ "cast128-cbc" => "CAST128(已废弃)",
254
+ "arcfour" => "RC4(严重不安全)",
255
+ "hmac-md5" => "MD5哈希(不安全)",
256
+ "hmac-sha1-96" => "截断的SHA1"
257
+ }
258
+
259
+ [@results[:negotiated_kex], @results[:negotiated_cipher], @results[:negotiated_mac]].compact.each do |alg|
260
+ if weak_algorithms[alg]
261
+ @results[:security_issues] << "⚠️ #{alg}: #{weak_algorithms[alg]}"
262
+ end
263
+ end
264
+
265
+ if @results[:security_issues].empty?
266
+ puts "✅ 未检测到明显的弱算法"
267
+ else
268
+ @results[:security_issues].each { |issue| puts " #{issue}" }
269
+ end
270
+ end
271
+
272
+ def display_report
273
+ puts "\n" + "=" * 70
274
+ puts "📊 SSH协议检测报告"
275
+ puts "=" * 70
276
+
277
+ puts "\n🌐 基本信息:"
278
+ puts " 目标: #{@host}:#{@port}"
279
+ puts " 协议版本: SSH-#{@results[:ssh_version]}"
280
+ puts " 软件版本: #{@results[:software]}"
281
+
282
+ puts "\n🔑 密钥交换算法 (KEX):"
283
+ puts " 协商结果: #{@results[:negotiated_kex] || 'N/A'}"
284
+ puts " 服务器支持 (#{@results[:server_kex]&.length || 0}种):"
285
+ display_list(@results[:server_kex], 5)
286
+
287
+ puts "\n🔐 加密算法 (Cipher):"
288
+ puts " 协商结果: #{@results[:negotiated_cipher] || 'N/A'}"
289
+ puts " 服务器支持 (#{@results[:server_cipher_c2s]&.length || 0}种):"
290
+ display_list(@results[:server_cipher_c2s], 5)
291
+
292
+ puts "\n📝 MAC算法:"
293
+ puts " 协商结果: #{@results[:negotiated_mac] || 'N/A'}"
294
+ puts " 服务器支持 (#{@results[:server_mac_c2s]&.length || 0}种):"
295
+ display_list(@results[:server_mac_c2s], 5)
296
+
297
+ puts "\n📦 压缩算法:"
298
+ puts " 服务器支持: #{@results[:server_compress_c2s]&.join(', ') || 'N/A'}"
299
+
300
+ puts "\n💡 安全建议:"
301
+ if @results[:security_issues]&.any?
302
+ puts " 发现 #{@results[:security_issues].length} 个安全问题,建议升级服务器配置"
303
+ else
304
+ puts " ✅ 算法配置良好"
305
+ end
306
+
307
+ puts "\n📋 协议栈总结:"
308
+ puts " SSH-TRANS (传输层): 版本 #{@results[:ssh_version]}"
309
+ puts " ├── 密钥交换: #{@results[:negotiated_kex]}"
310
+ puts " ├── 加密: #{@results[:negotiated_cipher]}"
311
+ puts " ├── 完整性: #{@results[:negotiated_mac]}"
312
+ puts " └── 压缩: #{@results[:server_compress_c2s]&.first || 'none'}"
313
+ end
314
+
315
+ def display_list(list, per_line)
316
+ return unless list
317
+ list.each_slice(per_line) do |slice|
318
+ puts " • #{slice.join(' • ')}"
319
+ end
320
+ end
321
+ end
322
+
323
+ # 命令行入口
324
+ if __FILE__ == $0
325
+ if ARGV.empty?
326
+ puts "SSH协议检测器 - 原生Ruby实现"
327
+ puts "=" * 50
328
+ puts "用法: ruby ssh_native_detector.rb <主机> [端口]"
329
+ puts ""
330
+ puts "示例:"
331
+ puts " ruby ssh_native_detector.rb github.com"
332
+ puts " ruby ssh_native_detector.rb 192.168.1.100 2222"
333
+ puts ""
334
+ puts "功能:"
335
+ puts " • 检测SSH协议版本和软件版本"
336
+ puts " • 获取服务器支持的所有算法"
337
+ puts " • 分析密钥交换、加密、MAC算法"
338
+ puts " • 识别弱算法和安全风险"
339
+ puts " • 无需外部依赖(纯Ruby实现)"
340
+ exit 1
341
+ end
342
+
343
+ host = ARGV[0]
344
+ port = (ARGV[1] || 22).to_i
345
+
346
+ detector = SSHNativeDetector.new(host, port)
347
+ detector.detect
348
+ end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: network-utility
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.62
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt
8
- bindir: bin
8
+ bindir: application
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -41,13 +41,15 @@ description: network-core
41
41
  email:
42
42
  - matthrewchains@gmail.com
43
43
  - 18995691365@189.cn
44
- executables: []
44
+ executables:
45
+ - ssh-detect
45
46
  extensions: []
46
47
  extra_rdoc_files: []
47
48
  files:
48
49
  - GEMFILE
49
50
  - LICENSE
50
51
  - README.md
52
+ - application/ssh-detect
51
53
  - document/acl-CR16010H-F.md
52
54
  - document/acl-CR16018-F.md
53
55
  - document/acl-CR19000-20.md
@@ -272,7 +274,10 @@ files:
272
274
  - document/vpn-ZXCTN9000-18EA.md
273
275
  - document/vpn-ZXCTN9000-8EA.md
274
276
  - network.rb
275
- - support/snmp.rb
277
+ - support/snmp/snmp.rb
278
+ - support/ssh/2310/detector/ssh_detector.rb
279
+ - support/ssh/2310/detector/ssh_detector_final.rb
280
+ - support/ssh/2310/detector/ssh_native_detector.rb
276
281
  - utility/asnum.rb
277
282
  - utility/ipv4_address.rb
278
283
  - utility/ipv6_address.rb
@@ -298,7 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
298
303
  - !ruby/object:Gem::Version
299
304
  version: '0'
300
305
  requirements: []
301
- rubygems_version: 4.0.8
306
+ rubygems_version: 4.0.10
302
307
  specification_version: 4
303
308
  summary: core
304
309
  test_files: []
File without changes