class IemlAVAutoServerPatcher(object): """IemlAVAutoServerPatcher Class.""" def __init__(self, debug=False, cred=None): # Initialize logger self.logger = PatchLogger(__name__, debug=debug) if not utils.check_root(): self.logger.log("Please run as root, exiting.", logtype="error") sys.exit(0) if not cred: self.logger.log("No credentials specified.", logtype="error") sys.exit(0) # List of files to patch self.to_patch = list() url = cred['url'] apache = int(cred['apache']) ssh = int(cred['ssh']) login = int(cred['login']) sysctl = int(cred['sysctl']) # Determine which file to patch if apache == 1: self.to_patch.append("apache") if ssh == 1: self.to_patch.append("ssh") if login == 1: self.to_patch.append("login") if sysctl == 1: self.to_patch.append("sysctl") if url and url != "XXXX": # if valid URL self.url = url else: self.url = None # Create Installer object self.installer = Installer(debug=debug) # Create Patcher object self.patcher = ConfigPatcher(debug=debug, to_patch=self.to_patch) if self.url: # Create SSLScanner object self.ssl_scanner = SSLScanner(debug=debug, url=self.url) def start(self): self.patcher.patch() # Start executing configuraton commands self.installer.install() if self.url: # if url is provided # Start SSL scanning self.ssl_scanner.start_scan()
class Installer(object): """Installer Class.""" def __init__(self, debug=False): # Initialize logger self.logger = PatchLogger( __name__, debug=debug ) # Command configuraton path self._COMMAND_PATH = "iemlav/lib/auto_server_patcher/configs/commands.json" # Load configuraton data self.config_data = self.open_json(self._COMMAND_PATH) # Categorize OS self.os_name = utils.categorize_os() if self.os_name: try: self.os_config_data = self.config_data[self.os_name] except KeyError: self.logger.log( "Could not load OS configuraton data.", logtype="error" ) else: self.logger.log( "Could not determine OS specific config." ) @staticmethod def open_json(path): with open(path, "r") as json_data_file: data = json.load(json_data_file) return data @staticmethod def excecute_command(command): command = command.split(' ') process_respose = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) output, error = process_respose.communicate() if output: output = output.decode('utf-8') if error: error = error.decode('utf-8') return output, error def install(self): for command in self.os_config_data["commands"]: self.logger.log( "Executing command: " + command, logtype="info" ) output, error = self.excecute_command(command) if output: msg = "Ouput: " + str(output) self.logger.log( msg, logtype="info" ) if error: msg = "Error: " + str(output) self.logger.log( msg, logtype="info" )
class SSLScanner(object): """SSLScanner class.""" def __init__(self, debug=False, url=None): # Initialize logger self.logger = PatchLogger(__name__, debug=debug) # API URL self._API_URL = "https://api.ssllabs.com/api/v3/analyze/" self.analyze_payload = { 'startNew': 'on', 'publish': 'off', 'all': 'done', 'ignoreMismatch': 'on' } # URL / website to scan self.url = str(url) @staticmethod def request_api(url, payload): resp = requests.get(url, params=payload) return resp.json() def analyze(self): self.analyze_payload.update({'host': self.url}) # Start analyzing the website self.logger.log("Analyzing URL: " + self.url, logtype="info") resp = self.request_api(self._API_URL, self.analyze_payload) self.analyze_payload.pop('startNew') while resp['status'] != 'READY' and resp['status'] != 'ERROR': time.sleep(15) resp = self.request_api(self._API_URL, self.analyze_payload) return resp @staticmethod def vulnerability_parser(data): base_data = data['endpoints'][0]['details'] # Parse the API data into a dict vuln_dict = { 'beastAttack': base_data['vulnBeast'], 'poodle': base_data['poodle'], 'poodleTls': base_data['poodleTls'], 'rc4': base_data['rc4Only'], 'heartbeat': base_data['heartbeat'], 'heartbleed': base_data['heartbleed'], 'ticketbleed': base_data['ticketbleed'], 'openSSL_CCS': base_data['openSslCcs'], 'openSSL_padding': base_data['openSSLLuckyMinus20'], 'robot': base_data['bleichenbacher'], 'freak': base_data['freak'], 'logjam': base_data['logjam'], 'drown_attack': base_data['drownVulnerable'], } return vuln_dict @staticmethod def get_value(key, value): main_dict = { 'poodleTls': { '-3': 'timeout', '-2': 'TLS not supported', '-1': 'test failed', '0': 'unknown', '1': 'not vulnerable', '2': 'vulnerable' }, 'ticketbleed': { '-1': 'test failed', '0': 'unknown', '1': 'not vulnerable', '2': 'vulnerable and insecure' }, 'openSSL_CCS': { '-1': 'test failed', '0': 'unknown', '1': 'not vulnerable', '2': 'possibly vulnerable, but not exploitable', '3': 'vulnerable and exploitable' }, 'openSSL_padding': { '-1': 'test failed', '0': 'unknown', '1': 'not vulnerable', '2': 'vulnerable and insecure' }, 'robot': { '-1': 'test failed', '0': 'unknown', '1': 'not vulnerable', '2': 'vulnerable (weak oracle)', '3': 'vulnerable (strong oracle)', '4': 'inconsistent results' } } value = str(value) return main_dict[key][value] def log_data(self, dict_value): self.logger.log("Vulnerability Scan Result: " + self.url, logtype="info") for vuln, res in dict_value.items(): if not isinstance(res, bool): res = self.get_value(vuln, res) self.logger.log(str(vuln) + ": " + str(res), logtype="info") def start_scan(self): # Start analyzing resp = self.analyze() # Parse the result parsed_resp = self.vulnerability_parser(resp) # Log the parsed data self.log_data(parsed_resp)
class ConfigPatcher(object): """ConfigPatcher class.""" def __init__(self, debug=False, to_patch=None): # Initialize logger self.logger = PatchLogger(__name__, debug=debug) # Configuration file path self._CONFIG_PATH = "iemlav/lib/auto_server_patcher/configs/config.json" # Load configuration self.config_data = self.open_json(self._CONFIG_PATH) # Categorize OS os_name = utils.categorize_os() if os_name: try: self.os_config_data = self.config_data[ os_name] # if OS in configuration except KeyError: self.logger.log("Could not load OS specific configuration.", logtype="error") else: self.logger.log("Operating system cannot be determined.", logtype="error") sys.exit(0) # List of files to patch if to_patch: self.to_patch = to_patch else: self.to_patch = [] def open_file(self, path): try: with open(path, "r") as f: return f.readlines() except Exception as e: self.logger.log("Error occured: " + str(e), logtype="error") def open_json(self, path): try: with open(path, "r") as json_data_file: data = json.load(json_data_file) return data except Exception as e: self.logger.log("Error occured: " + str(e), logtype="error") def write_data(self, path, data): try: with open(path, "w") as wf: for line in data: wf.write(line + "\n") except Exception as e: self.logger.log("Error occured: " + str(e), logtype="error") def patch(self): for path in self.os_config_data: # iterate over the configuration patch_this = False # patch this file or not for req_patch in self.to_patch: if req_patch in path: patch_this = True if patch_this: self.logger.log("Patching: " + str(path), logtype="info") file_data = self.open_file(path) # open the file to configure new_data = [] # new data to over-write config_added = [] # successfully configured parameters config_not_added = [] # not configured parameters sep = self.os_config_data[path]["sep"] # separator for index, line in enumerate(file_data): flag = 0 # write the original line for rep_text in self.os_config_data[path]["config"].keys(): hold = False # forward refrencing not needed in_front = False # not found in forward refrence if rep_text in line: if line.strip(" ").strip("\n").startswith( "#"): # found comment hold = True # hold, prepare for forward refrence if hold: # if forward refrence is needed for _, nf_line in enumerate(file_data, start=index + 1): if (rep_text in nf_line and not nf_line.strip(" ").strip( "\n").startswith("#")): in_front = True # found in forward refrencing if not in_front: # not in forward refrencing self.logger.log("Old config line: " + line.strip("\n"), logtype="info") new_config_line = rep_text + sep + \ self.os_config_data[path]["config"][rep_text] new_data.append(new_config_line) config_added.append(rep_text) flag = 1 # write the new line self.logger.log("New config line: " + new_config_line, logtype="info") if flag == 0: # write the original line new_data.append(line.strip(" ").strip("\n")) elif flag == 1: # already written flag = 0 # reset flag # Look which parameters were not over-written # as they were not found in the config file for rep_text in self.os_config_data[path]["config"].keys(): if rep_text not in config_added: new_config_line = rep_text + sep + \ self.os_config_data[path]["config"][rep_text] config_not_added.append(new_config_line) # Extend the new configuration new_data.extend(config_not_added) # Write the data (overwrite) the config file self.write_data(path=path, data=new_data) self.logger.log("Patched: " + str(path), logtype="info") # Empty the list for the next configuration file new_data.clear() config_added.clear() config_not_added.clear()