diff --git a/fakeftp.py b/fakeftp.py index 45331c1..17d9a97 100644 --- a/fakeftp.py +++ b/fakeftp.py @@ -2,16 +2,169 @@ import SocketServer import threading import socket from random import randint +import datetime + +def mask(rights): + m = "" + if rights&4 != 0: + m += "r" + else: + m += "-" + + if rights&2 != 0: + m += "w" + else: + m += "-" + + if rights&1 != 0: + m += "x" + else: + m+= "-" + + return m + +class FSNode: + def __init__(self,name,owner="nobody",group="nobody",rights=384,size=0, iun=1): + self.name = name + self.owner = owner + self.group = group + self.rights = rights + self.size = size + self.parent = None + self.iun = iun + + def is_dir(self): + return False + + def is_file(self): + return False + + def get_name(self): + return self.name + + def get_size(self): + return self.size + + def get_rights(self): + return self.rights + + def get_last_change(self): + return datetime.date(2013,8,22) + + #Thanks to a mumble session with lod :3 + def get_boner(self): + return "large" + + def get_parent_directory(self): + return self.parent + + def get_owner(self): + return self.owner + + def get_group(self): + return self.group + + def get_imaginary_unicorn_number(self): + return self.iun + + def get_mask(self): + m = "" + if self.is_dir(): + m += "d" + else: + m += "-" + + rights = self.get_rights() + m += mask((rights >> 6)&7) + m += mask((rights >> 3)&7) + m += mask(rights&7) + + return m + + def get_absolute_path(self): + if self.parent is None: + if self.is_dir(): + return "/" + else: + return self.get_name() + else: + p = self.parent.get_absolute_path() + if p[-1] != "/": + p+= "/" + p+=self.get_name() + return p + +class Directory(FSNode): + def __init__(self,name,owner="nobody",group="nobody",rights=384,size=4096,files=[]): + FSNode.__init__(self, name=name, owner=owner, group=group,rights=rights, size=size, iun=randint(2,10)) + + self.files = files + + for f in self.files: + f.parent = self + + def is_dir(self): + return True + + def get_node(self,name): + for f in self.files: + if f.name == name: + return f + return None + +class File(FSNode): + def __init__(self,name,owner="nobody",group="nobody",rights=384,size=4096, content=None): + FSNode.__init__(self, name=name, owner=owner, group=group,rights=rights, size=size, iun=1) + if content is None: + content = TextFileContent() + self.content = content + + def is_file(self): + return True + + def send_content(self,socket): + self.content.send_content(socket) + +class FileContent: + def send_content(self, socket): + pass + +class TextFileContent(FileContent): + def __init__(self, text=""): + self.text = text + + def send_content(self, socket): + socket.send(self.text) + +class FileFileContent(FileContent): + def __init__(self, filepath): + self.filepath = filepath + + def send_content(self, socket): + fh = file(self.filepath,"r") + buf = fh.read(1024) + while len(buf) > 0: + socket.send(buf) + buf = fh.read(1024) + +class HoneypotFileContent(FileContent): + def send_content(self, socket): + for i in range(4000): + socket.send('HONEYPOT'); + +class URandomFileContent(FileFileContent): + def __init__(self): + FileFileContent.__init__(self, "/dev/urandom") class DataConnection: def run(self, dchandler): pass - class PassiveDataConnection(DataConnection): def __init__(self, port): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.bind(("",port)) + self.connection = None threading.Thread(target=self.waitForConnection).start() def waitForConnection(self): @@ -19,6 +172,8 @@ class PassiveDataConnection(DataConnection): self.connection, _ = self.socket.accept() def run(self, dchandler): + while self.connection is None: + pass threading.Thread(target=self.runHandler, args=(dchandler,) ).start() def runHandler(self, dchandler): @@ -26,9 +181,9 @@ class PassiveDataConnection(DataConnection): self.connection.close() class ActiveDataConnection(DataConnection): - def __init__(self, host, port): + def __init__(self, addr): self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - self.socket.connect( (host,port) ) + self.socket.connect( addr ) def run(self, dchandler): threading.Thread(target=self.runHandler, args=(dchandler,)).start() @@ -106,10 +261,94 @@ class EnteringPassiveModeMessage(FTPMessage): def __init__(self,addrtuple): FTPMessage.__init__(self,"227","Entering Passive Mode %s." % addrtuple) +class BeginDirectoryListingMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"150","Here comes the directory listing.") + +class EndDirectoryListingMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"226","Directory send OK.") + +class WorkingDirectoryMessage(FTPMessage): + def __init__(self, workingDirectory): + FTPMessage.__init__(self,"257","\"%s\" is the current directory" % workingDirectory) + +class ChangeDirectorySuccessfulMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"250","command successful") + +class ChangeDirectoryFailedMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"550","No such directory") + +class InvalidParametersMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"504","Command not implemented for that parameter") + +class InvalidPortCommandMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"500","Illegal port command") + +class CommandSuccessfulMessage(FTPMessage): + def __init__(self,cmd): + FTPMessage.__init__(self,"200","%s command successful" % cmd) + +class OperationNotPermittedMessage(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"550","Operation not permitted") + +class TypeSetMessage(FTPMessage): + def __init__(self,val): + FTPMessage.__init__(self,"200","Type set to %s" % val) + +class BeginFileRetrieve(FTPMessage): + def __init__(self,node): + FTPMessage.__init__(self,"150","Opening ASCII mode data connection for %s (%d bytes)" % (node.get_name(),node.get_size())) + +class EndFileRetrieve(FTPMessage): + def __init__(self): + FTPMessage.__init__(self,"226","Transfer complete") + # Data Connection Handler -class HeloHandler: +class NListHandler: + def __init__(self, directory, handler): + self.directory = directory + self.handler = handler + def handle(self, socket): - socket.send("Hello World") + for f in self.directory.files: + socket.send(f.get_name()+"\n") + +class ListHandler: + def __init__(self, directory, handler): + self.directory = directory + self.handler = handler + + def handle(self, socket): + self.handler.send( BeginDirectoryListingMessage() ) + for f in self.directory.files: + socket.send( "%10s %3d %-8s %-8s %8d %3s %2d %5d %s\n" % ( + f.get_mask(), + f.get_imaginary_unicorn_number(), + f.get_owner(), + f.get_group(), + f.get_size(), + f.get_last_change().strftime("%b"), + f.get_last_change().day, + f.get_last_change().year, + f.get_name() + ) ) + self.handler.send( EndDirectoryListingMessage() ) + +class RetrieveHandler: + def __init__(self, node, handler): + self.node = node + self.handler = handler + + def handle(self, socket): + self.handler.send( BeginFileRetrieve(self.node) ) + self.node.send_content(socket) + self.handler.send( EndFileRetrieve() ) # State classes class State: @@ -160,12 +399,70 @@ class AuthorizedState(MainState): port = handler.createPassiveDataConnection() handler.debug("Created passive data connection") handler.send( EnteringPassiveModeMessage( self.addrtuple("127.0.0.1",port) ) ) - elif message.cmd == "HELO": - handler.debug("Running Helo on data connection") - if handler.runDataConnection(HeloHandler()): + elif message.cmd in ["DELE","RMD","RNFR","RNTO","STOR"]: + handler.send( OperationNotPermittedMessage() ) + elif message.cmd == "RETR": + node = handler.get_node(message.parameter) + if node.is_file(): + handler.runDataConnection( RetrieveHandler(node, handler) ) + else: + handler.send( OperationNotPermittedMessage() ) + elif message.cmd == "PORT": + hp = message.parameter.split(",") + if len(hp) != 6: + handler.debug( "Invalid port information" ) + handler.send( InvalidParametersMessage() ) + return + + if int(hp[0]) == 10 or int(hp[0]) == 127 or int(hp[0]) == 192: #Prevent local scanning + handler.send( InvalidPortCommandMessage() ) + return + + addr = ( ".".join(hp[:4]), int(hp[4])<<8+int(hp[5]) ) + + handler.createActiveDataConnection(addr) + + handler.debug("Created active data connection to %s:%d" % addr) + + handler.send( CommandSuccessfulMessage("PORT") ) + elif message.cmd == "LIST": + handler.debug("Listing current directory") + if handler.runDataConnection(ListHandler(handler.currentDirectory, handler)): handler.debug("successful") else: handler.debug("failed") + elif message.cmd == "NLST": + handler.debug("Listing current directory (simple)") + if handler.runDataConnection(NListHandler(handler.currentDirectory)): + handler.debug("successful") + else: + handler.debug("failed") + elif message.cmd == "CWD": + node = handler.get_node(message.parameter) + if node is None or not node.is_dir(): + handler.debug("Error trying to switch into non-existent directory") + handler.send( ChangeDirectoryFailedMessage() ) + else: + handler.currentDirectory = node + handler.debug("Changed working directory to "+node.get_absolute_path()) + handler.send( ChangeDirectorySuccessfulMessage() ) + elif message.cmd == "CDUP": + node = handler.currentDirectory.get_parent_directory() + if node is None or not node.is_dir(): + handler.debug("Error trying to switch to parent directory") + else: + handler.debug("Changed working directory to "+node.get_absolute_path()) + handler.currentDirectory = node + handler.send( ChangeDirectorySuccessfulMessage() ) + elif message.cmd == "PWD": + handler.send( WorkingDirectoryMessage(handler.currentDirectory.get_absolute_path()) ) + elif message.cmd == "TYPE": + if message.parameter == "A": + handler.send( TypeSetMessage(message.parameter) ) + else: + handler.send( InvalidCommandMessage("TYPE") ) + elif message.cmd == "NOOP": + handler.send( CommandSuccessfulMessage("NOOP") ) else: MainState.process(self, handler, message) @@ -181,7 +478,7 @@ class LineReader: while pos != -1: line = self.buf[:pos] if line[-1] == "\r": - line = line[:-2] + line = line[:-1] yield line self.buf = self.buf[pos+1:] pos = self.buf.find("\n") @@ -191,15 +488,39 @@ class FTPHandler(SocketServer.BaseRequestHandler): print ("[%s] " % self.client_address[0]) + text def send(self, message): - self.request.send( str(message) ) + line = str(message) + self.debug("< "+line) + self.request.send( line ) def createPassiveDataConnection(self): port = randint(60000,63000) self.connection = PassiveDataConnection(port) return port - def createActiveDataConnection(self,port): - self.connection = ActiveDataConnection(self.client_address[0],port) + def createActiveDataConnection(self,addr): + self.connection = ActiveDataConnection(addr) + + def get_node(self,path): + if path == "/": + return self.rootDirectory + + if path[0] == "/": + cnode = self.rootDirectory + path = path[1:] + else: + cnode = self.currentDirectory + + if path[-1] == "/": + path = path[:-1] + + parts = path.split("/") + for part in parts: + if cnode == None or not cnode.is_dir(): + return None + + cnode = cnode.get_node(part) + + return cnode def runDataConnection(self, dchandler): if self.connection is None: @@ -210,6 +531,8 @@ class FTPHandler(SocketServer.BaseRequestHandler): return True def handle(self): + self.currentDirectory = self.rootDirectory + self.connection = None self.state = NonAuthorizedState() @@ -230,6 +553,7 @@ class FTPHandler(SocketServer.BaseRequestHandler): linereader.push(data) for line in linereader.get_lines(): + self.debug("> "+line) message = FTPMessage.parse(line) self.state.process(self,message) @@ -238,6 +562,63 @@ class FTPHandler(SocketServer.BaseRequestHandler): handler = FTPHandler handler.authorizer = AnonymousAuthorizer() -server = SocketServer.ThreadingTCPServer(("",1234),handler) +recdir = Directory("recursive",files=[ + File("is") +]) +recdir.files.append(recdir) -server.serve_forever() \ No newline at end of file +handler.rootDirectory = Directory("",files=[ + Directory("etc", files=[ + File("passwd",content=URandomFileContent()), + File("shadow",content=URandomFileContent()), + recdir + ]), + Directory("usr", files=[ + Directory("share"), + Directory("bin", files=[ + File("sh") + ]) + ]), + Directory("home", files=[ + Directory("john",owner="john"), + Directory("jane",owner="jane"), + Directory("peter",owner="peter") + ]), + Directory("root", files=[ + Directory("ssh",files=[ + File("authorized_keys",content=URandomFileContent()) + ]), + File("db.dump", content=URandomFileContent()) + ]), + Directory("dev"), + Directory("mnt"), + Directory("var", files=[ + Directory("www") + ]), + Directory("bin"), + Directory("tmp"), + Directory("lost+found"), + Directory("boot"), + Directory("lib"), + Directory("lib32"), + Directory("proc"), + Directory("src"), + File("vmlinuz",size=4000000), + Directory("selinux"), + Directory("opt"), + File("initrd.img",size=4000000) +]) + +port = 1234 +while True: + try: + server = SocketServer.ThreadingTCPServer(("",port),handler) + print "Listening on port %d" % port + server.serve_forever() + break + except Exception: + if port < 1500: + port += 1 + else: + print "Failed to find a port" + break \ No newline at end of file