## Summary

Fledge 3.1.0 is vulnerable to authenticated OS command injection in the production backup-upload REST handler. An administrator can upload a syntactically valid `.tar.gz` archive whose first member starts with `fledge_backup_`, ends with `.db`, and contains shell metacharacters between those accepted strings. `upload_backup()` extracts that member, concatenates its path into `cp {source} {backup_path}`, and executes the result with `os.system()`. A real Fledge service consequently runs the embedded shell command as its `fledge` service user.

## Impact

- **Product/component:** `fledge-iot/fledge`, specifically `python/fledge/services/core/api/backup_restore.py::upload_backup()` and `POST /fledge/backup/upload`.
- **Affected release tested:** Fledge **3.1.0**, tag and commit `f90ffc2047ee49a380ada98a59fcc2985bd6a943` (database schema 75). The ticket reports versions through 3.1.0 as affected.
- **Required attacker position:** An authenticated Fledge administrator with access to the backup-upload endpoint.
- **Risk:** High. The administrator can execute arbitrary local shell commands with the identity and filesystem access of the Fledge service account. In the reproduced deployment that identity was UID 10001, user/group `fledge`.
- **Consequence demonstrated:** Deterministic command execution through the network API, including attacker-selected file creation and content.

## Impact Parity

- **Disclosed/claimed maximum impact:** Authenticated remote OS command execution as the Fledge service user.
- **Reproduced impact:** Two fresh vulnerable Fledge 3.1.0 service instances accepted authenticated crafted uploads. Each spawned `/bin/sh -c` with the attacker-controlled member filename and created a distinct attacker-selected marker as `fledge`.
- **Parity:** `full`.
- **Not demonstrated:** No privilege escalation beyond the Fledge service account, lateral movement, persistence, or outbound network behavior was attempted or required. These are not part of the claimed maximum impact.

## Root Cause

The handler applies validation to two different filename layers:

1. The outer multipart filename must start with `fledge_backup_` and end in `.tar.gz`.
2. At least one archive member must start with `fledge_backup_` and end in `.db` or `.dump`.

For archives compatible with older Fledge versions, the handler calls `tar_file.extractall(temp_path)`, assigns `backup_file_name = tar_file_names[0]`, and builds `source = temp_path + "/" + backup_file_name`. The prefix/suffix checks do not reject shell syntax inside the accepted member name. The vulnerable code then performs:

```python
cmd = "cp {} {}".format(source, backup_path)
ret_code = os.system(cmd)
```

`os.system()` invokes a command shell. Consequently, characters such as `;`, redirections, and `#` in `source` become shell syntax instead of literal filename bytes. The proof member retains the accepted prefix and `.db` suffix while placing `;printf ...;sleep 2;#` between them. The generated shell command runs `printf`, and the comment suppresses the remainder of the `cp` command.

No upstream fixed release or fix commit was identified in the ticket or the tested repository state. The reproduction therefore labels its negative control as a **same-version patched control**, not as an upstream fixed version. That control changes only the unsafe copy block to `shutil.copy2(source, backup_path)`. It accepts the same request and copies the literal filename without interpreting its metacharacters, proving that the shell sink is causal.

## Reproduction Steps

1. Run `bundle/repro/reproduction_steps.sh` from any directory. The script resolves `PRUVA_ROOT`, uses `/pruva/project-cache/repo` when the prepared cache contract permits it, and otherwise falls back to `bundle/artifacts/fledge`.
2. The script checks out exact commit `f90ffc2047ee49a380ada98a59fcc2985bd6a943`, verifies the vulnerable source statements, and builds the real Fledge product in a pinned Ubuntu 22.04 base image. Compilation is bounded to two jobs. The runtime includes the normal rsyslog socket and starts Fledge through its shipped `bin/fledge start` flow.
3. It generates deterministic minimal archives at runtime and exercises:
   - one normal valid backup upload (marker must remain absent);
   - two fresh vulnerable service instances with distinct crafted archives and markers;
   - two fresh same-version patched-control instances with the exact corresponding archive bytes (requests succeed, literal files are copied, and markers remain absent).
4. Each service must become healthy at `/fledge/ping`, report version 3.1.0 with mandatory authentication, and accept a normal `admin` login before the upload.
5. The vulnerable requests must return HTTP 200, produce service debug logs showing the generated `cp` command, produce `strace` evidence of `/bin/sh -c`, show the shell as a child of the Fledge core process, and create markers owned by `fledge`.
6. Expected final output includes:

```text
[+] CONFIRMED: authenticated remote OS command execution through Fledge 3.1.0 backup upload API
[+] Vulnerable markers: PRUVA_CVE_2026_71284_ATTEMPT_1, PRUVA_CVE_2026_71284_ATTEMPT_2
[+] Benign and same-version patched controls reached the endpoint without marker execution
```

The final script was executed twice consecutively and returned exit status 0 on both runs.

## Evidence

Current-run evidence is under `bundle/logs/repro/cve-2026-71284/`; `bundle/repro/runtime_manifest.json` binds every finalized artifact to its SHA-256.

Key artifacts include:

