# Root Cause Analysis: CVE-2024-23334

## Vulnerability Summary

**CVE:** CVE-2024-23334
**Package:** aiohttp (pip)
**Vulnerable versions:** >= 1.0.5, < 3.9.2
**Patched version:** 3.9.2
**CWE:** CWE-22 (Path Traversal)
**Severity:** High

## Description

aiohttp's `web.static()` handler allows directory traversal when
`follow_symlinks=True` is configured. Path traversal sequences (`../`)
in the URL can escape the configured static root directory and read
arbitrary files on the server filesystem, even when no symlinks are
present.

## Root Cause

The vulnerability is in `StaticResource._handle()` in
`aiohttp/web_urldispatcher.py`.

### Vulnerable code (aiohttp 3.9.1)

```python
async def _handle(self, request: Request) -> StreamResponse:
    rel_url = request.match_info["filename"]
    try:
        filename = Path(rel_url)
        if filename.anchor:
            raise HTTPForbidden()
        filepath = self._directory.joinpath(filename).resolve()
        if not self._follow_symlinks:
            filepath.relative_to(self._directory)
    except (ValueError, FileNotFoundError) as error:
        raise HTTPNotFound() from error
```

When `follow_symlinks=True`:
1. `filename = Path(rel_url)` creates a Path from the URL filename
   (e.g., `../poc-aiohttp-test.txt`)
2. `filename.anchor` only checks for absolute paths; `../...` is
   relative, so it passes
3. `filepath = self._directory.joinpath(filename).resolve()` joins the
   directory with the traversal path and resolves `..` sequences to
   the real filesystem path outside the static root
4. `if not self._follow_symlinks:` is **False** when
   `follow_symlinks=True`, so the `relative_to(self._directory)`
   validation is **skipped entirely**
5. The code proceeds to serve the file at the escaped path

### Fixed code (aiohttp 3.9.2)

```python
async def _handle(self, request: Request) -> StreamResponse:
    rel_url = request.match_info["filename"]
    try:
        filename = Path(rel_url)
        if filename.anchor:
            raise HTTPForbidden()
        unresolved_path = self._directory.joinpath(filename)
        if self._follow_symlinks:
            normalized_path = Path(os.path.normpath(unresolved_path))
            normalized_path.relative_to(self._directory)
            filepath = normalized_path.resolve()
        else:
            filepath = unresolved_path.resolve()
            filepath.relative_to(self._directory)
    except (ValueError, FileNotFoundError) as error:
        raise HTTPNotFound() from error
```

The fix adds a `relative_to(self._directory)` check **even when**
`follow_symlinks=True`. It normalizes the path with
`os.path.normpath()` first (resolving `..` sequences without
following symlinks), then validates it stays within the directory
before resolving symlinks. If the path escapes the directory,
`relative_to()` raises `ValueError` which is caught and results in
`HTTPNotFound` (404).

## Reproduction Evidence

### Environment
- Python 3.11 (via uv standalone build)
- aiohttp 3.9.1 (vulnerable) and 3.9.2 (fixed)
- Server: `web.static("/static", "static/", follow_symlinks=True)`
- Probe file: `poc-aiohttp-test.txt` placed as a sibling of the static root

### Results

**Vulnerable aiohttp 3.9.1:**
- HTTP GET `/static/../poc-aiohttp-test.txt` with
  `curl --path-as-is` or raw-socket HTTP -> **HTTP 200**, body contains probe content
- Traversal variants include raw `../`, `%2f`, `%2e`, `%2F`, and a raw HTTP socket request
- Leak method: raw_path_as_is
- Both vulnerable attempts leaked

**Fixed aiohttp 3.9.2:**
- Same requests -> **HTTP 404 Not Found** for all variants
- Both fixed attempts blocked

## Impact

**Arbitrary file read** via HTTP path traversal. An attacker can read
any file accessible to the aiohttp server process by sending crafted
HTTP requests with `../` sequences to a static route configured with
`follow_symlinks=True`.

## Surface and Impact Classification

The ticket metadata claims `claimed_surface=converter_document` and
`expected_impact=code_execution`. However:

- **Actual surface:** `api_remote` -- the vulnerability is exploited
  via HTTP requests to a running aiohttp web server, not a document
  converter.
- **Actual impact:** `info_leak` -- the vulnerability allows arbitrary
  file read (path traversal), not code execution.

The vulnerability is real and confirmed at the `api_remote` surface,
but the claim metadata misclassifies both the surface and the impact.

## References

- https://nvd.nist.gov/vuln/detail/CVE-2024-23334
- https://github.com/aio-libs/aiohttp/commit/1c335944d6a8b1298baf179b7c0b3069f10c514b
- https://github.com/aio-libs/aiohttp/pull/8079
- https://github.com/advisories/GHSA-5h86-8mv2-jq9f
