CVE-2025-0928
Arbitrary executable upload via authenticated endpoint
In Juju versions prior to 3.6.8 and 2.9.52, any authenticated controller user was allowed to upload arbitrary agent binaries to any model or to the controller itself, without verifying model membership or requiring explicit permissions. This enabled the distribution of poisoned binaries to new or upgraded machines, potentially resulting in remote code execution.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- Low
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
8.8
Arbitrary executable upload via authenticated endpoint
In Juju versions prior to 3.6.8 and 2.9.52, any authenticated controller user was allowed to upload arbitrary agent binaries to any model or to the controller itself, without verifying model membership or requiring explicit permissions. This enabled the distribution of poisoned binaries to new or upgraded machines, potentially resulting in remote code execution.
- Record
CVE-2025-0928- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-285
- Updated
- 2026-11-02
EUVDEUVD-2025-20665
8.8
In Juju versions prior to 3.6.8 and 2.9.52, any authenticated controller user was allowed to upload arbitrary agent binaries to any model or to the controller itself, without verifying model membership or requiring explicit permissions. This enabled the distribution of poisoned binaries to new or upgraded machines, potentially resulting in remote code execution.
- Record
EUVD-2025-20665- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-4vc8-wvhw-m5gv
8.8
Juju allows arbitrary executable uploads via authenticated endpoint without authorization
Summary
You can affect the agent binaries used in a Juju controller and the code that is run in the binaries by simply having a user account on a controller. You aren't required to have a model or any permissions. This just requires a user account in the controller database.
Details
Because of the way Juju upload tools code works in the controller it only checks that the user uploading agent binaries is authenticated and is a user tag. No more checks are performed and it allows that user to upload binaries to any model they like (as long as they know the model uuid) or upload binaries to the controller (attacker doesn't need to know any uuid's for controller or controller model).
Once the poison binaries have been uploaded any new machine that is started in the affected model or controller will get started with the poison binaries. Alternatively administrator's of the controller running either juju upgrade-controller or juju upgrade-model will force distribution of the poisoned binaries to all machines in either the model or poison the controllers themselves.
On top of this the exploit can be done with the Juju client tooling itself and no real knowledge on constructing raw API requests is required.
The tools handler is the main piece of code that is used in the APIServer for handling upload requests and persisting the data uploaded: The following code references is how Juju uses and defines this:
- The tools upload handler is defined here (https://github.com/juju/juju/blob/3.6/apiserver/apiserver.go#L972)
- The tools upload handler is created in the api server here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L766C2-L766C25).
- The main authoriser that is used for the upload handler is created here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L770C2-L770C28)
- The upload handler is registered for the model here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L902)
- The upload handler is registered for the controller here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L972)
The authoriser that is used (https://github.com/juju/juju/blame/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/httpcontext.go#L209) only confirms that the logged in user is authenticated and authenticated as a user tag. No other checks are performed.
The toolsUploaderHandler also uses another server func for getting the Mongo state. This also confirms a logged in user but the state that is returned to the caller is scoped to whatever model the requester has asked for. No checks are performed to make sure that the user in question actually has access to this model or the controller. See code here (https://github.com/juju/juju/blob/4e50a28cdde17832aa31634915fbe7442dca6ab3/apiserver/httpcontext.go#L38). We end up here through a few layers of indirection of https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L768
We can also see that when handlers are registered with no model uuid scope in the handler like the controller registration of the tools upload handler, the model uuid gets defaulted to that of the controller model. See (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L690).
PoC
This proof of concept was done with the latest tip of the juju/juju 3.6 branch (https://github.com/juju/juju/commit/cd12b4951d657a980e113564bf2ea82f167589fd). Pull this code and work from inside of the root of the code base. It is expected that this security issue applies to 2.9 onwards as well.
Repo steps:
-
Bootstrap a new controller to lxd. This was done with a compiled client from the branch but there is no reason performing this action from latest snap won't produce the same result.
juju bootstrap localhost sec-demo -
Add a new…
- Record
GHSA-4vc8-wvhw-m5gv- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-285, CWE-434
- Updated
- 2026-11-02
NVD
8.8
In Juju versions prior to 3.6.8 and 2.9.52, any authenticated controller user was allowed to upload arbitrary agent binaries to any model or to the controller itself, without verifying model membership or requiring explicit permissions. This enabled the distribution of poisoned binaries to new or upgraded machines, potentially resulting in remote code execution.
- Record
CVE-2025-0928- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-285, CWE-434
- Updated
- 2026-11-02
OSV
8.8
Arbitrary executable upload via authenticated endpoint
In Juju versions prior to 3.6.8 and 2.9.52, any authenticated controller user was allowed to upload arbitrary agent binaries to any model or to the controller itself, without verifying model membership or requiring explicit permissions. This enabled the distribution of poisoned binaries to new or upgraded machines, potentially resulting in remote code execution.
- Record
CVE-2025-0928- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-285
- Updated
- 2026-11-02
OSVGHSA-4vc8-wvhw-m5gv
8.8
Juju allows arbitrary executable uploads via authenticated endpoint without authorization
Summary
You can affect the agent binaries used in a Juju controller and the code that is run in the binaries by simply having a user account on a controller. You aren't required to have a model or any permissions. This just requires a user account in the controller database.
Details
Because of the way Juju upload tools code works in the controller it only checks that the user uploading agent binaries is authenticated and is a user tag. No more checks are performed and it allows that user to upload binaries to any model they like (as long as they know the model uuid) or upload binaries to the controller (attacker doesn't need to know any uuid's for controller or controller model).
Once the poison binaries have been uploaded any new machine that is started in the affected model or controller will get started with the poison binaries. Alternatively administrator's of the controller running either juju upgrade-controller or juju upgrade-model will force distribution of the poisoned binaries to all machines in either the model or poison the controllers themselves.
On top of this the exploit can be done with the Juju client tooling itself and no real knowledge on constructing raw API requests is required.
The tools handler is the main piece of code that is used in the APIServer for handling upload requests and persisting the data uploaded: The following code references is how Juju uses and defines this:
- The tools upload handler is defined here (https://github.com/juju/juju/blob/3.6/apiserver/apiserver.go#L972)
- The tools upload handler is created in the api server here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L766C2-L766C25).
- The main authoriser that is used for the upload handler is created here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L770C2-L770C28)
- The upload handler is registered for the model here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L902)
- The upload handler is registered for the controller here (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L972)
The authoriser that is used (https://github.com/juju/juju/blame/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/httpcontext.go#L209) only confirms that the logged in user is authenticated and authenticated as a user tag. No other checks are performed.
The toolsUploaderHandler also uses another server func for getting the Mongo state. This also confirms a logged in user but the state that is returned to the caller is scoped to whatever model the requester has asked for. No checks are performed to make sure that the user in question actually has access to this model or the controller. See code here (https://github.com/juju/juju/blob/4e50a28cdde17832aa31634915fbe7442dca6ab3/apiserver/httpcontext.go#L38). We end up here through a few layers of indirection of https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L768
We can also see that when handlers are registered with no model uuid scope in the handler like the controller registration of the tools upload handler, the model uuid gets defaulted to that of the controller model. See (https://github.com/juju/juju/blob/4bcbd094097016b2fde926afd8c9e590eabb3f0c/apiserver/apiserver.go#L690).
PoC
This proof of concept was done with the latest tip of the juju/juju 3.6 branch (https://github.com/juju/juju/commit/cd12b4951d657a980e113564bf2ea82f167589fd). Pull this code and work from inside of the root of the code base. It is expected that this security issue applies to 2.9 onwards as well.
Repo steps:
-
Bootstrap a new controller to lxd. This was done with a compiled client from the branch but there is no reason performing this action from latest snap won't produce the same result.
juju bootstrap localhost sec-demo -
Add a new…
- Record
GHSA-4vc8-wvhw-m5gv- CVSS 3.1
- 8.8 High
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-285, CWE-434
- Updated
- 2026-11-02
OSVGO-2025-3805 No score
Juju allows arbitrary executable uploads via authenticated endpoint without authorization in github.com/juju/juju
Juju allows arbitrary executable uploads via authenticated endpoint without authorization in github.com/juju/juju.
NOTE: The source advisory for this report contains additional versions that could not be automatically mapped to standard Go module versions.
(If this is causing false-positive reports from vulnerability scanners, please suggest an edit to the report.)
The additional affected modules and versions are: github.com/juju/juju before v0.0.0-20250619215741-4034aa13c7cf.
- Record
GO-2025-3805- Updated
- 2026-11-02
OSVopenSUSE-SU-2025:15405-1 No score
govulncheck-vulndb-0.0.20250730T213748-1.1 on GA media
These are all security issues fixed in the govulncheck-vulndb-0.0.20250730T213748-1.1 package on the GA media of openSUSE Tumbleweed.
- Record
openSUSE-SU-2025:15405-1- Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/juju/jujuGo |
before 0.0.0-20250619215741-4034aa13c7cf | 0.0.0-20250619215741-4034aa13c7cf |
github.com/juju/jujuGo |
All versions | None known |
govulncheck-vulndbopenSUSE:Tumbleweed From an advisory covering 56 CVEs |
before 0.0.20250730T213748-1.1 | 0.0.20250730T213748-1.1 |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected, facts | ||
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2025-0928 is a high vulnerability (CVSS 8.8) in Canonical Juju, published July 8, 2025. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.69%, the 53rd percentile. Fixed in github.com/juju/juju 0.0.0-20250619215741-4034aa13c7cf.
References
github.com/juju/juju/security/advisories/GHSA-4vc8-wvhw-m5gv
nvd.nist.gov/vuln/detail/CVE-2025-0928
github.com/juju/juju/commit/22cdcf6b54c2f371822e1c203d4f341be6c9589e
github.com/juju/juju/commit/311e374cb8d2431032c51fb3fb5c4b0aaaa7196c
github.com/juju/juju/commit/4034aa13c7cf5a37427fcd032925d5d21955b096
github.com/juju/juju/commit/b4176e6e45c2c3c817ab60b39e2d52f9a11a5ddf
Show 61 moreShow less
pkg.go.dev/vuln/GO-2025-3805
github.com/advisories/GHSA-4vc8-wvhw-m5gv
juju.is
github.com/CVEProject/cvelistV5/tree/main/cves/2025/0xxx/CVE-2025-0928.json
github.com/juju/juju
suse.com/security/cve/CVE-2024-44905
suse.com/security/cve/CVE-2024-44906
suse.com/security/cve/CVE-2024-56731
suse.com/security/cve/CVE-2025-0928
suse.com/security/cve/CVE-2025-1088
suse.com/security/cve/CVE-2025-30086
suse.com/security/cve/CVE-2025-32019
suse.com/security/cve/CVE-2025-3227
suse.com/security/cve/CVE-2025-3228
suse.com/security/cve/CVE-2025-3415
suse.com/security/cve/CVE-2025-4563
suse.com/security/cve/CVE-2025-4656
suse.com/security/cve/CVE-2025-46702
suse.com/security/cve/CVE-2025-4674
suse.com/security/cve/CVE-2025-47281
suse.com/security/cve/CVE-2025-47871
suse.com/security/cve/CVE-2025-47943
suse.com/security/cve/CVE-2025-4922
suse.com/security/cve/CVE-2025-4981
suse.com/security/cve/CVE-2025-49825
suse.com/security/cve/CVE-2025-5030
suse.com/security/cve/CVE-2025-51471
suse.com/security/cve/CVE-2025-52477
suse.com/security/cve/CVE-2025-52889
suse.com/security/cve/CVE-2025-52890
suse.com/security/cve/CVE-2025-52893
suse.com/security/cve/CVE-2025-52894
suse.com/security/cve/CVE-2025-52900
suse.com/security/cve/CVE-2025-52901
suse.com/security/cve/CVE-2025-52902
suse.com/security/cve/CVE-2025-52903
suse.com/security/cve/CVE-2025-52904
suse.com/security/cve/CVE-2025-52995
suse.com/security/cve/CVE-2025-52996
suse.com/security/cve/CVE-2025-52997
suse.com/security/cve/CVE-2025-53512
suse.com/security/cve/CVE-2025-53513
suse.com/security/cve/CVE-2025-53547
suse.com/security/cve/CVE-2025-53632
suse.com/security/cve/CVE-2025-53633
suse.com/security/cve/CVE-2025-53634
suse.com/security/cve/CVE-2025-53826
suse.com/security/cve/CVE-2025-53893
suse.com/security/cve/CVE-2025-53945
suse.com/security/cve/CVE-2025-54059
suse.com/security/cve/CVE-2025-54379
suse.com/security/cve/CVE-2025-5689
suse.com/security/cve/CVE-2025-5981
suse.com/security/cve/CVE-2025-6023
suse.com/security/cve/CVE-2025-6032
suse.com/security/cve/CVE-2025-6224
suse.com/security/cve/CVE-2025-6226
suse.com/security/cve/CVE-2025-6227
suse.com/security/cve/CVE-2025-6233
suse.com/security/cve/CVE-2025-6264
suse.com/security/cve/CVE-2025-6624
Left out here: the rest of 2 source descriptions. The JSON record has them.