Skip to content

Security: version argument unvalidated and concatenated into firmware download URL #2071

@frenckatron

Description

@frenckatron

Problem

The version parameter accepted by upgrade(version=...) is interpolated twice into the download URL with no validation:

update_file = f"WLED_{version}_{architecture}{ethernet}.bin{gzip}"
download_url = (
    f"https://github.com/{repo}/releases/download/v{version}/{update_file}"
)

A value such as version="0.14.0/../../attacker/wled-mal/releases/download/v9.9.9/payload" is normalized by yarl/aiohttp into a path that points at a different release in a different repository. The same hole exists for the repo argument, which is appended raw and defaults to wled/WLED. Any downstream application that takes version/repo from user input (Home Assistant integrations, web dashboards, etc.) inherits this URL-injection primitive.

Why This Matters

Allows an attacker who controls the version (or repo) input — for instance via a web UI that passes user input straight to this library — to force download of firmware from an arbitrary GitHub repository, which is then flashed onto the target device.

Suggested Fix

Validate version against a strict semantic-version pattern (^v?\d+\.\d+\.\d+(?:[-+.][A-Za-z0-9.]+)?$) and repo against ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ before use. Raise WLEDUpgradeError on mismatch:

import re
if not re.match(r"^\d+\.\d+\.\d+([-+.][A-Za-z0-9.]+)?$", str(version)):
    raise WLEDUpgradeError(f"Invalid version: {version!r}")
if not re.match(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$", repo):
    raise WLEDUpgradeError(f"Invalid repo: {repo!r}")

Details

Severity 🟡 Medium
Category request_handling
Location src/wled/wled.py:702-716
Effort ⚡ Quick fix

🤖 Created by Kōan from audit session

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions