Skip to content

Commit 3f08e30

Browse files
committed
fix
1 parent 62250ad commit 3f08e30

2 files changed

Lines changed: 16 additions & 22 deletions

File tree

dojo/tools/pingcastle/parser.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99

1010

1111
class PingCastleParser:
12+
1213
CVE_REGEX = re.compile(r"(CVE-\d{4}-\d{4,7})", re.IGNORECASE)
13-
# keep severity order for minimal contextual bumping
14+
1415
_SEVERITY_ORDER = ["Info", "Low", "Medium", "High", "Critical"]
1516

1617
def get_scan_types(self):
@@ -36,16 +37,18 @@ def get_findings(self, file, test):
3637
model = rr.findtext("Model") or ""
3738
risk_id = rr.findtext("RiskId") or ""
3839
rationale = rr.findtext("Rationale") or ""
39-
# base severity from points
40+
4041
severity = self._map_points_to_severity(points)
41-
# minimal contextual bumping (CVE mention, missing mitigation, DC scope, Exposure category)
4242
severity = self._apply_contextual_bump(
4343
severity=severity,
4444
category=category,
4545
model=model,
4646
risk_id=risk_id,
4747
rationale=rationale,
4848
)
49+
if not severity or severity not in self._SEVERITY_ORDER:
50+
severity = "Info"
51+
4952
title = f"[PingCastle] {risk_id} ({category}/{model})"
5053
description = self._compose_risk_rule_description(
5154
domain_fqdn=domain_fqdn,
@@ -92,8 +95,6 @@ def get_findings(self, file, test):
9295
findings.extend(list(dupes.values()))
9396
return findings
9497

95-
# --- helpers ---
96-
9798
def _compose_risk_rule_description(
9899
self,
99100
domain_fqdn,
@@ -140,7 +141,6 @@ def _collect_domain_controllers(self, root):
140141
"ips": ips,
141142
"rpc_interfaces": [],
142143
}
143-
# RPC interfaces
144144
for rpc in dc.findall("RPCInterfacesOpen/HealthcheckDCRPCInterface"):
145145
dc_info["rpc_interfaces"].append({
146146
"ip": rpc.attrib.get("IP", ""),
@@ -149,7 +149,6 @@ def _collect_domain_controllers(self, root):
149149
"function": rpc.attrib.get("Function", ""),
150150
})
151151
dc_infos.append(dc_info)
152-
# Endpoints: DC name + IPs
153152
if name:
154153
endpoints.append(Endpoint(host=name))
155154
endpoints.extend(Endpoint(host=ip) for ip in ips)
@@ -244,23 +243,19 @@ def _is_dc_specific_risk(risk_id: str, model: str = "", rationale: str = "") ->
244243
rid = (risk_id or "").strip()
245244
mod = (model or "").strip()
246245
rat = (rationale or "").strip().lower()
247-
# 1) Explicit prefixes commonly used by PingCastle for DC-specific checks
248246
dc_prefixes = ("A-DC-", "S-DC-")
249247
if rid.startswith(dc_prefixes):
250248
return True
251-
# 2) Known DC-specific RiskIds (extend as needed)
252249
dc_specific_ids = {
253250
"A-DC-Spooler",
254251
"A-DC-Coerce",
255-
"A-AuditDC", # audit policy on domain controllers
256-
"S-DC-SubnetMissing", # DC subnet declaration incomplete
252+
"A-AuditDC",
253+
"S-DC-SubnetMissing",
257254
}
258255
if rid in dc_specific_ids:
259256
return True
260-
# 3) Model hints: "Audit" with a DC-focused RiskId, or PassTheCredential but DC scoped
261257
if mod == "Audit" and rid.endswith("DC"):
262258
return True
263-
# 4) Rationale heuristic: mentions DC presence/quantity
264259
dc_markers = (
265260
" from ",
266261
" dc",
@@ -270,7 +265,6 @@ def _is_dc_specific_risk(risk_id: str, model: str = "", rationale: str = "") ->
270265
)
271266
return bool(any(marker in rat for marker in dc_markers))
272267

273-
# --- minimal contextual bumping ---
274268
def _apply_contextual_bump(self, severity: str, category: str = "", model: str = "",
275269
risk_id: str = "", rationale: str = "") -> str:
276270
"""
@@ -280,22 +274,22 @@ def _apply_contextual_bump(self, severity: str, category: str = "", model: str =
280274
- If DC-specific -> bump by 1 level.
281275
- If category is 'Exposure' -> bump by 1 level.
282276
"""
283-
current = severity
284-
idx = self._SEVERITY_ORDER.index(current)
277+
if not severity or severity not in self._SEVERITY_ORDER:
278+
severity = "Info"
279+
idx = self._SEVERITY_ORDER.index(severity)
285280
rat = (rationale or "").lower()
286281
cat = (category or "").strip().lower()
287282

288-
# CVE present
289283
if self.CVE_REGEX.search(rationale or ""):
290284
idx = min(idx + 1, len(self._SEVERITY_ORDER) - 1)
291285
mitigation_markers = ("mitigation", "not set", "disabled", "missing", "not enabled", "enable")
292286
if any(m in rat for m in mitigation_markers):
293287
idx = max(idx, self._SEVERITY_ORDER.index("Medium"))
294288

295-
# DC-specific
296289
if self._is_dc_specific_risk(risk_id, model, rationale):
297290
idx = min(idx + 1, len(self._SEVERITY_ORDER) - 1)
298291

299-
# Exposure category
300292
if cat == "exposure":
301293
idx = min(idx + 1, len(self._SEVERITY_ORDER) - 1)
294+
295+
return self._SEVERITY_ORDER[idx]

unittests/tools/test_pingcastle_parser.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ def test_many_findings(self):
3535
spooler = next((f for f in findings if f.vuln_id_from_tool == "A-DC-Spooler"), None)
3636
self.assertIsNotNone(spooler)
3737
self.assertEqual(spooler.title, "[PingCastle] A-DC-Spooler (Anomalies/PassTheCredential)")
38-
self.assertEqual(spooler.severity, "Medium")
38+
self.assertEqual(spooler.severity, "High")
3939
self.assertTrue(len(getattr(spooler, "unsaved_endpoints", [])) > 0)
4040
ds_heuristics = next((f for f in findings if f.vuln_id_from_tool == "A-DsHeuristicsLDAPSecurity"), None)
4141
self.assertIsNotNone(ds_heuristics)
4242
self.assertEqual(ds_heuristics.title, "[PingCastle] A-DsHeuristicsLDAPSecurity (Anomalies/Reconnaissance)")
43-
self.assertEqual(ds_heuristics.severity, "Info")
43+
self.assertEqual(ds_heuristics.severity, "Medium")
4444
self.assertTrue(
4545
hasattr(ds_heuristics, "unsaved_vulnerability_ids")
4646
and len(ds_heuristics.unsaved_vulnerability_ids) >= 1,
4747
)
4848
coerce = next((f for f in findings if f.vuln_id_from_tool == "A-DC-Coerce"), None)
4949
self.assertIsNotNone(coerce)
50-
self.assertEqual(coerce.severity, "Medium")
50+
self.assertEqual(coerce.severity, "High")

0 commit comments

Comments
 (0)