Update Eigener Parser
parent
d5f59c6020
commit
9608cf7d16
@ -100,10 +100,190 @@ Jedes Result bildet hierbei einen einzelnen Durchlauf eines Tools ab, z.B. "otse
|
|||||||
Weiter haben beide Klassen den `@dataclass` und `@dataclass_json` Decorator. Dieser sorgt dafür, dass das Objekt später leicht serialisiert werden kann und erspart Boiler-Plate Code.
|
Weiter haben beide Klassen den `@dataclass` und `@dataclass_json` Decorator. Dieser sorgt dafür, dass das Objekt später leicht serialisiert werden kann und erspart Boiler-Plate Code.
|
||||||
|
|
||||||
## Stage 3: Parsen der Daten
|
## Stage 3: Parsen der Daten
|
||||||
|
```python
|
||||||
|
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)))
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
### Stage 3.1: Lynis
|
### Stage 3.1: Lynis
|
||||||
|
|
||||||
|
* Was ist wichtig?
|
||||||
|
* Nur das Console-Log benutzt
|
||||||
|
* Dat und Lynis-Log waren unbrauchbar
|
||||||
|
|
||||||
|
```python
|
||||||
|
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 ,:-]*).*"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
LYNIS_BLOCKS = {
|
||||||
|
"Boot and services",
|
||||||
|
...
|
||||||
|
"Kernel Hardening",
|
||||||
|
"Hardening"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Weiter wurden noch waringCount, bla bla
|
||||||
|
|
||||||
|
Nach dem generellen step wurden dann noch spezielle props aus den obigen blocks extrahiert, dies ginge aber zu weit, genaueres in der datei [lynis.py](https://gitlab.com/marcel.schwarz/it-security-2/-/blob/master/scan_output_parser/lynis.py)
|
||||||
|
|
||||||
### Stage 3.2: Testssl
|
### Stage 3.2: Testssl
|
||||||
|
* Einer der leichtesten Parser
|
||||||
|
* Leider zu spät bemerkt, dass es einen JSON export gegeben hätte.
|
||||||
|
* TXT datei hat auch funktioniert
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
* Gelöst durch initialisierung mit default Werten und dann das setzen auf bestimmte regex
|
||||||
|
* Vorher trennung der einzelnen logs der ports durch einen split bei "## Scan startet as: "
|
||||||
|
|
||||||
|
|
||||||
### Stage 3.3: Otseca
|
### Stage 3.3: Otseca
|
||||||
|
* HTML result einfacher parse
|
||||||
|
* Nur nach rot, gelb und grün und gesamt
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
* Weitere Distro info, was in lynis gefehlt hat.
|
||||||
|
|
||||||
|
```python
|
||||||
|
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": int(upgrades_count[0]),
|
||||||
|
"newlyInstalled": int(upgrades_count[1]),
|
||||||
|
"remove": int(upgrades_count[2]),
|
||||||
|
"notUpgraded": int(upgrades_count[3])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Stage 4: Ausgabe
|
## Stage 4: Ausgabe
|
||||||
### Stage 4.1: JSON-Ausgabe
|
### Stage 4.1: JSON-Ausgabe
|
||||||
|
```python
|
||||||
|
with open("export.json", "w") as handle:
|
||||||
|
handle.write(Run.schema().dumps(list_of_all, many=True))
|
||||||
|
```
|
||||||
|
|
||||||
|
* Problem mit JSON
|
||||||
|
|
||||||
### Stage 4.2: SQLite-Ausgabe
|
### Stage 4.2: SQLite-Ausgabe
|
||||||
# Ergebnisse
|
* Warum SQLite?
|
||||||
|
|
||||||
|
```python
|
||||||
|
for run in list_of_all:
|
||||||
|
write_run_to_db(run)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def write_run_to_db(run):
|
||||||
|
conn = sqlite3.connect(DB_NAME)
|
||||||
|
run_data = (run.id, run.platform, run.system, run.version, run.path)
|
||||||
|
conn.execute("INSERT OR IGNORE INTO runs VALUES (?, ?, ?, ?, ?)", run_data)
|
||||||
|
|
||||||
|
... otseca ...
|
||||||
|
... testssl ...
|
||||||
|
|
||||||
|
for lynis_res in run.lynis_results:
|
||||||
|
categories = list(lynis_res.result.values())[:31]
|
||||||
|
general = list(lynis_res.result.values())[31]
|
||||||
|
data = (
|
||||||
|
run.id, lynis_res.run_nr, str(lynis_res.path),
|
||||||
|
*list(map(json.dumps, categories)),
|
||||||
|
*general.values()
|
||||||
|
)
|
||||||
|
print(data)
|
||||||
|
conn.execute("INSERT OR IGNORE INTO lynis_results VALUES (" + "".join("?," * 37) + "?)", data)
|
||||||
|
conn.commit()
|
||||||
|
```
|
||||||
|
# Ergebnisse
|
||||||
|
* Insgesamt etwa 30 RegEx um zu parsen
|
||||||
|
* laufzeit unter 10 Sekunden
|
||||||
|
* Extremer Vorteil durch das abfragen mit SQL Syntax
|
||||||
|
* Keine Flüchtigkeits- oder Übertragungsfehler durch menschliches verschulden
|
Loading…
Reference in New Issue
Block a user