Last active
May 29, 2019 08:02
-
-
Save RaidAndFade/97d15ee213bb7ed33f1cb2128ca894b6 to your computer and use it in GitHub Desktop.
a MITM SSH server that is designed for use as a honeypot (can take your hostkey so that there is no problem, is multithreaded, forwards valid login attempts to the endpoint server)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import socket, sys, threading | |
import paramiko | |
import pwd | |
import base64 | |
from paramiko.py3compat import b, u, decodebytes | |
from binascii import hexlify | |
#generate keys with 'ssh-keygen -t rsa -f server.key' | |
HOST_KEY_RSA = paramiko.RSAKey(filename='rsa.key') | |
#HOST_KEY_ECDSA = paramiko.ECDSAKey(filename='ecdsa.key') | |
SSH_PORT = 2222 | |
LOGFILE = 'logins.txt' #File to log the user:password combinations to | |
LOGFILE_LOCK = threading.Lock() | |
SERVER_KEY = "" | |
REMOTE_HOST = "" | |
REMOTE_PORT = 0 | |
class SSHServerHandler (paramiko.ServerInterface): | |
def __init__(self): | |
self.event = threading.Event() | |
self.username = "" | |
self.valid_types = "password" | |
self.prxclient = paramiko.Transport((REMOTE_HOST, REMOTE_PORT)) | |
self.prxclient.start_client() | |
assert self.prxclient.get_remote_server_key().get_base64() == SERVER_KEY | |
self.failcount = 0 | |
self.authtypes = None | |
self.hasAgent = False | |
self.sftp = None | |
def check_channel_request(self, kind, chanid): | |
print(kind) | |
if kind == 'session': | |
return paramiko.OPEN_SUCCEEDED | |
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED | |
def check_channel_shell_request(self, channel): | |
self.event.set() | |
self.mode="SHELL" | |
return True | |
def check_auth_password(self, username, password): | |
self.username = username | |
self.password = password | |
LOGFILE_LOCK.acquire() | |
try: | |
logfile_handle = open(LOGFILE,"a") | |
print("New login: " + username + ":" + password) | |
logfile_handle.write(username + ":" + password + "\n") | |
logfile_handle.close() | |
finally: | |
LOGFILE_LOCK.release() | |
try: | |
self.prxclient.auth_password(username,password) | |
return paramiko.AUTH_SUCCESSFUL | |
except paramiko.ssh_exception.BadAuthenticationType as bat: | |
self.authtypes = bat.allowed_types | |
print("User has pubkey enabled on remote, ignoring.") | |
except paramiko.ssh_exception.AuthenticationException: | |
pass # this error is basically "password wrong!!" | |
except Exception: | |
import traceback | |
traceback.print_exc() | |
if self.failcount >= 2: | |
self.prxclient.close() | |
self.prxclient = None | |
print("Failed 3 times, letting into honeypot. deleting prxclient") | |
return paramiko.AUTH_SUCCESSFUL | |
else: | |
self.failcount+=1 | |
print(f"Failed {self.failcount} times, trying again") | |
return paramiko.AUTH_PARTIALLY_SUCCESSFUL | |
def check_channel_forward_agent_request(self, channel): | |
print("FA "+str(channel)) | |
self.hasAgent = True | |
return True | |
def get_allowed_auths(self, username): | |
return self.valid_types | |
def check_channel_pty_request( | |
self, channel, term, width, height, pixelwidth, pixelheight, | |
modes): | |
return True | |
class SFTPProxyFileHandle (paramiko.SFTPHandle): | |
def __init__(self, file: paramiko.SFTPFile, flags = 0): | |
paramiko.SFTPHandle.__init__(self, flags) | |
self.file = file | |
def chattr(self, attr: paramiko.SFTPAttributes): | |
if attr.st_mode: | |
self.file.chmod(attr.st_mode) | |
if attr.st_uid and attr.st_gid: | |
self.file.chown(attr.st_uid, attr.st_gid) | |
elif attr.st_uid or attr.st_gid: | |
return paramiko.SFTP_OP_UNSUPPORTED | |
def close(self): | |
self.file.close() | |
def read(self, offset, length): | |
self.file.seek(offset) | |
return self.file.read(length) | |
def stat(self): | |
return self.file.stat() | |
def write(self, offset, data): | |
self.file.seek(offset) | |
self.file.write(data) | |
return paramiko.SFTP_OK | |
class SFTPProxy (paramiko.SFTPServerInterface): | |
proxy: paramiko.SFTPClient | |
def __init__(self, server, transport, handler): | |
self.server = server | |
self.transport = transport | |
self.handler = handler | |
self.handler.event.set() | |
self.handler.mode="SFTP" | |
self.handler.sftp=self | |
self.ready = threading.Event() | |
self.proxy = None | |
self.ready.wait() | |
def list_folder(self, path): | |
if self.proxy is None: return None | |
return self.proxy.listdir_attr(path) | |
def lstat(self, path): | |
if self.proxy is None: return None | |
return self.proxy.lstat(path) | |
def mkdir(self, path, attr: paramiko.SFTPAttributes): | |
if self.proxy is None: return None | |
#TODO attr has more things, like gid, uid, etc | |
try: | |
self.proxy.mkdir(path, attr.st_mode) | |
if attr.st_uid and attr.st_gid: | |
self.proxy.chown(path, attr.st_uid, attr.st_gid) | |
return paramiko.SFTP_OK | |
except Exception as e: | |
import traceback | |
traceback.print_exc() | |
return paramiko.SFTP_FAILURE | |
def rmdir(self, path): | |
if self.proxy is None: return None | |
try: | |
self.proxy.rmdir(path) | |
return paramiko.SFTP_OK | |
except Exception as e: | |
import traceback | |
traceback.print_exc() | |
return paramiko.SFTP_FAILURE | |
def open(self, path, flags, attr): | |
if self.proxy is None: return None | |
import os | |
mode = "" | |
if flags & os.O_RDONLY > 0: | |
mode = "r" | |
elif flags & os.O_WRONLY > 0: | |
if flags & os.O_APPEND > 0: | |
mode = "a" | |
else: | |
mode = "w" | |
else: | |
if flags & os.O_TRUNC > 0: | |
if flags & os.O_APPEND > 0: | |
mode = "a+" | |
else: | |
mode = "w+" | |
else: | |
mode = "r+" | |
if flags & os.O_EXCL > 0: | |
mode = "x"+mode | |
print("opening file @ "+path+" ("+mode+")") | |
try: | |
h = SFTPProxyFileHandle(self.proxy.open(path,mode)) | |
return h | |
except Exception as e: | |
import traceback | |
traceback.print_exc() | |
print(str(e)) | |
return paramiko.SFTP_FAILURE | |
def remove(self, path): | |
try: | |
self.proxy.remove(path) | |
return paramiko.SFTP_OK | |
except Exception as e: | |
import traceback | |
traceback.print_exc() | |
print(str(e)) | |
return paramiko.SFTP_FAILURE | |
def posix_rename(self, opath, npath): | |
if self.proxy is None: return None | |
try: | |
self.proxy.posix_rename(opath,npath) | |
return paramiko.sftp.SFTP_OK | |
except: | |
# not a good error | |
return paramiko.sftp.SFTP_PERMISSION_DENIED | |
def readlink(self, path): | |
if self.proxy is None: return None | |
return self.proxy.readlink(path) | |
def stat(self, path): | |
if self.proxy is None: return None | |
return self.proxy.stat(path) | |
def symlink(self, tp, p): | |
if self.proxy is None: return None | |
self.proxy.symlink(tp,p) | |
return paramiko.sftp.SFTP_OK | |
def process_cmd(cmd,handler): | |
return "Unknown Command: "+cmd | |
def handleConnection(client): | |
transport = paramiko.Transport(client) | |
transport.add_server_key(HOST_KEY_RSA) | |
transport.load_server_moduli() | |
handler = SSHServerHandler() | |
transport.set_subsystem_handler("sftp", paramiko.SFTPServer, SFTPProxy, transport, handler) | |
transport.start_server(server=handler) | |
channel = transport.accept(None) | |
if channel is not None: | |
print("Connection succeeded") | |
handler.event.wait(10) | |
if not handler.event.isSet(): | |
print("Client never asked for shell or sftp") | |
channel.close() | |
return | |
print("Shell connected") | |
if handler.mode == "SFTP": | |
if handler.prxclient is not None: | |
sftp = paramiko.SFTPClient.from_transport(handler.prxclient) | |
handler.sftp.proxy = sftp | |
handler.sftp.ready.set() | |
import time | |
while True: | |
time.sleep(1) | |
return | |
if handler.prxclient is not None: # connected?!? | |
print("hooked to server") | |
prxchnl = handler.prxclient.open_session() | |
prxchnl.get_pty() | |
prxchnl.invoke_shell() | |
else: | |
if handler.hasAgent: | |
ree = paramiko.agent.AgentServerProxy(transport) | |
ree.connect() | |
print(ree.get_keys()) | |
print("no proxy.") | |
prxchnl = None | |
if prxchnl is not None: | |
print("Proxying to server") | |
import select | |
while True: | |
r, w, x = select.select([prxchnl, channel], [], []) | |
if channel in r: | |
data = channel.recv(1024) | |
if len(data) == 0: | |
break | |
prxchnl.send(data) | |
if prxchnl in r: | |
data = prxchnl.recv(1024) | |
if len(data) == 0: | |
break | |
channel.send(data) | |
else: | |
print("Proxy failed, honeypotting") | |
with open("motd","r") as motdf: | |
motdtxt = motdf.read() | |
if handler.authtypes != None: | |
motdtxt = motdtxt.replace("[POTINDC]","Some realities are false, your falsities are real.") | |
else: | |
motdtxt = motdtxt.replace("[POTINDC]","Some realities are false, some falsities are real.") | |
channel.send(motdtxt.replace("\n","\n\r")) | |
channel.send("\r\n") | |
f = channel.makefile('rU') | |
while True: | |
channel.send('[%s@entry ~]$ ' % handler.username) | |
cmd = '' | |
while True: | |
c = f.read(1) | |
print(c) | |
if c == b"\x04": # CTRL+D | |
channel.send("^"+chr(ord('A')+c[0]-1)) | |
cmd="exit" | |
break | |
if c == b"\x7f": | |
cmd=cmd[:-1] | |
channel.send("\x1b[D\x1b[0K") # go to prev char, clear everything to right of cursor | |
continue | |
if c == b"\x1b": | |
c = f.read(1) | |
while c not in [b'A',b'B',b'C',b'D',b'M',"K"]: | |
c = f.read(1) | |
continue | |
if c == b"\n" or c == b"\r": | |
break | |
channel.send(c) | |
cmd += c.decode() | |
res = process_cmd(cmd,handler) | |
channel.send("\r\n") | |
channel.send(res.replace("\n","\r\n")) | |
channel.send("\r\n") | |
if cmd == "exit": | |
break | |
channel.close() | |
if __name__ == "__main__": | |
try: | |
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
server_socket.bind(('', SSH_PORT)) | |
server_socket.listen(100) | |
paramiko.util.log_to_file('paramiko.log') | |
while(True): | |
try: | |
client_socket, client_addr = server_socket.accept() | |
print("!!!") | |
threading.Thread(target=handleConnection,args=(client_socket,)).start() | |
except Exception as e: | |
print("ERROR: Client handling") | |
print(e) | |
except Exception as e: | |
print("ERROR: Failed to create socket") | |
print(e) | |
sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment