Add parser for output
This commit is contained in:
parent
b6d0391ff0
commit
5c7939c163
109
.gitignore
vendored
Normal file
109
.gitignore
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
__pycache__
|
||||||
|
|
||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/pycharm
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=pycharm
|
||||||
|
|
||||||
|
### PyCharm ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### PyCharm Patch ###
|
||||||
|
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# Sonarlint plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# SonarQube Plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Markdown Navigator plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
# Cache file creation bug
|
||||||
|
# See https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
|
# CodeStream plugin
|
||||||
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
|
.idea/codestream.xml
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/pycharm
|
8
scan_output_parser/.idea/.gitignore
generated
vendored
Normal file
8
scan_output_parser/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/../../../../../:\Git-Repos\it-security-2\scan_output_parser\.idea/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
6
scan_output_parser/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
scan_output_parser/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
4
scan_output_parser/.idea/misc.xml
generated
Normal file
4
scan_output_parser/.idea/misc.xml
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.9" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
8
scan_output_parser/.idea/modules.xml
generated
Normal file
8
scan_output_parser/.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/scan_output_parser.iml" filepath="$PROJECT_DIR$/.idea/scan_output_parser.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
8
scan_output_parser/.idea/scan_output_parser.iml
generated
Normal file
8
scan_output_parser/.idea/scan_output_parser.iml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
6
scan_output_parser/.idea/vcs.xml
generated
Normal file
6
scan_output_parser/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
108
scan_output_parser/lynis.py
Normal file
108
scan_output_parser/lynis.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
LYNIS_REGEX = {
|
||||||
|
"green": r"\[1;32m", # more advanced "green": r"\[ .*\[1;32m([\w\d ]*).*",
|
||||||
|
"yellow": r"\[1;33m",
|
||||||
|
"red": r"\[1;31m",
|
||||||
|
"white": r"\[1;37m",
|
||||||
|
"heading": r".*\[1;33m([\w\d ,:-]*).*"
|
||||||
|
}
|
||||||
|
LYNIS_BLOCKS = {
|
||||||
|
"Boot and services",
|
||||||
|
"Kernel",
|
||||||
|
"Memory and Processes",
|
||||||
|
"Users, Groups and Authentication",
|
||||||
|
"Shells",
|
||||||
|
"File systems",
|
||||||
|
"USB Devices",
|
||||||
|
"Storage",
|
||||||
|
"NFS",
|
||||||
|
"Name services",
|
||||||
|
"Ports and packages",
|
||||||
|
"Networking",
|
||||||
|
"Printers and Spools",
|
||||||
|
"Software: e-mail and messaging",
|
||||||
|
"Software: firewalls",
|
||||||
|
"Software: webserver",
|
||||||
|
"SSH Support",
|
||||||
|
"Databases",
|
||||||
|
"PHP",
|
||||||
|
"Logging and files",
|
||||||
|
"Insecure services",
|
||||||
|
"Scheduled tasks",
|
||||||
|
"Accounting",
|
||||||
|
"Time and Synchronization",
|
||||||
|
"Cryptography",
|
||||||
|
"Security frameworks",
|
||||||
|
"Software: Malware",
|
||||||
|
"File Permissions",
|
||||||
|
"Home directories",
|
||||||
|
"Kernel Hardening",
|
||||||
|
"Hardening"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def lynis_get_base(path_to_log):
|
||||||
|
with open(path_to_log) as handle:
|
||||||
|
text = handle.read()
|
||||||
|
blocks = text.split("[+]")
|
||||||
|
interesting_blocks = {}
|
||||||
|
for block in blocks:
|
||||||
|
heading = re.findall(LYNIS_REGEX["heading"], block.splitlines()[0])
|
||||||
|
if heading and heading[0] in LYNIS_BLOCKS:
|
||||||
|
block_text = "".join(block.splitlines()[2:])
|
||||||
|
interesting_blocks[heading[0]] = {
|
||||||
|
"text": block_text,
|
||||||
|
"counts": {
|
||||||
|
"green": len(re.findall(LYNIS_REGEX['green'], block_text)),
|
||||||
|
"yellow": len(re.findall(LYNIS_REGEX['yellow'], block_text)),
|
||||||
|
"red": len(re.findall(LYNIS_REGEX['red'], block_text)),
|
||||||
|
"white": len(re.findall(LYNIS_REGEX['white'], block_text)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interesting_blocks["GENERAL"] = {}
|
||||||
|
if warning_count := re.findall(r".*Warnings.* \((\d+)\)", text):
|
||||||
|
interesting_blocks["GENERAL"]["warningCount"] = int(warning_count[0])
|
||||||
|
|
||||||
|
if suggestion_count := re.findall(r".*Suggestions.* \((\d+)\)", text):
|
||||||
|
interesting_blocks["GENERAL"]["suggestionCount"] = int(suggestion_count[0])
|
||||||
|
|
||||||
|
if hardening_index := re.findall(r".*Hardening index.*m(\d+)", text):
|
||||||
|
interesting_blocks["GENERAL"]["hardeningIndex"] = int(hardening_index[0])
|
||||||
|
|
||||||
|
if tests_performed := re.findall(r".*Tests performed.*m(\d+)", text):
|
||||||
|
interesting_blocks["GENERAL"]["testsPerformed"] = int(tests_performed[0])
|
||||||
|
|
||||||
|
return interesting_blocks
|
||||||
|
|
||||||
|
|
||||||
|
def lynis_add_special_properties(lynis_base):
|
||||||
|
def find_prop(category, key, regex, as_int=False):
|
||||||
|
if category in lynis_base:
|
||||||
|
if match := re.findall(regex, lynis_base[category]["text"]):
|
||||||
|
lynis_base[category][key] = int(match[0]) if as_int else match[0]
|
||||||
|
|
||||||
|
find_prop("Boot and services", "runningServices", r"found (\d+) running services", as_int=True)
|
||||||
|
find_prop("Boot and services", "enabledServices", r"found (\d+) enabled services", as_int=True)
|
||||||
|
find_prop("Kernel", "defaultRunLevel", r"RUNLEVEL (\d+)", as_int=True)
|
||||||
|
find_prop("Kernel", "activeModules", r"Found (\d+) active modules", as_int=True)
|
||||||
|
find_prop("Kernel", "kernelUpdate", r"Checking for available kernel update.*? \[ .*?m([\w ]*).*? \]")
|
||||||
|
find_prop("Shells", "numShells", r"Result: found (\d*) shells", as_int=True)
|
||||||
|
find_prop("Networking", "ipv6Enabled", r"Checking IPv6 configuration.*? \[ .*?m([\w ]*).*? \]")
|
||||||
|
find_prop("Time and Synchronization", "lastSync", r"Last time synchronization.*? \[ .*?m([\w ]*).*? \]")
|
||||||
|
find_prop("Security frameworks", "unconfinedProcesses", r"Found (\d+) unconfined processes")
|
||||||
|
|
||||||
|
|
||||||
|
def lynis_remove_text_from_blocks(lynis_base):
|
||||||
|
for block in lynis_base:
|
||||||
|
try:
|
||||||
|
del lynis_base[block]["text"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def lynis_parse(path):
|
||||||
|
lynis_base = lynis_get_base(path)
|
||||||
|
lynis_add_special_properties(lynis_base)
|
||||||
|
lynis_remove_text_from_blocks(lynis_base)
|
||||||
|
return lynis_base
|
57
scan_output_parser/main.py
Normal file
57
scan_output_parser/main.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import glob
|
||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from lynis import lynis_parse
|
||||||
|
from otseca import otseca_parse
|
||||||
|
from testssl import testssl_parse
|
||||||
|
|
||||||
|
BASE_SCAN_PATH = os.path.join("..", "raw_scans")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Result:
|
||||||
|
path: str
|
||||||
|
run_nr: int
|
||||||
|
result: dict
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Run:
|
||||||
|
id: int
|
||||||
|
platform: str
|
||||||
|
system: str
|
||||||
|
version: str
|
||||||
|
path: str
|
||||||
|
otseca_results: List[Result]
|
||||||
|
lynis_results: List[Result]
|
||||||
|
testssl_results: List[Result]
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
list_of_all = []
|
||||||
|
all_scans = glob.glob(os.path.join(BASE_SCAN_PATH, "*", ""))
|
||||||
|
for scan in all_scans:
|
||||||
|
findings = re.findall(r"(\d+)_(.*)_(.*)_(.*)", os.path.dirname(scan))
|
||||||
|
findings = findings[0]
|
||||||
|
list_of_all.append(
|
||||||
|
Run(findings[0], findings[1], findings[2], findings[3], scan, [], [], [])
|
||||||
|
)
|
||||||
|
for run in list_of_all:
|
||||||
|
for otseca_path in glob.glob(os.path.join(run.path, "otseca*", "report*")):
|
||||||
|
nr = re.findall(r"otseca-(\d+)", otseca_path)[0]
|
||||||
|
run.otseca_results.append(Result(otseca_path, nr, otseca_parse(otseca_path)))
|
||||||
|
for log_file in os.listdir(run.path):
|
||||||
|
path = os.path.join(run.path, log_file)
|
||||||
|
nr = re.findall(r"(\d+)", log_file)[0]
|
||||||
|
if "lynis-console-" in log_file:
|
||||||
|
run.lynis_results.append(Result(path, nr, lynis_parse(path)))
|
||||||
|
if "testssl-" in log_file:
|
||||||
|
run.testssl_results.append(Result(path, nr, testssl_parse(path)))
|
||||||
|
[print(run) for run in list_of_all]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
49
scan_output_parser/otseca.py
Normal file
49
scan_output_parser/otseca.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
|
||||||
|
OTSECA_FILES = {
|
||||||
|
"distro": "distro.all.log.html",
|
||||||
|
"kernel": "kernel.all.log.html",
|
||||||
|
"network": "network.all.log.html",
|
||||||
|
"permission": "permissions.all.log.html",
|
||||||
|
"services": "services.all.log.html",
|
||||||
|
"system": "system.all.log.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def otseca_box_counts(path_to_report):
|
||||||
|
counts = {}
|
||||||
|
for (name, curr_file) in OTSECA_FILES.items():
|
||||||
|
curr_path = os.path.join(path_to_report, curr_file)
|
||||||
|
with open(curr_path, encoding="UTF-8") as handle:
|
||||||
|
text = handle.read()
|
||||||
|
counts[name] = {
|
||||||
|
"green": (len(re.findall("background-color: #1F9D55", text))),
|
||||||
|
"yellow": (len(re.findall("background-color: #F2D024", text))),
|
||||||
|
"red": (len(re.findall("background-color: #CC1F1A", text))),
|
||||||
|
"total": (len(re.findall("background-color:", text)))
|
||||||
|
}
|
||||||
|
return counts
|
||||||
|
|
||||||
|
|
||||||
|
def otseca_distro_info(path_to_report):
|
||||||
|
with open(os.path.join(path_to_report, OTSECA_FILES["distro"])) as handle:
|
||||||
|
text = handle.read()
|
||||||
|
|
||||||
|
pkg_count = len(re.findall("ii {2}", text))
|
||||||
|
upgrades_count = re.findall(r"(\d+) .*? (\d+) .*? (\d+) .*? (\d+) .*", text).pop()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"pkgCount": pkg_count,
|
||||||
|
"upgraded": upgrades_count[0],
|
||||||
|
"newlyInstalled": upgrades_count[1],
|
||||||
|
"remove": upgrades_count[2],
|
||||||
|
"notUpgraded": upgrades_count[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def otseca_parse(path):
|
||||||
|
return {
|
||||||
|
"boxes": otseca_box_counts(path),
|
||||||
|
"general": otseca_distro_info(path)
|
||||||
|
}
|
32
scan_output_parser/testssl.py
Normal file
32
scan_output_parser/testssl.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def testssl_parse(path):
|
||||||
|
with open(path) as handle:
|
||||||
|
text = handle.read()
|
||||||
|
|
||||||
|
testssl_overall = {
|
||||||
|
"443": {"open": False, "ssl": False},
|
||||||
|
"21": {"open": False, "ssl": False},
|
||||||
|
"465": {"open": False, "ssl": False},
|
||||||
|
"587": {"open": False, "ssl": False},
|
||||||
|
"110": {"open": False, "ssl": False},
|
||||||
|
"995": {"open": False, "ssl": False},
|
||||||
|
"993": {"open": False, "ssl": False},
|
||||||
|
"5432": {"open": False, "ssl": False},
|
||||||
|
"3306": {"open": False, "ssl": False}
|
||||||
|
}
|
||||||
|
|
||||||
|
tests = text.split("## Scan started as: ")
|
||||||
|
for test in tests:
|
||||||
|
port = re.findall("Start .*? -->> 127\.0\.0\.1:(\d*) \(localhost\) <<--", test)
|
||||||
|
if not port:
|
||||||
|
continue
|
||||||
|
port = port[0]
|
||||||
|
if "Overall Grade" in test:
|
||||||
|
testssl_overall[port]["ssl"] = True
|
||||||
|
testssl_overall[port]["open"] = True
|
||||||
|
if "firewall" not in test:
|
||||||
|
testssl_overall[port]["open"] = True
|
||||||
|
continue
|
||||||
|
return testssl_overall
|
Loading…
Reference in New Issue
Block a user