Skip to content

Instantly share code, notes, and snippets.

@RaidAndFade
Last active May 29, 2019 08:02
Show Gist options
  • Save RaidAndFade/97d15ee213bb7ed33f1cb2128ca894b6 to your computer and use it in GitHub Desktop.
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)
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