Skip to content

[medium] Match values with a literal asterisk with a regex - #84

Open
elhoim wants to merge 1 commit into
SigmaHQ:mainfrom
elhoim:fix/escaped-wildcard-literal
Open

elhoim wants to merge 1 commit into
SigmaHQ:mainfrom
elhoim:fix/escaped-wildcard-literal

Conversation

@elhoim

@elhoim elhoim commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

BLUF

  • Problem: a Sigma value with an escaped (literal) asterisk, e.g. '/tn \*' or '\\\\\*\\IPC$', is emitted as \* in the search string. Splunk's search command treats every * as a wildcard, and the Splunk docs (Search Manual, "Backslashes") say a backslash cannot escape an asterisk. So f="lit\*star" matches lit*star, litstar and litXYZstar alike.
  • Detection impact: 27 SigmaHQ rules (46 values) look for a literal *, and they turn into much broader searches (false positives):
    • proc_creation_win_schtasks_delete_all ("delete all scheduled tasks", /tn *) matches the deletion of any task.
    • The ShareName: \\*\IPC$ rules (win_security_susp_psexec, ..._atsvc_task, ..._lm_namedpipe, ...) match every \\HOST\IPC$.
    • -Filter * in the Get-ADUser/Get-ADComputer discovery rules matches any filter.
  • Fix: convert these values to an anchored, case-insensitive regular expression and pass them through the backend's existing regex handling. That is | regex, or rex/eval inside an OR, the same path as |re. Keywords are matched against _raw. IN lists are only skipped when one of their values has a literal asterisk. Values without a literal asterisk are converted exactly as before.
  • Tests: 8 new tests. All existing tests are unchanged. Before and after were validated on a live Splunk Enterprise 10.4.3.

Priority: medium

Details

Before and after for the real rule proc_creation_win_schtasks_delete_all.yml, converted by main and by this branch and run on Splunk 10.4.3 against 3 events:

main:   Image="*\\schtasks.exe" CommandLine="* /delete *" CommandLine="*/tn \**" CommandLine="* /f*"
        -> schtasks.exe /delete /tn * /f
           schtasks.exe /delete /tn \Microsoft\Office\UpdateTask /f
           schtasks.exe /delete /tn MyBackupJob /f
branch: Image="*\\schtasks.exe" CommandLine="* /delete *" CommandLine="* /f*"
        | regex CommandLine="(?i)^.*/tn \\*.*$"
        -> schtasks.exe /delete /tn * /f

Splunk also warns about the current output: "The term 'f="lit*star"' contains a wildcard in the middle of a word or string." For keywords the current output is worse: "lit\*star" did not match an event containing lit*star at all, but did match litXstar and litstar.

Notes on the change (sigma/backends/splunk/splunk.py):

  • _has_literal_asterisk() looks for a * inside the plain-string parts of a SigmaString. Wildcards are SpecialChars and are not affected.
  • convert_condition_field_eq_val_str() converts such a value with SigmaString.to_regex(), anchors it with ^...$ and adds the i flag, because Sigma string matching and Splunk search are case-insensitive. It then hands the value to convert_condition_field_eq_val_re(). That method already does the regex/regex != (NOT) and the rex + eval + fieldCondition="true" (OR) handling.
  • convert_condition_val_str() does the same for keywords, unanchored, on _raw.
  • decide_convert_condition_as_in_expression() returns False when one of the values has a literal asterisk. Otherwise the value would stay inside IN (...) as a wildcard.
  • A list of several literal-asterisk values on one field expands into one rex/eval pair per value, as |re lists do today. For example, posh_ps_get_process_security_software_discovery gives 9 pairs.
  • The data_model (tstats) output uses the same deferred | regex path as |re.

Testing

New tests in tests/test_backend_splunk.py:

  • equals, contains, and backslashes combined with a literal asterisk (\\*\IPC$)
  • NOT
  • a value list (OR path), and a list without a literal asterisk that still uses IN
  • a keyword
  • data_model output

Live validation on Splunk Enterprise 10.4.3 (container). The events were ingested with the field f set to lit*star, litXstar, lit\star, litstar, lit\*star, lit\Xstar, litXYZstar, LIT*STAR, \\*\IPC$ and \\HOST\IPC$, and the queries were converted by this branch:

Case main matches branch matches
f: 'lit\*star' 9 events (every lit...star) lit*star, LIT*STAR
f: '\\\\\*\\IPC$' \\*\IPC$, \\HOST\IPC$ \\*\IPC$
f|contains: 'it\*st' 9 events lit*star, LIT*STAR
not f: 'lit\*star' only the 2 IPC$ events everything except lit*star, LIT*STAR
f: ['lit\*star', 'litXYZstar'] (OR) 9 events the literal ones and litXYZstar
keyword 'lit\*star' litXstar, lit\star, litstar, ... but not lit*star the events that contain lit*star
keywords ['lit\*star', 'IPC$'] (rex field=_raw) n/a the lit*star and IPC$ events
three literal-asterisk values on one field n/a correct, with 3 rex/eval pairs

All 27 SigmaHQ rules with a literal asterisk convert without errors in the default, savedsearches and data_model formats. The SPL2 backend is a separate class and is not affected.

The steps from .github/workflows/test.yml were run locally with poetry install (the locked pySigma 1.2.0):

  • pytest --cov=sigma: 159 passed on Python 3.10, 3.11, 3.12 and 3.13.
  • Coverage is 97.31%.

🤖 Generated with Claude Code

A Sigma value with an escaped asterisk, e.g. '/tn \*' or '\\*\IPC$', was
emitted as `\*` in the search string. The Splunk search command treats
every asterisk as a wildcard and documents that a backslash cannot
escape it, so `f="lit\*star"` also matches `litstar` and `litXYZstar`.
Rules that look for a literal asterisk turned into much broader
searches, e.g. `schtasks /delete /tn *` (delete all tasks) matched the
deletion of any task and `\\*\IPC$` matched every host's IPC$ share.

Convert such values to an anchored, case-insensitive regular
expression and pass them through the existing regex handling (`regex`
command, or `rex`/`eval` inside OR). Keywords are matched against
_raw. Values without a literal asterisk are converted as before, and
IN lists are only skipped when one of their values has a literal
asterisk.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant