Skip to content

Commit 418f84b

Browse files
authored
feat(#441): lot 6 netcross_core, exploit_signatures et banners
Résumés DEBUG, détections et bannières en TRACE ; charge utile malformée en TRACE au lieu de logger.exception.
1 parent dd38f42 commit 418f84b

3 files changed

Lines changed: 97 additions & 3 deletions

File tree

‎src/netcross_core/application/banners.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,9 +395,18 @@ def extract_banners(proto: str, sport: int | None, dport: int | None, payload: b
395395
banners = _greeting_banners(payload, sport)
396396
elif sport == 53:
397397
banners = _dns_banners(payload, proto)
398+
if banners:
399+
logger.trace(
400+
"extract_banners: {} banniere(s) {} (port source {}){}",
401+
len(banners),
402+
"/".join(sorted({b.protocol for b in banners})),
403+
sport,
404+
", tronque a " + str(MAX_BANNERS_PER_PACKET) if len(banners) > MAX_BANNERS_PER_PACKET else "",
405+
)
398406
return tuple(banners[:MAX_BANNERS_PER_PACKET])
399-
except (ValueError, IndexError, struct.error, UnicodeError):
400-
logger.exception("échec dans extract_banners")
407+
except (ValueError, IndexError, struct.error, UnicodeError) as exc:
408+
# charge utile malformee : resultat attendu, a chaque paquet -> TRACE sans traceback
409+
logger.trace("extract_banners: charge utile {} malformee ({} octets) : {!r}", proto, len(payload), exc)
401410
return ()
402411

403412

@@ -439,6 +448,11 @@ def build_service_fingerprints(packets: Iterable[Pkt]) -> list[dict]:
439448
}
440449
elif len(b.raw) > len(entry["banner"]):
441450
entry["banner"] = b.raw
451+
logger.debug(
452+
"build_service_fingerprints: {} logiciel(s) identifie(s) sur {} hote(s)",
453+
len(found),
454+
len({(e["point"], e["host"]) for e in found.values()}),
455+
)
442456
return sorted(
443457
found.values(),
444458
key=lambda e: (e["point"], e["host"], e["port"] or 0, e["service"].lower(), e["version"] or "", e["role"]),

‎src/netcross_core/exploit_signatures.py‎

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -729,12 +729,19 @@ class ExploitScanner:
729729
def __init__(self, signatures: Sequence[Signature] | None = None) -> None:
730730
self.signatures: list[Signature] = list(signatures) if signatures is not None else load_signatures()
731731
self._flows: dict[tuple[str, int | None, str, int | None, str], _FlowState] = {}
732+
logger.debug(
733+
"ExploitScanner: {} signature(s) ({}), dont {} a detecteur structurel",
734+
len(self.signatures),
735+
"fournies" if signatures is not None else "base par defaut",
736+
sum(1 for sig in self.signatures if sig.detector is not None),
737+
)
732738

733739
def _state(self, pkt: PacketLike) -> _FlowState:
734740
key = (pkt.src, pkt.sport, pkt.dst, pkt.dport, pkt.proto)
735741
state = self._flows.get(key)
736742
if state is None:
737743
if len(self._flows) >= MAX_TRACKED_FLOWS:
744+
logger.trace("ExploitScanner._state: {} flux suivis, eviction du plus ancien", len(self._flows))
738745
del self._flows[next(iter(self._flows))] # evince le flux le plus ancien
739746
state = self._flows[key] = _FlowState()
740747
return state
@@ -781,6 +788,16 @@ def scan_packet(self, pkt: PacketLike, point: str = "") -> list[Detection]:
781788
else:
782789
hit = self._match_pattern(sig, view)
783790
if hit is not None:
791+
logger.trace(
792+
"scan_packet: signature {} sur la trame {} ({}:{} -> {}:{}), cible {}",
793+
sig.id,
794+
pkt.frame_number,
795+
pkt.src,
796+
pkt.sport,
797+
pkt.dst,
798+
pkt.dport,
799+
hit[0],
800+
)
784801
detections.append(
785802
Detection(
786803
signature_id=sig.id,
@@ -807,8 +824,18 @@ def scan_packet(self, pkt: PacketLike, point: str = "") -> list[Detection]:
807824
def scan_packets(self, packets: Iterable[PacketLike], point: str = "") -> list[Detection]:
808825
"""Detections pour des paquets fournis dans l'ordre de capture."""
809826
detections: list[Detection] = []
827+
scanned = 0
810828
for pkt in packets:
829+
scanned += 1
811830
detections.extend(self.scan_packet(pkt, point))
831+
logger.debug(
832+
"scan_packets: point {!r}, {} paquet(s), {} detection(s) sur {} signature(s) distincte(s), {} flux suivis",
833+
point,
834+
scanned,
835+
len(detections),
836+
len({d.signature_id for d in detections}),
837+
len(self._flows),
838+
)
812839
return detections
813840

814841

@@ -825,7 +852,9 @@ def detect_exploits(
825852

826853
def detections_to_dicts(detections: Iterable[Detection]) -> list[dict[str, Any]]:
827854
"""Serialisation JSON stable (sans charge utile)."""
828-
return [asdict(d) | {"cves": list(d.cves)} for d in detections]
855+
out = [asdict(d) | {"cves": list(d.cves)} for d in detections]
856+
logger.debug("detections_to_dicts: {} detection(s)", len(out))
857+
return out
829858

830859

831860
__all__ = [
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""#441, lot 6 : journalisation de exploit_signatures et application/banners."""
2+
3+
from __future__ import annotations
4+
5+
from types import SimpleNamespace
6+
7+
import pytest
8+
from loguru import logger
9+
10+
from netcross_core.application.banners import build_service_fingerprints, extract_banners
11+
from netcross_core.exploit_signatures import ExploitScanner, detections_to_dicts
12+
13+
14+
@pytest.fixture
15+
def journal():
16+
logger.enable("netcross_core")
17+
lignes: list[tuple[str, str]] = []
18+
sink = logger.add(lambda m: lignes.append((m.record["level"].name, m.record["message"])), level="TRACE")
19+
yield lignes
20+
logger.remove(sink)
21+
22+
23+
def _vu(journal, niveau, fragment):
24+
return any(n == niveau and fragment in m for n, m in journal)
25+
26+
27+
def test_scanner_resume_en_debug(journal):
28+
scanner = ExploitScanner()
29+
pkt = SimpleNamespace(
30+
payload=b"GET / HTTP/1.1\\r\\n\\r\\n", proto="TCP", src="10.0.0.1", sport=50000,
31+
dst="10.0.0.2", dport=80, ts=1.0, frame_number=1, payload_hash=None,
32+
) # fmt: skip
33+
detections = scanner.scan_packets([pkt], point="A")
34+
detections_to_dicts(detections)
35+
assert _vu(journal, "DEBUG", "base par defaut")
36+
assert _vu(journal, "DEBUG", "scan_packets: point 'A', 1 paquet(s)")
37+
assert _vu(journal, "DEBUG", "detections_to_dicts:")
38+
39+
40+
def test_bannieres_trace_et_resume(journal):
41+
banners = extract_banners("TCP", 22, 50000, b"SSH-2.0-OpenSSH_9.6\\r\\n")
42+
assert banners
43+
assert _vu(journal, "TRACE", "extract_banners: 1 banniere(s)")
44+
build_service_fingerprints([])
45+
assert _vu(journal, "DEBUG", "build_service_fingerprints: 0 logiciel(s)")
46+
47+
48+
def test_charge_utile_malformee_sans_traceback(journal):
49+
# reponse DNS tronquee sur le port 53 : parsee puis rejetee
50+
assert extract_banners("UDP", 53, 40000, b"\\x00\\x01\\x85\\x00\\x00\\x01\\x00\\x01") == ()
51+
assert not any(n in ("ERROR", "CRITICAL") for n, _m in journal)

0 commit comments

Comments
 (0)