network-utility 1.1.61 → 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 +4 -4
- data/application/ssh-detect +30 -0
- data/document/acl-CR16010H-F.md +40 -0
- data/document/acl-CR16018-F.md +40 -0
- data/document/acl-CR19000-20.md +40 -0
- data/document/acl-CRS-16.md +40 -0
- data/document/acl-M6000-16E.md +55 -4
- data/document/acl-M6000-18S.md +55 -4
- data/document/acl-M6000-8.md +55 -4
- data/document/acl-M6000-8E.md +55 -4
- data/document/acl-NE5000E-20.md +17 -1
- data/document/acl-NE5000E-X16.md +17 -1
- data/document/acl-NE5000E-X16A.md +17 -1
- data/document/acl-T8000-18.md +55 -4
- data/document/telnet.md +11 -0
- data/network.rb +5 -2
- data/support/ssh/2310/detector/ssh_detector.rb +259 -0
- data/support/ssh/2310/detector/ssh_detector_final.rb +273 -0
- data/support/ssh/2310/detector/ssh_native_detector.rb +348 -0
- metadata +14 -5
- /data/support/{snmp.rb → snmp/snmp.rb} +0 -0
data/document/telnet.md
CHANGED
|
@@ -209,6 +209,17 @@ module NE5000E_20
|
|
|
209
209
|
end
|
|
210
210
|
```
|
|
211
211
|
|
|
212
|
+
```ruby
|
|
213
|
+
@sign << ['VNE9000', 'telnet']
|
|
214
|
+
|
|
215
|
+
module VNE9000
|
|
216
|
+
def setting
|
|
217
|
+
@selectors = {" ---- More ----"=>' ',"Are you sure to display some information?(Y/N)[Y]:"=>'y'}
|
|
218
|
+
@erasers << " ---- More ----" << "[16D [16D" << " \x1b[1D" << "[16D [16D"
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
```
|
|
222
|
+
|
|
212
223
|
```ruby
|
|
213
224
|
@sign << ['NE8000E-X8', 'telnet']
|
|
214
225
|
|
data/network.rb
CHANGED
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
].each{|mod|require "utility/#{mod}"}
|
|
12
12
|
|
|
13
13
|
[
|
|
14
|
-
'
|
|
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 = '
|
|
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
|