By Cheng Tan
CVE-2026-42945, CVSS 9.2, affecting Nginx 0.6.27 to 1.30.0, is an 18-year-old heap overflow vulnerability. It is not an exquisite chain of exploit, but rather a most simple oversight of state management. But it is this very "rookie mistake" that gives us an opportunity to reexamine the security design philosophy of gateways.
Nginx's rewrite and set directives are not simple string substitutions. They are compiled into a series of opcodes and executed by Nginx's internal script engine. This engine uses a classic performance optimization design—two-pass execution:
This design avoids repeated reallocations, which is a very reasonable optimization at the C language level. But it has an implicit prerequisite: the engine state seen by both passes must be completely identical.
Consider this extremely common Nginx configuration:
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /internal?migrated=true;
set $original_endpoint $1;
}
The replacement string of rewrite contains a ?. When Nginx sees the ?, it assumes the subsequent part is a query string, so it calls ngx_http_script_start_args_code() and permanently sets the engine's e->is_args flag to 1.
Next, set $original_endpoint $1 is executed. This references the regex capture group $1, triggering ngx_http_script_complex_value_code(). Here comes the crucial part—in order to calculate the length of the variable value, this function creates a brand new, zero-initialized sub-enginele:
ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); // Completely zeroed out
le.ip = code->lengths->elts;
Because le.is_args is 0, the length calculation goes down the "do not escape" branch and returns the original length.
However, the copy phase uses the main enginee, whose is_args is still 1. Consequently, the copy code goes down the "needs escaping" branch, expanding characters like +, &, and = in the URI from 1 byte to 3 bytes (such as + → %2B).
A buffer of raw_size is allocated, but raw_size + 2*N bytes of data are written. Heap overflow.
This question is more interesting than the vulnerability itself:
?, set references a capture group, and the request URI contains a large number of characters that need to be escaped (+&=). If any is missing, no overflow occurs.The implicit contract of the state machine was broken by a new feature, and this contract was never written down. When writing the rewrite engine in 2008, the semantics of is_args were "currently processing the query string part," which did not need to be reset once set—because the complex value logic would not be entered again within the same processing flow. Later, support for capture group references in the set directive broke this assumption.
Nginx's rewrite, set, if, and other directives are essentially simulating an imperative programming language using a configuration language. It has variable assignment, regex capturing, conditional branches, loops (last/break), and even an implicit state machine.
This design is successful in terms of flexibility—you can implement almost any request processing logic using nginx.conf. But it also introduces fundamental problems:
The interaction effect between directives is unpredictable.rewrite changes the engine state, and set reads the modified state, with no documentation or mechanism to constrain this cross-directive state propagation. This is not unique to Nginx; any system trying to stuff programming capabilities into a configuration language will encounter it—it's just that here in Nginx, the consequence is RCE.
Envoy chose a completely different path. Its configuration is declarative:
route:
match:
regex: "^/api/(.*)$"
rewrite:
regex_rewrite:
pattern:
regex: "^/api/(.*)$"
substitution: "/internal/\\1"
No variables, no assignments, no state machines. Each routing rule is independent and self-contained. The match and substitution of rewrite are completed in a single rule, eliminating the possibility of "first rewrite modifies the global state, then set reads the dirty state."
This design fundamentally eliminates the attack surface of state-leakage vulnerabilities. Envoy's route configuration does not need to maintain engine state across rules, so the two-pass inconsistency problem naturally does not exist.
However, declarative configuration also comes at a price—insufficient flexibility:
query_parameters is merely a match condition, not a rewriting tool.For simple routing rewrites, Envoy is more than sufficient. But for complex configurations migrated from Nginx, especially those scenarios depending on rewrite + set + capture group passing, Envoy's native route configuration is inadequate.
Higress's WASM plugin mechanism provides an elegant solution—since imperative configuration has state management hazards and declarative configuration is not flexible enough, let's use real code to solve the problem.
Taking the equivalent capabilities of nginx rewrite + set as an example, the implementation of a Higress WASM plugin would look something like this:
func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig) types.Action {
// 1. Get request path
path, _ := proxywasm.GetHttpRequestHeader(":path")
pathPart, query := splitPathQuery(path)
// 2. Regex matching
for _, rule := range config.Rules {
matches := rule.Regex.FindStringSubmatch(pathPart)
if matches == nil {
continue
}
// 3. Construct new path (replace capture groups)
newPath := expandCaptures(rule.Replacement, matches)
// 4. Handle query string
newQuery := mergeQuery(query, rule.QueryAppend, rule.QueryTemplate, matches)
// 5. Save variables (equivalent to nginx set)
for _, v := range rule.SetVars {
value := matches[v.CaptureGroup]
// For subsequent plugins
proxywasm.SetProperty([]string{v.Name}, []byte(value))
// For upstream services
proxywasm.AddHttpRequestHeader("X-Rewrite-"+v.Name, url.QueryEscape(value))
}
// 6. Write back modified path
fullPath := joinPathQuery(newPath, newQuery)
proxywasm.ReplaceHttpRequestHeader(":path", fullPath)
if rule.Break {
break
}
}
return types.ActionContinue
}
What this code does is completely equivalent to Nginx's rewrite + set, but with several fundamental differences:
Each time a request comes in, the plugin function is called once. The path is read once, regex matching is performed once, the new path is calculated, and it is written back. There is no "first calculate length then copy" two-pass design, so the possibility of two-pass state inconsistency naturally does not exist.
This is the most critical point. WASM plugins run in a sandboxed virtual machine:
In contrast to Nginx's C modules—any memory error occurs directly within the address space of the worker process, where a heap overflow can directly overwrite adjacent function pointers, making RCE the natural attack path.
Nginx directives have implicit state propagation (the is_args flag is an example), which is neither documented nor easy to deduce from the configuration text.
In WASM plugins, all logic is explicit Go code. The assignment and passage of variables are clear at a glance, and there is no possibility of "the side effects of one directive quietly affecting the behavior of another." Code review and testing are much easier than auditing Nginx configurations.
Starting from this vulnerability, we can observe three levels of gateway security architecture:
| Nginx | Envoy Native | Higress WASM | |
|---|---|---|---|
| Configuration Method | Imperative | Declarative | Code (Go/Rust) |
| State Management | Implicit global state machine | Stateless | Explicit local variables |
| Memory Safety | C language, unprotected | C++, but core path is safe | WASM sandbox isolation |
| Flexibility | High (but with security hazards) | Medium | High (and safe) |
| Vulnerability Impact | Worker process RCE | Configuration analysis bug | Only the current request fails |
This is not to say Envoy or Higress is free of vulnerabilities. All software has bugs. But different architectural designs determine the blast radius of a vulnerability:
CVE-2026-42945 will not be the last "security vulnerability hidden within a configuration language." Any system trying to stuff Turing-complete capabilities into a configuration format will face the complexity of state management. Nginx's rewrite module was a reasonable engineering choice back in 2008; 18 years later today, we have better alternatives.
Envoy eliminated the attack surface of state leaks with declarative configuration, but sacrificed flexibility. Higress's WASM plugins, while retaining flexibility, fundamentally restrict the impact scope of vulnerabilities through sandbox isolation.
Replacing directives with code, replacing trust with sandboxes. This might just be the right direction for the evolution of gateway security.
If you are migrating from Nginx to Higress, the Higress community already has an nginx-rewrite-compatible WASM plugin that fully covers all features of rewrite + set, allowing you to directly replace vulnerable Nginx configurations.
719 posts | 58 followers
FollowAlibaba Cloud Native Community - November 14, 2025
Alibaba Cloud Native Community - April 21, 2025
Alibaba Cloud Native - November 9, 2022
Alibaba Cloud Native Community - September 12, 2023
Alibaba Cloud Native Community - February 3, 2026
Alibaba Cloud Native Community - November 26, 2025
719 posts | 58 followers
Follow
Cloud Migration Solution
Secure and easy solutions for moving you workloads to the cloud
Learn More
Security Center
A unified security management system that identifies, analyzes, and notifies you of security threats in real time
Learn More
Oracle Database Migration Solution
Migrate your legacy Oracle databases to Alibaba Cloud to save on long-term costs and take advantage of improved scalability, reliability, robust security, high performance, and cloud-native features.
Learn More
Database Migration Solution
Migrating to fully managed cloud databases brings a host of benefits including scalability, reliability, and cost efficiency.
Learn MoreMore Posts by Alibaba Cloud Native Community