def _is_exif_in_logs(self, result, basedir, logfiles): """Checks to see if there is evidence of the exiftool exploit in the logs. Args: result (TurbiniaTaskResult): The object to place task results into. basedir (str): the root of the evidence. logfiles (str): The file(s) to check Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ check = " ".join(glob.glob(os.path.join(basedir, logfiles))) if not len(check): return ('', Priority.LOW, '') cmd = ['zgrep', '"exiftool command failed"', check] ret, result = self.execute(cmd, result, success_codes=[0, 1]) if ret == 0: summary = 'exif exploit detected in {0:s}'.format(logfiles) report = fmt.heading4(fmt.bold(summary)) return (report, Priority.HIGH, summary) return ('', Priority.LOW, '')
def analyze_jenkins(version, credentials): """Analyses a Jenkins configuration. Args: version (str): Version of Jenkins. credentials (list): of tuples with username and password hash. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ report = [] summary = '' priority = Priority.LOW credentials_registry = { hash: username for username, hash in credentials } # TODO: Add timeout parameter when dynamic configuration is ready. # Ref: https://github.com/google/turbinia/issues/244 weak_passwords = bruteforce_password_hashes( credentials_registry.keys()) if not version: version = 'Unknown' report.append(fmt.bullet('Jenkins version: {0:s}'.format(version))) if weak_passwords: priority = Priority.CRITICAL summary = 'Jenkins analysis found potential issues' report.insert(0, fmt.heading4(fmt.bold(summary))) line = '{0:n} weak password(s) found:'.format(len(weak_passwords)) report.append(fmt.bullet(fmt.bold(line))) for password_hash, plaintext in weak_passwords: line = 'User "{0:s}" with password "{1:s}"'.format( credentials_registry.get(password_hash), plaintext) report.append(fmt.bullet(line, level=2)) else: summary = 'Jenkins analysis found no issues' priority = Priority.LOW report.insert(0, fmt.heading4(summary)) report = '\n'.join(report) return (report, priority, summary)
def analyse_config(self, jupyter_config): """Extract security related configs from Jupyter configuration files. Args: config (str): configuration file content. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ findings = [] num_misconfigs = 0 for line in jupyter_config.split('\n'): if all(x in line for x in ['disable_check_xsrf', 'True']): findings.append(fmt.bullet('XSRF protection is disabled.')) num_misconfigs += 1 continue if all(x in line for x in ['allow_root', 'True']): findings.append( fmt.bullet('Juypter Notebook allowed to run as root.')) num_misconfigs += 1 continue if 'NotebookApp.password' in line: if all(x in line for x in ['required', 'False']): findings.append( fmt.bullet( 'Password is not required to access this Jupyter Notebook.' )) num_misconfigs += 1 continue if 'required' not in line: password_hash = line.split('=') if len(password_hash) > 1: if password_hash[1].strip() == "''": findings.append( fmt.bullet( 'There is no password set for this Jupyter Notebook.' )) num_misconfigs += 1 if all(x in line for x in ['allow_remote_access', 'True']): findings.append( fmt.bullet( 'Remote access is enabled on this Jupyter Notebook.')) num_misconfigs += 1 continue if findings: summary = 'Insecure Jupyter Notebook configuration found. Total misconfigs: {}'.format( num_misconfigs) findings.insert(0, fmt.heading4(fmt.bold(summary))) report = '\n'.join(findings) return (report, Priority.HIGH, summary) report = 'No issues found in Jupyter Notebook configuration.' return (report, Priority.LOW, report)
def runLoki(self, result, evidence): log_file = os.path.join(self.output_dir, 'loki.log') stdout_file = os.path.join(self.output_dir, 'loki_stdout.log') stderr_file = os.path.join(self.output_dir, 'loki_stderr.log') cmd = [ 'python', '/opt/loki/loki.py', '-w', '0', '--csv', '--intense', '--noprocscan', '--dontwait', '--noindicator', '-l', log_file, '-p', evidence.local_path ] (ret, result) = self.execute( cmd, result, log_files=[log_file], stdout_file=stdout_file, stderr_file=stderr_file, cwd='/opt/loki/') if ret != 0: raise TurbiniaException('Return code: {0:d}'.format(ret)) report = [] summary = 'No Loki threats found' priority = Priority.LOW report_lines = [] with open(stdout_file, 'r') as loki_report_csv: lokireader = csv.DictReader( loki_report_csv, fieldnames=['Time', 'Hostname', 'Level', 'Log']) for row in lokireader: if row['Level'] == 'ALERT': report_lines.append(row['Log']) if report_lines: priority = Priority.HIGH summary = 'Loki analysis found {0:d} alert(s)'.format(len(report_lines)) report.insert(0, fmt.heading4(fmt.bold(summary))) line = '{0:n} alerts(s) found:'.format(len(report_lines)) report.append(fmt.bullet(fmt.bold(line))) for line in report_lines: report.append(fmt.bullet(line, level=2)) report = '\n'.join(report) return (report, priority, summary)
def testFormatting(self): """Test text formatting.""" self.assertEqual('**testing**', fmt.bold(self.test_string)) self.assertEqual('# testing', fmt.heading1(self.test_string)) self.assertEqual('## testing', fmt.heading2(self.test_string)) self.assertEqual('### testing', fmt.heading3(self.test_string)) self.assertEqual('#### testing', fmt.heading4(self.test_string)) self.assertEqual('##### testing', fmt.heading5(self.test_string)) self.assertEqual('* testing', fmt.bullet(self.test_string)) self.assertEqual(' * testing', fmt.bullet(self.test_string, level=3)) self.assertEqual('`testing`', fmt.code(self.test_string))
def _analyse_wordpress_creds(self, creds, hashnames, timeout=300): """Attempt to brute force extracted Wordpress credentials. Args: creds (list): List of strings containing raw extracted credentials hashnames (dict): Dict mapping hash back to username for convenience. timeout (int): How long to spend cracking. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ report = [] summary = 'No weak passwords found' priority = Priority.LOW # 1000 is "phpass" weak_passwords = bruteforce_password_hashes( creds, tmp_dir=self.tmp_dir, timeout=timeout, extra_args='--username -m 400') if weak_passwords: priority = Priority.CRITICAL summary = 'Wordpress analysis found {0:d} weak password(s)'.format( len(weak_passwords)) report.insert(0, fmt.heading4(fmt.bold(summary))) line = '{0:n} weak password(s) found:'.format(len(weak_passwords)) report.append(fmt.bullet(fmt.bold(line))) for password_hash, plaintext in weak_passwords: if password_hash in hashnames: line = """User '{0:s}' with password '{1:s}'""".format( hashnames[password_hash], plaintext) report.append(fmt.bullet(line, level=2)) report = '\n'.join(report) return (report, priority, summary)
def analyse_shadow_file(self, shadow, hashes, timeout=300): """Analyses a Linux shadow file. Args: shadow (list): shadow file content (list of str). hashes (dict): dict of hashes to usernames timeout (int): Time in seconds to run password bruteforcing. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ report = [] summary = 'No weak passwords found' priority = Priority.LOW # 1800 is "sha512crypt $6$, SHA512 (Unix)" weak_passwords = bruteforce_password_hashes(shadow, tmp_dir=self.tmp_dir, timeout=timeout, extra_args='-m 1800') if weak_passwords: priority = Priority.CRITICAL summary = 'Shadow file analysis found {0:n} weak password(s)'.format( len(weak_passwords)) report.insert(0, fmt.heading4(fmt.bold(summary))) line = '{0:n} weak password(s) found:'.format(len(weak_passwords)) report.append(fmt.bullet(fmt.bold(line))) for password_hash, plaintext in weak_passwords: line = """User '{0:s}' with password '{1:s}'""".format( hashes[password_hash], plaintext) report.append(fmt.bullet(line, level=2)) report = '\n'.join(report) return (report, priority, summary)
def analyse_tomcat_file(self, tomcat_file): """Analyse a Tomcat file. - Search for clear text password entries in user configuration file - Search for .war deployment - Search for management control panel activity Args: tomcat_file (str): Tomcat file content. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ findings = [] tomcat_user_passwords_re = re.compile('(^.*password.*)', re.MULTILINE) tomcat_deploy_re = re.compile( '(^.*Deploying web application archive.*)', re.MULTILINE) tomcat_manager_activity_re = re.compile( '(^.*POST /manager/html/upload.*)', re.MULTILINE) count = 0 for password_entry in re.findall(tomcat_user_passwords_re, tomcat_file): findings.append( fmt.bullet('Tomcat user: '******'Tomcat App Deployed: ' + deployment_entry.strip())) count += 1 for mgmt_entry in re.findall(tomcat_manager_activity_re, tomcat_file): findings.append( fmt.bullet('Tomcat Management: ' + mgmt_entry.strip())) count += 1 if findings: msg = 'Tomcat analysis found {0:d} results'.format(count) findings.insert(0, fmt.heading4(fmt.bold(msg))) report = '\n'.join(findings) return (report, Priority.HIGH, msg) report = 'No Tomcat findings to report' return (report, Priority.LOW, report)
def analyze_wp_access_logs(self, config): """Analyses access logs containing Wordpress traffic. Args: config (str): access log file content. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ report = [] findings_summary = set() for log_line in config.split('\n'): if self.install_step_regex.search(log_line): line = '{0:s}: Wordpress installation successful'.format( self._get_timestamp(log_line)) report.append(fmt.bullet(line)) findings_summary.add('install') match = self.theme_editor_regex.search(log_line) if match: line = '{0:s}: Wordpress theme editor edited file ({1:s})'.format( self._get_timestamp(log_line), match.group('edited_file')) report.append(fmt.bullet(line)) findings_summary.add('theme_edit') if report: findings_summary = ', '.join(sorted(list(findings_summary))) summary = 'Wordpress access logs found ({0:s})'.format( findings_summary) report.insert(0, fmt.heading4(fmt.bold(summary))) report_text = '\n'.join(report) return (report_text, Priority.HIGH, summary) report_text = 'No Wordpress install or theme editing found in access logs' return (fmt.heading4(report_text), Priority.LOW, report_text)
def analyse_sshd_config(self, config): """Analyses an SSH configuration. Args: config (str): configuration file content. Returns: Tuple( report_text(str): The report data report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ findings = [] permit_root_login_re = re.compile( r'^\s*PermitRootLogin\s*(yes|prohibit-password|without-password)', re.IGNORECASE | re.MULTILINE) password_authentication_re = re.compile( r'^\s*PasswordAuthentication[\s"]*No', re.IGNORECASE | re.MULTILINE) permit_empty_passwords_re = re.compile( r'^\s*PermitEmptyPasswords[\s"]*Yes', re.IGNORECASE | re.MULTILINE) if re.search(permit_root_login_re, config): findings.append(fmt.bullet('Root login enabled.')) if not re.search(password_authentication_re, config): findings.append(fmt.bullet('Password authentication enabled.')) if re.search(permit_empty_passwords_re, config): findings.append(fmt.bullet('Empty passwords permitted.')) if findings: summary = 'Insecure SSH configuration found.' findings.insert(0, fmt.heading4(fmt.bold(summary))) report = '\n'.join(findings) return (report, 20, summary) report = 'No issues found in SSH configuration' return (report, 60, report)
def _AnalyzeHadoopAppRoot(self, collected_artifacts, output_dir): """Runs a naive AppRoot files parsing method. This extracts strings from the saved task file, and searches for usual post-compromise suspicious patterns. TODO: properly parse the Proto. Some documentation can be found over there: https://svn.apache.org/repos/asf/hadoop/common/branches/branch-0.23.7/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/proto/yarn_protos.proto Args: collected_artifacts(list(str)): a list of paths to extracted files output_dir(str): The base directory the artfacts are in. Returns: Tuple( list(str): The report data as a list of lines report_priority(int): The priority of the report (0 - 100) summary(str): A summary of the report (used for task status) ) """ report = [] evil_commands = [] strings_count = 0 priority = Priority.MEDIUM summary = '' for filepath in collected_artifacts: relpath = os.path.relpath(filepath, output_dir) command = 'strings -a "{0:s}"'.format(filepath) log.debug('Running command [{0:s}]'.format(command)) proc = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) strings_output, _ = proc.communicate() strings_output = codecs.decode(strings_output, 'utf-8') for line in strings_output.splitlines(): strings_count += 1 if (line.find('curl') >= 0) or (line.find('wget') >= 0): evil_commands.append((relpath, line)) if evil_commands: msg = 'Found suspicious commands!' report.append(fmt.heading4(fmt.bold(msg))) summary = msg priority = Priority.CRITICAL else: msg = 'Did not find any suspicious commands.' report.append(fmt.heading4(msg)) summary = msg for filepath, command in evil_commands: report.append(fmt.bullet(fmt.bold('Command:'))) report.append(fmt.code(command)) report.append('Found in file:') report.append(fmt.code(filepath)) msg = 'Extracted {0:d} strings from {1:d} file(s)'.format( strings_count, len(collected_artifacts)) report.append(fmt.bullet(msg)) return (report, priority, summary)