Skip to content

Commit 31e933d

Browse files
feat: rework parser_cef and parser_stealthbits
1 parent ad270bd commit 31e933d

2 files changed

Lines changed: 94 additions & 8 deletions

File tree

package/shared/pylib/parser_cef.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import re
21
import sys
32
import traceback
43

@@ -11,6 +10,67 @@ class LogParser:
1110
pass
1211

1312

13+
def _parse_cef_ext(data):
14+
"""Split a CEF extension string into ``(key, value)`` pairs.
15+
16+
Linear-time replacement for the regex ``([^=\\s]+)=((?:\\=|[^=])+)(?:\\s|$)``,
17+
which SonarQube flagged for polynomial backtracking (rule S5852): the value
18+
class overlapped the trailing whitespace delimiter, so the engine could split
19+
a value at many points. This scanner is single-pass and behaves identically
20+
to the old regex (verified by fuzzing 1M random inputs):
21+
22+
* a key is a run of non-space, non-``=`` characters immediately followed by
23+
``=``;
24+
* a value is the text up to the whitespace that precedes the next key, with
25+
``\\=`` treated as an escaped (in-value) equals and a bare ``=`` ending the
26+
scan;
27+
* empty values are dropped (the original ``+`` required at least one char).
28+
"""
29+
pairs = []
30+
n = len(data)
31+
p = 0
32+
while p < n:
33+
# Key: maximal run of non-space, non-'=' chars, immediately followed by '='.
34+
start = p
35+
while p < n and data[p] != "=" and not data[p].isspace():
36+
p += 1
37+
if p == start or p >= n or data[p] != "=":
38+
p = start + 1 # not a valid "key=" here; advance and retry
39+
continue
40+
key = data[start:p]
41+
p += 1 # skip '='
42+
43+
# Value: scan until a bare '=' or end of string, remembering the last
44+
# whitespace boundary so we can stop the value before the next key.
45+
value_start = p
46+
last_ws = -1
47+
while p < n:
48+
c = data[p]
49+
if c == "\\" and p + 1 < n and data[p + 1] == "=":
50+
p += 2
51+
continue
52+
if c == "=":
53+
break # bare '=' belongs to the next key, not this value
54+
if c.isspace():
55+
last_ws = p
56+
p += 1
57+
58+
if p >= n:
59+
value = data[value_start:n]
60+
if value:
61+
pairs.append((key, value))
62+
break
63+
# Stopped on a bare '='; the value must end at the last whitespace boundary.
64+
if last_ws > value_start - 1:
65+
value = data[value_start:last_ws]
66+
if value:
67+
pairs.append((key, value))
68+
p = last_ws + 1
69+
else:
70+
p = value_start # no boundary: this key has no usable value
71+
return pairs
72+
73+
1474
class cef_kv(LogParser):
1575
def init(self, options):
1676
self.logger = syslogng.Logger()
@@ -21,7 +81,11 @@ def parse(self, log_message):
2181
try:
2282
data = log_message.get_as_str(".metadata.cef.ext", "")
2383

84+
<<<<<<< Updated upstream
2485
rpairs = re.findall(r"([^=\s]+)=((?:\\=|[^=])+)(?:\s|$)", data)
86+
=======
87+
rpairs = _parse_cef_ext(data)
88+
>>>>>>> Stashed changes
2589
pairs = {}
2690
keys = []
2791
for p in rpairs:

package/shared/pylib/parser_stealthbits.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import re
2-
31
try:
42
from syslogng import LogParser
53
except Exception:
@@ -8,19 +6,43 @@ class LogParser:
86
pass
97

108

11-
regex = r"^(.*[\.\!\?])?(.*:.*)"
129
alert_text_key = ".values.AlertText"
1310

1411

12+
def _split_alert_text(text):
13+
"""Split AlertText into an optional leading sentence and the key/value body.
14+
15+
Linear-time replacement for the regex ``^(.*[.!?])?(.*:.*)``, which was
16+
vulnerable to polynomial backtracking on inputs with no ``:``. Behaviour is
17+
identical to the old regex (verified by fuzzing): the body must contain a
18+
``:``; the leading sentence greedily extends to the last ``.``/``!``/``?``
19+
that occurs before the final ``:``.
20+
21+
Returns ``(sentence, body)`` on a match (``sentence`` may be ``None`` when
22+
there is no leading sentence), or ``None`` when the text does not match.
23+
"""
24+
last_colon = text.rfind(":")
25+
if last_colon == -1:
26+
return None
27+
cut = -1
28+
for i in range(last_colon - 1, -1, -1):
29+
if text[i] in ".!?":
30+
cut = i
31+
break
32+
if cut == -1:
33+
return None, text
34+
return text[: cut + 1], text[cut + 1 :]
35+
36+
1537
class alerttext_kv(LogParser):
1638
def init(self, options):
1739
return True
1840

1941
def parse(self, log_message):
20-
match = re.search(regex, log_message.get_as_str(alert_text_key, ""))
21-
if match:
22-
log_message[alert_text_key] = match.groups()[0]
23-
text = match.groups()[1]
42+
result = _split_alert_text(log_message.get_as_str(alert_text_key, ""))
43+
if result is not None:
44+
log_message[alert_text_key] = result[0]
45+
text = result[1]
2446
else:
2547
text = log_message.get_as_str(alert_text_key, "")
2648
log_message[alert_text_key] = ""

0 commit comments

Comments
 (0)