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 +4 -4
- data/application/ssh-detect +30 -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 +10 -5
- /data/support/{snmp.rb → snmp/snmp.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8d6c9de02a10fbfb742fe2ec10d5b98fab8ce8398f635012c53ccb5a3e12a58
|
|
4
|
+
data.tar.gz: d869326b07a6023b9bbecd4ea14866e6e92201ac566d100a45d4eaa6fe7b9ce4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
'
|
|
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
|
|
@@ -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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt
|
|
8
|
-
bindir:
|
|
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.
|
|
306
|
+
rubygems_version: 4.0.10
|
|
302
307
|
specification_version: 4
|
|
303
308
|
summary: core
|
|
304
309
|
test_files: []
|
|
File without changes
|