
Controlled NGINX HTTP/2 frame injection lab for CVE-2026-42926 patch validation and defensive research
A controlled cybersecurity lab for validating and comparing behavior related to CVE-2026-42926, an HTTP/2 frame injection issue affecting specific NGINX versions when a vulnerable proxy configuration is used.
This repository is intended for defensive research, patch validation, configuration auditing, and controlled lab reproduction only.
Classification: HTTP/2 frame injection Affected versions: NGINX
1.29.4through1.30.0Fixed versions: NGINX1.30.1+/1.31.0+
Use this project only in an isolated lab environment that you own or are explicitly authorized to test.
Do not run this against third-party systems, public infrastructure, shared environments, or production services without written authorization.
Recommended isolation:
The lab checks whether a target NGINX binary and configuration match the conditions needed to reproduce the issue, sends a crafted request to the test location, and inspects a controlled upstream logger for evidence that injected HTTP/2 frame-like bytes reached the upstream side.
The normal validation pattern is:
Expected result:
| File | Description |
|---|---|
README.md | Project documentation. |
LICENSE | MIT license. |
Dockerfile | Builds a self-contained lab image with NGINX 1.29.4, PHP CLI/cURL, Python, and the project scripts. |
docker-compose.yml | Starts the vulnerable NGINX service, upstream frame logger, and an optional validation runner. |
cve_2026_42926_lab.php | Main lab validation script. It checks version/config preconditions, sends the crafted request, inspects upstream logs, and returns a verdict. |
nginx_vulnerable.conf | Sample NGINX configuration containing the vulnerable proxying pattern used for both vulnerable and patched comparison runs. |
docker/nginx_vulnerable.docker.conf | Docker-specific NGINX config using the same vulnerable pattern and Compose service discovery. |
nginx_config_verify.sh | Helper that verifies whether a target NGINX config contains the required vulnerable proxy pattern. |
upstream_frame_logger.py | Controlled raw HTTP/2 upstream logger used to capture and inspect frames received from NGINX. |
run_lab_comparison.sh | Orchestrates vulnerable-vs-patched comparison runs. |
.dockerignore | Keeps generated logs and IDE metadata out of Docker build context. |
bashpython3phpFor the containerized workflow:
The sample config listens on port 80, which commonly requires root privileges.
For an unprivileged local lab, change listen 80; in nginx_vulnerable.conf to
an available high port such as 8080, then use the matching target URL in the
PHP command.
Make the shell scripts executable:
chmod +x nginx_config_verify.sh run_lab_comparison.sh
Confirm PHP has cURL support:
php -m | grep -i curl
Confirm each NGINX binary can print its version:
/path/to/nginx -V
The provided nginx_vulnerable.conf contains the required test pattern:
location /exploit {
proxy_pass http://127.0.0.1:8081;
proxy_http_version 2;
proxy_set_body $request_body;
proxy_set_header Host $host;
proxy_set_header Content-Length $content_length;
}
Important details:
proxy_http_version 2 enables HTTP/2 proxying to the upstream logger.proxy_set_body $request_body uses a client-controlled request body.client_max_body_size 20m allows the crafted 16 MiB request body used by the
validation script.127.0.0.1:8081 by default.The Docker-specific config in docker/nginx_vulnerable.docker.conf keeps the
same vulnerable proxy pattern but listens on container port 8080 and proxies
to the Compose service name upstream:8081.
The Dockerfile builds NGINX 1.29.4 from source and installs the PHP/Python
tooling required by the lab. Compose then runs three services from the same
image:
upstream: raw HTTP/2 frame loggernginx: vulnerable NGINX 1.29.4 using docker/nginx_vulnerable.docker.confrunner: one-shot PHP validation commandBuild the lab image:
docker compose build
Start the upstream logger and vulnerable NGINX:
docker compose up -d upstream nginx
Confirm the bundled NGINX version:
docker compose exec nginx nginx -V
Run the validation script inside the Compose network:
docker compose --profile run run --rm runner
The runner uses these in-container arguments:
php /lab/cve_2026_42926_lab.php \
http://nginx:8080/exploit \
/lab/upstream_logs \
/lab/nginx_config_verify.sh \
/usr/local/nginx/sbin/nginx \
/lab/docker/nginx_vulnerable.docker.conf \
/exploit
Generated upstream logs are written to the host directory:
./upstream_logs/
The NGINX service is also exposed to the host at:
http://localhost:8080/version
Stop and remove the lab containers:
docker compose down
Start the controlled upstream logger:
python3 upstream_frame_logger.py 8081 ./upstream_logs
In another terminal, start NGINX with the sample configuration:
/path/to/nginx -c "$PWD/nginx_vulnerable.conf"
Run the validation script:
php cve_2026_42926_lab.php \
http://localhost/exploit \
./upstream_logs \
./nginx_config_verify.sh \
/path/to/nginx \
"$PWD/nginx_vulnerable.conf" \
/exploit
Stop NGINX after the run:
/path/to/nginx -s stop
If you changed NGINX to listen on another port, update the first argument. For example:
php cve_2026_42926_lab.php http://localhost:8080/exploit ./upstream_logs ./nginx_config_verify.sh /path/to/nginx "$PWD/nginx_vulnerable.conf" /exploit
Set paths to the two NGINX binaries and run the comparison harness:
VULNERABLE_NGINX=/usr/local/nginx_1.29.4/sbin/nginx \
PATCHED_NGINX=/usr/local/nginx_1.30.1/sbin/nginx \
bash run_lab_comparison.sh
Optional configuration overrides:
VULNERABLE_NGINX=/path/to/vulnerable/nginx \
PATCHED_NGINX=/path/to/patched/nginx \
VULNERABLE_CONFIG="$PWD/nginx_vulnerable.conf" \
PATCHED_CONFIG="$PWD/nginx_vulnerable.conf" \
bash run_lab_comparison.sh
The comparison script writes:
vulnerable_result.txtpatched_result.txtupstream_logs_vulnerable/upstream_logs_patched/Use nginx_config_verify.sh directly when you only want to inspect whether a
configuration contains the vulnerable pattern:
./nginx_config_verify.sh /path/to/nginx "$PWD/nginx_vulnerable.conf" /exploit
The helper checks for:
proxy_http_version 2proxy_set_body with a variableclient_max_body_size of at least 16 MiBcve_2026_42926_lab.php accepts positional arguments:
php cve_2026_42926_lab.php <target_url> <upstream_log_dir> <config_script> <nginx_binary> <nginx_config> <location> [version_url]
Defaults: