Security context
Medium· 4.3GHSA-wr6w-jxg7-qpfh CVE-2025-31721CWE-862Published Apr 2, 2025

Jenkins Missing Permission Check

Research this vulnerability

Research is free — Hunters explains how the bug works, the root-cause code pattern, how the fix addresses it, and how to test whether a target is affected, in chat. Investigate & write exploit is a paid run — the engine reads the advisory and fix commits, then builds and validates a working proof-of-concept exploit with reproduction steps.

Affected versions

2.500 → fixed in 2.5040 → fixed in 2.492.3

Details

Jenkins 2.503 and earlier, LTS 2.492.2 and earlier does not perform a permission check in an HTTP endpoint. This allows attackers with Computer/Create permission but without Computer/Configure permission to copy an agent, gaining access to encrypted secrets in its configuration. This is due to an incomplete fix of [SECURITY-3495](https://www.jenkins.io/security/advisory/2025-03-05/#SECURITY-3495)/CVE-2025-27622. Jenkins 2.504, LTS 2.492.3 requires Computer/Configure permission to copy an agent containing secrets.

The fix

[SECURITY-3513]

Daniel Beck· Mar 25, 2025, 09:02 PM+280b3651b4753
core/src/main/java/hudson/model/ComputerSet.java+15 0
@@ -61,6 +61,7 @@
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu.ContextMenu;
+import jenkins.security.ExtendedReadRedaction;
import jenkins.util.Timer;
import jenkins.widgets.HasWidgets;
import net.sf.json.JSONObject;
@@ -75,6 +76,7 @@
import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.interceptor.RequirePOST;
import org.kohsuke.stapler.verb.POST;
+import org.springframework.security.access.AccessDeniedException;
/**
* Serves as the top of {@link Computer}s in the URL hierarchy.
@@ -295,6 +297,19 @@ public synchronized void doCreateItem(StaplerRequest2 req, StaplerResponse2 rsp,
// copy through XStream
String xml = Jenkins.XSTREAM.toXML(src);
+ if (!src.hasPermission(Computer.CONFIGURE)) {
+ final String redactedConfigDotXml = ExtendedReadRedaction.applyAll(xml);
+ if (!xml.equals(redactedConfigDotXml)) {
+ // AccessDeniedException2 does not permit a custom message, and anyway redirecting the user to the login screen is obviously pointless.
+ throw new AccessDeniedException(
+ Messages.ComputerSet_may_not_copy_as_it_contains_secrets_and_(
+ src.getNodeName(),
+ Jenkins.getAuthentication2().getName(),
+ Computer.PERMISSIONS.title,
+ Computer.EXTENDED_READ.name,
+ Computer.CONFIGURE.name));
+ }
+ }
Node result = (Node) Jenkins.XSTREAM.fromXML(xml);
result.setNodeName(name);
result.holdOffLaunchUntilSave = true;
core/src/main/resources/hudson/model/Messages.properties+1 0
@@ -112,6 +112,7 @@ ComputerSet.NoSuchSlave=No such agent: {0}
ComputerSet.SlaveAlreadyExists=Agent called ‘{0}’ already exists
ComputerSet.SpecifySlaveToCopy=Specify which agent to copy
ComputerSet.DisplayName=Nodes
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=May not copy {0} as it contains secrets and {1} has {2}/{3} but not /{4}
Descriptor.From=(from <a href="{1}" rel="noopener noreferrer" target="_blank">{0}</a>)
core/src/main/resources/hudson/model/Messages_bg.properties+5 0
@@ -155,6 +155,11 @@ CLI.wait-node-offline.shortDescription=\
ComputerSet.DisplayName=\
Компютри
+# May not copy {0} as it contains secrets and {1} has {2}/{3} but not /{4}
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=\
+ Не може да копирате „{0}“, защото на него има пароли, а „{1}“ има {2}/{3}, но\
+ не и /{4}
+
Descriptor.From=\
(от <a href="{1}">{0}</a>)
core/src/main/resources/hudson/model/Messages_de.properties+1 0
@@ -107,6 +107,7 @@ ComputerSet.DisplayName=Knoten
ComputerSet.NoSuchSlave=Agent existiert nicht: „{0}“
ComputerSet.SlaveAlreadyExists=Ein Agent mit dem Namen „{0}“ existiert bereits.
ComputerSet.SpecifySlaveToCopy=Geben Sie an, welcher Agent kopiert werden soll
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=Kann „{0}“ nicht kopieren, da es Geheimnisse enthält und „{1}“ nur {2}/{3} aber nicht /{4} hat.
Descriptor.From=(aus <a href="{1}">{0}</a>)
core/src/main/resources/hudson/model/Messages_fr.properties+1 0
@@ -107,6 +107,7 @@ ComputerSet.NoSuchSlave=Aucun agent: {0}
ComputerSet.SlaveAlreadyExists=L''agent ‘{0}’ existe déjà
ComputerSet.SpecifySlaveToCopy=Quel agent à copier
ComputerSet.DisplayName=Nœuds
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=Copie impossible de {0} car il contient des secrets et {1} possède {2}/{3} mais pas /{4}
Descriptor.From=(de <a href="{1}" rel="noopener noreferrer" target="_blank">{0}</a>)
core/src/main/resources/hudson/model/Messages_it.properties+2 0
@@ -156,6 +156,8 @@ ComputerSet.DisplayName=Nodi
ComputerSet.NoSuchSlave=L''agente {0} non esiste
ComputerSet.SlaveAlreadyExists=Un agente di nome "{0}" esiste già
ComputerSet.SpecifySlaveToCopy=Specificare l''agente da copiare
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=Impossibile copiare \
+ {0} perché contiene segreti e {1} ha {2}/{3} ma non /{4}
Descriptor.From=(da <a href="{1}">{0}</a>)
Executor.NotAvailable=N/D
FileParameterDefinition.DisplayName=Parametro file
core/src/main/resources/hudson/model/Messages_pt_BR.properties+2 0
@@ -105,6 +105,8 @@ UpdateCenter.Status.Success=Sucesso
UpdateCenter.init=Iniciando o centro de atualização
ParameterAction.DisplayName=Parâmetros
ComputerSet.DisplayName=Nós
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=Pode não conseguir copiar {0} já que contem segredos e {1} \
+ tem {2}/{3} mas não /{4}
Run.DeletePermission.Description=\
Esta permissão concede aos usuários o direito de manualmente apagarem construções do histórico de construções.
Run.Summary.BrokenForALongTime=Quebrado há muito tempo
core/src/main/resources/hudson/model/Messages_sr.properties+1 0
@@ -85,6 +85,7 @@ Computer.BadChannel=Машина агента није повезана или
ComputerSet.SlaveAlreadyExists=Већ постоји агент са именом {0}
ComputerSet.SpecifySlaveToCopy=Одредите агент за копирање
ComputerSet.DisplayName=Рачунари
+ComputerSet.may_not_copy_as_it_contains_secrets_and_=Неможе се копирати {0} пошто садржи тајне и {1} има {2}/{3} а не /{4}
Descriptor.From=(од <a href="{1}">{0}</a>)
Executor.NotAvailable=Н/Д
FreeStyleProject.DisplayName=Независни задатак
More files changed — see the full commit.

References