## https://sploitus.com/exploit?id=EADB77CD-DA9A-5377-820A-B7574B833CE4
# CVE-2026-42926 NGINX HTTP/2 Frame Injection Lab
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.4` through `1.30.0`
> Fixed versions: NGINX `1.30.1+` / `1.31.0+`
## Safety Notice
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:
- Local VM
- Disposable container
- Private test host
- Non-routable lab network
## What This Lab Does
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:
1. Run the same vulnerable proxy configuration with a vulnerable NGINX build.
2. Run the same vulnerable proxy configuration with a patched NGINX build.
3. Compare the upstream evidence and script verdicts.
Expected result:
- Vulnerable build: injection evidence may be observed.
- Patched build: no injection evidence should be observed.
## Repository Contents
| 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. |
## Requirements
- Linux or macOS shell environment
- `bash`
- `python3`
- `php`
- PHP cURL extension
- NGINX test binaries for the versions you want to compare
- Permission to bind the configured NGINX listen port
For the containerized workflow:
- Docker
- Docker Compose v2
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.
## Setup
Make the shell scripts executable:
```bash
chmod +x nginx_config_verify.sh run_lab_comparison.sh
```
Confirm PHP has cURL support:
```bash
php -m | grep -i curl
```
Confirm each NGINX binary can print its version:
```bash
/path/to/nginx -V
```
## Lab Configuration
The provided `nginx_vulnerable.conf` contains the required test pattern:
```nginx
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.
- The upstream logger listens on `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`.
## Docker Quick Start: NGINX 1.29.4 Lab
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 logger
- `nginx`: vulnerable NGINX `1.29.4` using `docker/nginx_vulnerable.docker.conf`
- `runner`: one-shot PHP validation command
Build the lab image:
```bash
docker compose build
```
Start the upstream logger and vulnerable NGINX:
```bash
docker compose up -d upstream nginx
```
Confirm the bundled NGINX version:
```bash
docker compose exec nginx nginx -V
```
Run the validation script inside the Compose network:
```bash
docker compose --profile run run --rm runner
```
The runner uses these in-container arguments:
```text
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:
```text
./upstream_logs/
```
The NGINX service is also exposed to the host at:
```text
http://localhost:8080/version
```
Stop and remove the lab containers:
```bash
docker compose down
```
## Quick Start: Single Validation Run
Start the controlled upstream logger:
```bash
python3 upstream_frame_logger.py 8081 ./upstream_logs
```
In another terminal, start NGINX with the sample configuration:
```bash
/path/to/nginx -c "$PWD/nginx_vulnerable.conf"
```
Run the validation script:
```bash
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:
```bash
/path/to/nginx -s stop
```
If you changed NGINX to listen on another port, update the first argument. For
example:
```bash
php cve_2026_42926_lab.php http://localhost:8080/exploit ./upstream_logs ./nginx_config_verify.sh /path/to/nginx "$PWD/nginx_vulnerable.conf" /exploit
```
## Quick Start: Vulnerable vs Patched Comparison
Set paths to the two NGINX binaries and run the comparison harness:
```bash
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:
```bash
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.txt`
- `patched_result.txt`
- `upstream_logs_vulnerable/`
- `upstream_logs_patched/`
## Manual Configuration Check
Use `nginx_config_verify.sh` directly when you only want to inspect whether a
configuration contains the vulnerable pattern:
```bash
./nginx_config_verify.sh /path/to/nginx "$PWD/nginx_vulnerable.conf" /exploit
```
The helper checks for:
- `proxy_http_version 2`
- `proxy_set_body` with a variable
- `client_max_body_size` of at least 16 MiB
## Script Arguments
`cve_2026_42926_lab.php` accepts positional arguments:
```text
php cve_2026_42926_lab.php [version_url]
```
Defaults:
| Argument | Default |
| --- | --- |
| `target_url` | `http://localhost/exploit` |
| `upstream_log_dir` | `./upstream_logs` |
| `config_script` | `./nginx_config_verify.sh` |
| `nginx_binary` | `nginx` |
| `nginx_config` | `/etc/nginx/nginx.conf` |
| `location` | `/exploit` |
| `version_url` | `http://localhost/version` |
## Verdicts and Exit Codes
`cve_2026_42926_lab.php` returns one of three verdicts:
| Verdict | Meaning | Exit code |
| --- | --- | --- |
| `positive` | Frame injection evidence was observed and correlated to the run. | `0` |
| `negative` | Preconditions were met and no injection evidence was observed. | `1` |
| `inconclusive` | One or more preconditions or evidence checks failed. | `2` |
Positive evidence requires the script to correlate the run marker, observed
upstream frame header, injected-frame flag, and affected NGINX version.
## Output Artifacts
The upstream logger writes JSON files named like:
```text
frames_.json
```
Each log contains parsed HTTP/2 frame metadata, payload excerpts, byte offsets,
injection detection flags, and run-correlation fields.
The comparison harness stores separate log directories for vulnerable and
patched runs so evidence from the two executions does not mix.
## Troubleshooting
If the PHP script reports that the config helper is not executable, run:
```bash
chmod +x nginx_config_verify.sh
```
If the request returns `413 Request Entity Too Large`, increase
`client_max_body_size` to at least `16m`; the sample config uses `20m`.
If no upstream logs are created, check that:
- `upstream_frame_logger.py` is running.
- NGINX is proxying to `127.0.0.1:8081`.
- The target URL points to the configured NGINX listener.
- The request reached the `/exploit` location.
If NGINX fails to bind to port `80`, either run it with appropriate privileges
in a lab environment or change the config to a high port such as `8080`.
If the comparison result is inconclusive, inspect `vulnerable_result.txt`,
`patched_result.txt`, and the corresponding upstream log directories for the
failed precondition.
## License
This project is licensed under the MIT License. See `LICENSE` for details.