CVE-2020-2135
Description
Sandbox protection in Jenkins Script Security Plugin 1.70 and earlier could be circumvented through crafted method calls on objects that implement GroovyInterceptable.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Sandbox bypass in Jenkins Script Security Plugin 1.70 and earlier via crafted method calls on GroovyInterceptable objects allows arbitrary code execution.
The Jenkins Script Security Plugin provides a sandbox that restricts what Groovy scripts can do. In version 1.70 and earlier, the sandbox could be bypassed through crafted method calls on objects that implement the GroovyInterceptable interface [1]. This is because the sandbox did not properly intercept method invocations on such objects, allowing attackers to execute calls that should have been blocked [2].
Attackers need the ability to specify and run sandboxed scripts in Jenkins [1]. This can be achieved by users with limited permissions, such as Job/Configure, who are not fully trusted [3]. By crafting method calls on GroovyInterceptable objects, they can circumvent the sandbox restrictions and execute arbitrary code [1].
Successful exploitation allows the attacker to execute arbitrary code in the context of the Jenkins controller JVM [1]. This could lead to full compromise of the Jenkins instance, including access to credentials, secrets, and the ability to modify jobs or configurations.
Script Security Plugin version 1.71 fixes this vulnerability by adding additional checks that intercept method calls on objects that implement GroovyInterceptable as calls to GroovyObject#invokeMethod(String, Object), which is on the list of dangerous signatures and is not approved for use in the sandbox [1][4]. Administrators should upgrade to version 1.71 or later as soon as possible.
- Jenkins Security Advisory 2020-03-09
- security - Multiple vulnerabilities in Jenkins plugins
- GitHub - jenkinsci/script-security-plugin: Allows Jenkins admins to control what in-process scripts can be run by users
- [SECURITY-1754] Prevent unsandboxed constructor invocation and handle… · jenkinsci/script-security-plugin@5b1969e
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.plugins:script-securityMaven | < 1.71 | 1.71 |
Affected products
2- Jenkins project/Jenkins Script Security Pluginv5Range: unspecified
Patches
15b1969e0bdf5[SECURITY-1754] Prevent unsandboxed constructor invocation and handle GroovyInterceptable correctly
5 files changed · +117 −4
pom.xml+1 −1 modified@@ -63,7 +63,7 @@ <dependency> <groupId>org.kohsuke</groupId> <artifactId>groovy-sandbox</artifactId> - <version>1.25</version> + <version>1.26</version> <exclusions> <exclusion> <groupId>org.codehaus.groovy</groupId>
src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/GroovyCallSiteSelector.java+7 −2 modified@@ -26,6 +26,7 @@ import com.google.common.primitives.Primitives; import groovy.lang.GString; +import groovy.lang.GroovyInterceptable; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -146,7 +147,11 @@ private static boolean isInstancePrimitive(@Nonnull Class<?> type, @Nonnull Obje * @param args a set of actual arguments */ public static @CheckForNull Method method(@Nonnull Object receiver, @Nonnull String method, @Nonnull Object[] args) { - for (Class<?> c : types(receiver)) { + Set<Class<?>> types = types(receiver); + if (types.contains(GroovyInterceptable.class) && !"invokeMethod".equals(method)) { + return method(receiver, "invokeMethod", new Object[]{ method, args }); + } + for (Class<?> c : types) { Method candidate = findMatchingMethod(c, method, args); if (candidate != null) { return candidate; @@ -262,7 +267,7 @@ private static boolean isVarArgsMethod(@Nonnull Method m, @Nonnull Object[] args return null; } - private static Iterable<Class<?>> types(@Nonnull Object o) { + private static Set<Class<?>> types(@Nonnull Object o) { Set<Class<?>> types = new LinkedHashSet<Class<?>>(); visitTypes(types, o.getClass()); return types;
src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/StaticWhitelist.java+4 −1 modified@@ -63,7 +63,10 @@ public final class StaticWhitelist extends EnumeratingWhitelist { "staticMethod java.lang.System exit int", }; - private static final String[] PERMANENTLY_BLACKLISTED_CONSTRUCTORS = new String[0]; + private static final String[] PERMANENTLY_BLACKLISTED_CONSTRUCTORS = { + "new org.kohsuke.groovy.sandbox.impl.Checker$SuperConstructorWrapper java.lang.Object[]", + "new org.kohsuke.groovy.sandbox.impl.Checker$ThisConstructorWrapper java.lang.Object[]" + }; final List<MethodSignature> methodSignatures = new ArrayList<MethodSignature>(); final List<NewSignature> newSignatures = new ArrayList<NewSignature>();
src/main/resources/org/jenkinsci/plugins/scriptsecurity/sandbox/whitelists/blacklist+4 −0 modified@@ -110,3 +110,7 @@ staticMethod org.codehaus.groovy.runtime.ScriptBytecodeAdapter castToType java.l # TODO do we need a @Blacklisted annotation? method org.jenkinsci.plugins.workflow.support.steps.build.RunWrapper getRawBuild + +# SECURITY-1754 +new org.kohsuke.groovy.sandbox.impl.Checker$SuperConstructorWrapper java.lang.Object[] +new org.kohsuke.groovy.sandbox.impl.Checker$ThisConstructorWrapper java.lang.Object[]
src/test/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SandboxInterceptorTest.java+101 −0 modified@@ -79,6 +79,8 @@ import org.junit.Test; import org.junit.rules.ErrorCollector; import org.jvnet.hudson.test.Issue; +import org.kohsuke.groovy.sandbox.impl.Checker.SuperConstructorWrapper; +import org.kohsuke.groovy.sandbox.impl.Checker.ThisConstructorWrapper; public class SandboxInterceptorTest { @@ -1296,6 +1298,105 @@ public void blockIllegalAnnotationsInAnnotations() throws Exception { "class Foo { }\n"); } + @Issue("SECURITY-1754") + @Test public void blockDirectCallsToSyntheticConstructors() throws Exception { + try { + // Not ok, the call to super() in the synthetic constructor for Subclass cannot be intercepted. + evaluate(new GenericWhitelist(), + "class Superclass { }\n" + + "class Subclass extends Superclass { }\n" + + "new Subclass(null)"); + fail("Script should have failed"); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo( + "Rejecting illegal call to synthetic constructor: private Subclass(org.kohsuke.groovy.sandbox.impl.Checker$SuperConstructorWrapper). " + + "Perhaps you meant to use one of these constructors instead: public Subclass()")); + } + } + + @Issue("SECURITY-1754") + @Test public void blockMisinterceptedCallsToSyntheticConstructors() throws Exception { + try { + // Not ok, the call to super() in the synthetic constructor for Subclass cannot be intercepted. + evaluate(new GenericWhitelist(), + "class Superclass { }\n" + + "class Subclass extends Superclass {\n" + + " Subclass() { def x = 1 }\n" + + " Subclass(Subclass s) { def x = 1 }\n" + + "}\n" + + "new Subclass(null)"); // Intercepted as a call to the second constructor before SECURITY-1754, but actually calls synthetic constructor. + fail("Script should have failed"); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo( + "Rejecting illegal call to synthetic constructor: private Subclass(org.kohsuke.groovy.sandbox.impl.Checker$SuperConstructorWrapper). " + + "Perhaps you meant to use one of these constructors instead: public Subclass(), public Subclass(Subclass)")); + } + } + + @Issue("SECURITY-1754") + @Test public void blockCallsToSyntheticConstructorsViaOtherConstructors() throws Exception { + try { + // Not ok, the call to super() in the synthetic constructor for Subclass cannot be intercepted. + evaluate(new GenericWhitelist(), + "class Superclass { }\n" + + "class Subclass extends Superclass {\n" + + " Subclass() { }\n" + + " Subclass(int x, int y) { this(null) }\n" + // Calls synthetic constructor + "}\n" + + "new Subclass(1, 2)"); + fail("Script should have failed"); + } catch (SecurityException e) { + assertThat(e.getMessage(), equalTo( + "Rejecting illegal call to synthetic constructor: private Subclass(org.kohsuke.groovy.sandbox.impl.Checker$SuperConstructorWrapper). " + + "Perhaps you meant to use one of these constructors instead: public Subclass(), public Subclass(int,int)")); + } + } + + @Issue("SECURITY-1754") + @Test public void blockConstructorWrappersFromBeingUsedDirectly() throws Exception { + for (Class<?> syntheticParamType : new Class<?>[] { SuperConstructorWrapper.class, ThisConstructorWrapper.class }) { + // Not ok, instantiating any of the wrappers would allow attackers to bypass the fix. + assertRejected(new GenericWhitelist(), "new " + syntheticParamType.getName() + " java.lang.Object[]", + "new " + syntheticParamType.getCanonicalName() + "(null)"); + // The wrapper's constructors are permanently blacklisted + assertRejected(new BlanketWhitelist(), "new " + syntheticParamType.getName() + " java.lang.Object[]", + "new " + syntheticParamType.getCanonicalName() + "(null)"); + } + } + + @Issue("SECURITY-1754") + @Test public void allowCheckedCallsToSyntheticConstructors() throws Exception { + // Ok, super call is intercepted via Checker.checkedSuperConstructor. + assertEvaluate(new GenericWhitelist(), "Subclass", + "class Superclass { }\n" + + "class Subclass extends Superclass { }\n" + + "new Subclass().class.simpleName"); + // Ok, this call is intercepted via Checker.checkedThisConstructor. + assertEvaluate(new GenericWhitelist(), "Subclass", + "class Subclass {\n" + + " Subclass() { this(1) }\n" + + " Subclass(int x) { }\n" + + "}\n" + + "new Subclass().class.simpleName"); + } + + @Issue("SECURITY-1754") + @Test public void groovyInterceptable() throws Throwable { + assertRejected(new GenericWhitelist(), "method groovy.lang.GroovyObject invokeMethod java.lang.String java.lang.Object", + "class Test implements GroovyInterceptable {\n" + + " def hello() { 'world' }\n" + + " def invokeMethod(String name, Object args) { 'goodbye' }\n" + + "}\n" + + "new Test().hello()\n"); + // Property access is not affected by GroovyInterceptable. + assertEvaluate(new GenericWhitelist(), "world", + "class Test implements GroovyInterceptable {\n" + + " def hello = 'world'\n" + + " def invokeMethod(String name, Object args) { 'goodbye' }\n" + + "}\n" + + "new Test().hello\n"); + } + /** * Checks that the annotation is blocked from being used in the provided script whether it is imported or used via * fully-qualified class name.
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
5- github.com/advisories/GHSA-qvhf-3567-pc4vghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2020-2135ghsaADVISORY
- www.openwall.com/lists/oss-security/2020/03/09/1ghsamailing-listx_refsource_MLISTWEB
- github.com/jenkinsci/script-security-plugin/commit/5b1969e0bdf5cde04a165b847144756b28495788ghsaWEB
- jenkins.io/security/advisory/2020-03-09/ghsax_refsource_CONFIRMWEB
News mentions
1- Jenkins Security Advisory 2020-03-09Jenkins Security Advisories · Mar 9, 2020