| skipped 29 lines |
30 | 30 | | import socketserver |
31 | 31 | | from os.path import isfile |
32 | 32 | | import argparse |
33 | | - | import multiprocessing |
| 33 | + | from concurrent.futures import ProcessPoolExecutor |
34 | 34 | | from csv import DictReader |
| 35 | + | import asyncio |
| 36 | + | from typing import List, OrderedDict, ByteString |
35 | 37 | | except Exception as e: |
36 | 38 | | print("Caught exception: {}\nAre you running with python3?".format(e)) |
37 | 39 | | exit(1) |
38 | 40 | | |
39 | 41 | | |
40 | 42 | | _PORT_ = 4521 |
41 | | - | _IP_ = '0.0.0.0' |
| 43 | + | _IP_ = "0.0.0.0" |
42 | 44 | | _SEARCHSPLOIT_ = "/usr/share/exploitdb/files_exploits.csv" |
43 | 45 | | |
44 | | - | class SearchHandler(socketserver.StreamRequestHandler): |
45 | | - | def handle(self): |
46 | | - | try: |
47 | | - | print('[+] Connection from '+ self.client_address[0]) |
48 | | - | self.pool = multiprocessing.Pool(10) |
49 | | - | for output in self.pool.imap(SearchHandler.search, iter(self.rfile.readline, b'\n')): |
50 | | - | if output: |
51 | | - | print(output) |
52 | | - | self.wfile.write(output.encode() + b'\n') |
53 | 46 | | |
54 | | - | self.pool.close() |
55 | | - | print('[$] Closing connection from {}\n'.format(self.client_address[0])) |
56 | | - | self.pool.join() |
57 | | - | except Exception as e: |
58 | | - | self.pool.terminate() |
59 | | - | self.wfile.write('[-] Exception Caught: {}'.format(e).encode()) |
60 | | - | print("[-] Exception Caught: {}".format(e)) |
61 | | - | self.pool.join() |
| 47 | + | class SearchHandler(): |
| 48 | + | def __init__(self, database) -> None: |
| 49 | + | self.db = database |
62 | 50 | | |
63 | | - | @classmethod |
64 | | - | def search(cls, data): |
| 51 | + | async def handle(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter): |
| 52 | + | remote_addr = "{}:{}".format(*writer.get_extra_info('peername')) |
| 53 | + | print("[$] Connection from {}".format(remote_addr) ) |
| 54 | + | loop = asyncio.get_running_loop() |
| 55 | + | with ProcessPoolExecutor() as pool: |
| 56 | + | futures = [] |
| 57 | + | while True: # this is how they do it in the docs... smh |
| 58 | + | line = await reader.readline() |
| 59 | + | if not line: |
| 60 | + | break |
| 61 | + | print("[ ] checking line: '{!r}'".format(line)) |
| 62 | + | futures.append(loop.run_in_executor(pool, self.search, line)) |
| 63 | + | |
| 64 | + | if not futures: |
| 65 | + | print("[-] No data received from {}".format(remote_addr)) |
| 66 | + | |
| 67 | + | done,_ = await asyncio.wait(futures) |
| 68 | + | |
| 69 | + | for task in done: |
| 70 | + | exception = task.exception() |
| 71 | + | if exception: |
| 72 | + | print("[-] Error: {}".format(exception)) |
| 73 | + | writer.write("[-] Error: {}".format(exception).encode()) |
| 74 | + | else: |
| 75 | + | res = task.result() |
| 76 | + | if res: |
| 77 | + | print(res.decode()) |
| 78 | + | writer.write(res) |
| 79 | + | |
| 80 | + | print("[$] Closing connection from {}".format(remote_addr)) |
| 81 | + | await writer.drain() |
| 82 | + | writer.close() |
| 83 | + | |
| 84 | + | |
| 85 | + | def search(self, data: ByteString) -> ByteString: |
65 | 86 | | query = data.decode().strip().split(" ") |
66 | | - | query[-1] = query[-1][:3] #cut down on the last item which should be the version number |
| 87 | + | query[-1] = query[-1][:3] # cut down on the last item which should be the version number |
67 | 88 | | output = [] |
68 | | - | for rows in ExploitServer.exploitDatabase: |
| 89 | + | for rows in self.db: |
69 | 90 | | if all([term in rows["description"] for term in query]): |
70 | | - | output.append('\t'.join((rows["description"], rows["file"]))) |
| 91 | + | output.append("\t".join((rows["description"], rows["file"]))) |
71 | 92 | | if output: |
72 | | - | return "[ ] " + "\n".join([' '.join(query), *output]) |
| 93 | + | return ("[+] {}\n".format(data.decode().strip()) + "\n".join(output)).encode() |
| 94 | + | return b'' |
73 | 95 | | |
74 | 96 | | |
| 97 | + | async def start_server(ip, port, database): |
| 98 | + | handler = SearchHandler(database) |
| 99 | + | server = await asyncio.start_server(handler.handle, ip, port) |
| 100 | + | print("[+] Listening on {}:{}".format(ip, port)) |
75 | 101 | | |
76 | | - | class ExploitServer(socketserver.ThreadingMixIn, socketserver.TCPServer): |
77 | | - | exploitDatabase = [] |
| 102 | + | async with server: |
| 103 | + | await server.serve_forever() |
78 | 104 | | |
79 | 105 | | |
80 | 106 | | def main(): |
81 | | - | exploit = ExploitServer((_IP_, _PORT_), SearchHandler) |
82 | | - | print('[ ] Starting server on port ' + str(_PORT_)) |
83 | | - | try: |
84 | | - | exploit.serve_forever() |
85 | | - | except: |
86 | | - | print('[-] Caught exception. Shutting down.') |
87 | | - | exploit.shutdown() |
88 | | - | exploit.server_close() |
89 | | - | |
90 | | - | if __name__ == "__main__": |
91 | | - | #parse the args |
92 | 107 | | parser = argparse.ArgumentParser() |
93 | | - | parser.add_argument("-i", "--ip", help="Ip to listen on") |
94 | | - | parser.add_argument("-p", "--port", help="Port to listen on") |
95 | | - | parser.add_argument("-f", "--file", help="The exploit csv to use") |
| 108 | + | parser.add_argument("-i", "--ip", help="Ip to listen on", default=_IP_) |
| 109 | + | parser.add_argument("-p", "--port", help="Port to listen on", default=_PORT_) |
| 110 | + | parser.add_argument("-f", "--file", help="The exploit csv to use", default=_SEARCHSPLOIT_) |
96 | 111 | | args = parser.parse_args() |
97 | | - | if args.ip: |
98 | | - | _IP_ = args.ip |
99 | | - | if args.port: |
100 | | - | _PORT_ = args.port |
101 | | - | if args.file: |
102 | | - | _SEARCHSPLOIT_ = args.file |
| 112 | + | |
| 113 | + | print("[ ] Starting up") |
| 114 | + | database = parse_exploitdb(args.file) |
| 115 | + | asyncio.run(start_server(args.ip, args.port, database)) |
103 | 116 | | |
104 | | - | if not isfile(_SEARCHSPLOIT_): |
105 | | - | print("[-] Cannot find csv databse: {}\nFor more details visit: https://github.com/offensive-security/exploit-database".format(_SEARCHSPLOIT_)) |
106 | | - | exit(2) |
| 117 | + | def parse_exploitdb(path: str) -> List[OrderedDict[str,str]]: |
| 118 | + | database = [] |
| 119 | + | if not isfile(path): |
| 120 | + | raise FileNotFoundError( |
| 121 | + | "[-] Cannot find csv database at {}\nFor more details visit: https://github.com/offensive-security/exploit-database".format( |
| 122 | + | path |
| 123 | + | ) |
| 124 | + | ) |
107 | 125 | | |
108 | | - | #parse the exploit database and collect all the results |
| 126 | + | # parse the exploit database and collect all the results |
109 | 127 | | try: |
110 | | - | with open(_SEARCHSPLOIT_) as Fin: |
| 128 | + | with open(path, 'r') as Fin: |
111 | 129 | | reader = DictReader(Fin) |
112 | 130 | | for lines in reader: |
113 | | - | #add the database to the exploit server for non global persistance... or maybe it is technically still global? |
114 | | - | ExploitServer.exploitDatabase.append(lines) |
| 131 | + | # add the database to the exploit server for non global persistance |
| 132 | + | database.append(lines) |
115 | 133 | | except Exception as e: |
116 | | - | print("[-] Exception caught while attempting to parse database file. {}".format(e)) |
117 | | - | exit(3) |
| 134 | + | raise AttributeError( |
| 135 | + | "[-] Exception caught while attempting to parse database file. {}".format(e) |
| 136 | + | ) |
| 137 | + | return database |
118 | 138 | | |
119 | | - | print("[ ] Starting up") |
| 139 | + | if __name__ == "__main__": |
| 140 | + | # parse the args |
120 | 141 | | main() |