- `proof_summary.json` — concise result, exact commit and image identities, four fresh process/container identities, two successful vulnerable markers, and negative-control outcomes.
- `source_identity.json` — repository, v3.1.0 commit, vulnerable file digest, and canonical source target digest.
- `runtime_image_identity.txt` — pinned base, vulnerable image ID, patched-control image ID, and Docker inspection output.
- `vulnerable_1_health_response.json` and `vulnerable_2_health_response.json` — real service health, version 3.1.0, and mandatory authentication.
- `vulnerable_{1,2}_login_request.json` / `login_response.json` — normal administrator authentication with current tokens redacted from retained proof.
- `vulnerable_{1,2}_upload_request.json` / `upload_response.json` — authenticated production endpoint transactions.
- `vulnerable_{1,2}_service.log` — handler debug output containing attacker-controlled `source` and the constructed shell command.
- `vulnerable_{1,2}_execve_trace.log` and `shell_lineage.log` — syscall/process evidence. A representative excerpt is:

```text
execve("/bin/sh", ["sh", "-c", "cp /var/lib/fledge/upload/fledge_backup_...db;printf PRUVA_CVE_2026_71284_ATTEMPT_1 >/tmp/pruva_cve_2026_71284_attempt_1;sleep 2;#.db /var/lib/fledge/backup"], ...)
```

- `vulnerable_1_process_tree_during_attack.txt` — live lineage showing PID 155 `python3 -m fledge.services.core` (user `fledge`) parenting PID 579 `sh -c ...`, which parents `sleep 2`.
- `vulnerable_{1,2}_marker.txt` — exact attacker-selected marker bytes.
- `vulnerable_{1,2}_marker_stat.txt` — marker ownership and mode. Both record `uid=10001 user=fledge ... group=fledge`.
- `benign_control_*` — a conventional valid backup succeeds without a marker and appears in the backup directory.
- `patched_{1,2}_negative_control.json`, `patched_{1,2}_execve_trace.log`, `patched_{1,2}_service.log`, and `patched_{1,2}_backup_listing.txt` — the same crafted archive reaches the same endpoint in the same release, but no attacker command is passed to `execve`; the source is copied as a literal filename and no marker appears.
- `patched_control_source.txt` — exact behavior-preserving control statement using `shutil.copy2`.
- `bundle/logs/reproduction_steps.log` and `bundle/logs/fledge-image-build.log` — diagnostics for the final run; these are not hashed as immutable proof while active.

The final runtime manifest records `entrypoint_kind="endpoint"`, `service_started=true`, `healthcheck_passed=true`, `target_path_reached=true`, exact source commit, source target digest, vulnerable runtime image digest, Linux/x86-64 platform, and 101 finalized proof artifacts.

## Recommendations / Next Steps

1. Remove the shell from the copy operation. Prefer `shutil.copy2(source, backup_path)` or another API that treats source and destination as literal path arguments. If an external utility is unavoidable, invoke it with an argument vector and `shell=False`; do not build a shell command string.
2. Validate and normalize every archive member before extraction. Reject absolute paths, `..` traversal, links, device entries, control characters, and filenames outside a narrowly defined grammar. Extract only explicitly accepted members rather than calling unrestricted `extractall()`.
3. Do not rely solely on `startswith()`/`endswith()` for a security boundary. A suitable allowlist should constrain the entire backup basename, for example a known timestamp pattern and the exact `.db`/`.dump` extension.
4. Add production-path tests that upload names containing `;`, `$()`, backticks, quotes, whitespace, redirection operators, glob characters, newlines, and traversal sequences. Assert no child shell is created and that literal-safe backups still work.
5. Add a regression test using the same crafted archive against the real authenticated API and monitor process ancestry (`Fledge core -> shell`) to ensure the unsafe behavior is gone.
6. Until an upstream release containing a verified fix exists, restrict the endpoint to trusted administrators, isolate the service account, minimize writable paths and privileges, and alert on shell processes spawned by the Fledge core service.

## Additional Notes

- **Idempotency:** Confirmed. The script removes prior evidence/work directories and uniquely names containers; it completed successfully twice in succession. Each individual final run itself used two clean vulnerable and two clean patched-control service instances.
- **Real boundary:** The proof uses the compiled Fledge 3.1.0 core and storage service, normal product initialization, `/fledge/login`, and `POST /fledge/backup/upload` over localhost TCP. It does not invoke `upload_backup()` directly and uses no mock handler.
- **Sanitizers:** None used. The success oracle is real command execution, not a sanitizer or crash.
- **Authentication:** Fledge's normal first-install administrator credential was used only inside the isolated containers. Retained request evidence redacts the credential and retained responses redact JWTs.
- **Container capabilities:** `SYS_PTRACE` and an unconfined seccomp profile are used only so `strace` can capture child process execution. They do not enable the injection, change product parsing, or create the marker; vulnerable execution was independently observed before adding tracing.
- **Upstream status:** No released fixed version is asserted. The negative control is explicitly source-modified Fledge 3.1.0 for causal verification.
- **Limitations:** The proof requires a working Docker daemon and network access on an uncached first build to obtain the pinned base and package dependencies. It performs no outbound access from the exploit payload.
