High severity8.8NVD Advisory· Published Apr 7, 2016· Updated May 6, 2026
CVE-2016-0792
CVE-2016-0792
Description
Multiple unspecified API endpoints in Jenkins before 1.650 and LTS before 1.642.2 allow remote authenticated users to execute arbitrary code via serialized data in an XML file, related to XStream and groovy.util.Expando.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.jenkins-ci.main:jenkins-coreMaven | >= 1.643, < 1.650 | 1.650 |
org.jenkins-ci.main:jenkins-coreMaven | < 1.642.2 | 1.642.2 |
Affected products
3Patches
17f202f0317e6Merge pull request #60 from jenkinsci-cert/SECURITY-247-v2
3 files changed · +176 −0
core/src/main/java/hudson/util/XStream2.java+29 −0 modified@@ -47,6 +47,7 @@ import hudson.PluginManager; import hudson.PluginWrapper; import hudson.diagnosis.OldDataMonitor; +import hudson.remoting.ClassFilter; import hudson.util.xstream.ImmutableSetConverter; import hudson.util.xstream.ImmutableSortedSetConverter; import jenkins.model.Jenkins; @@ -159,6 +160,8 @@ private void init() { // but before reflection-based one kicks in. registerConverter(new AssociatedConverterImpl(this), -10); + registerConverter(new BlacklistedTypesConverter(), PRIORITY_VERY_HIGH); // SECURITY-247 defense + registerConverter(new DynamicProxyConverter(getMapper()) { // SECURITY-105 defense @Override public boolean canConvert(Class type) { return /* this precedes NullConverter */ type != null && super.canConvert(type); @@ -434,4 +437,30 @@ class PluginClassOwnership implements ClassOwnership { } + private static class BlacklistedTypesConverter implements Converter { + @Override + public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) { + throw new UnsupportedOperationException("Refusing to marshal for security reasons"); + } + + @Override + public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + throw new ConversionException("Refusing to unmarshal for security reasons"); + } + + @Override + public boolean canConvert(Class type) { + if (type == null) { + return false; + } + try { + ClassFilter.DEFAULT.check(type); + ClassFilter.DEFAULT.check(type.getName()); + } catch (SecurityException se) { + // claim we can convert all the scary stuff so we can throw exceptions when attempting to do so + return true; + } + return false; + } + } }
test/src/test/java/hudson/util/XStream2Security247Test.java+120 −0 added@@ -0,0 +1,120 @@ +package hudson.util; + +import hudson.model.Items; +import org.apache.commons.io.*; +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.jvnet.hudson.test.Issue; +import org.jvnet.hudson.test.JenkinsRule; +import org.kohsuke.stapler.StaplerRequest; +import org.kohsuke.stapler.StaplerResponse; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.servlet.ServletInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Mockito.when; + +public class XStream2Security247Test { + + @Rule + public JenkinsRule j = new JenkinsRule(); + + @Rule + public TemporaryFolder f = new TemporaryFolder(); + + @Mock + private StaplerRequest req; + + @Mock + private StaplerResponse rsp; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + } + + @Test + @Issue("SECURITY-247") + public void testXmlLoad() throws Exception { + File exploitFile = f.newFile(); + try { + // be extra sure there's no file already + if (exploitFile.exists() && !exploitFile.delete()) { + throw new IllegalStateException("file exists and cannot be deleted"); + } + File tempJobDir = new File(j.jenkins.getRootDir(), "security247"); + + String exploitXml = org.apache.commons.io.IOUtils.toString( + XStream2Security247Test.class.getResourceAsStream( + "/hudson/util/XStream2Security247Test/config.xml"), "UTF-8"); + + exploitXml = exploitXml.replace("@TOKEN@", exploitFile.getAbsolutePath()); + + FileUtils.write(new File(tempJobDir, "config.xml"), exploitXml); + + try { + Items.load(j.jenkins, tempJobDir); + } catch (Exception e) { + // ignore + } + assertFalse("no file should be created here", exploitFile.exists()); + } finally { + exploitFile.delete(); + } + } + + @Test + @Issue("SECURITY-247") + public void testPostJobXml() throws Exception { + File exploitFile = f.newFile(); + try { + // be extra sure there's no file already + if (exploitFile.exists() && !exploitFile.delete()) { + throw new IllegalStateException("file exists and cannot be deleted"); + } + File tempJobDir = new File(j.jenkins.getRootDir(), "security247"); + + String exploitXml = org.apache.commons.io.IOUtils.toString( + XStream2Security247Test.class.getResourceAsStream( + "/hudson/util/XStream2Security247Test/config.xml"), "UTF-8"); + + exploitXml = exploitXml.replace("@TOKEN@", exploitFile.getAbsolutePath()); + + when(req.getMethod()).thenReturn("POST"); + when(req.getInputStream()).thenReturn(new Stream(IOUtils.toInputStream(exploitXml))); + when(req.getContentType()).thenReturn("application/xml"); + when(req.getParameter("name")).thenReturn("foo"); + + try { + j.jenkins.doCreateItem(req, rsp); + } catch (Exception e) { + // don't care + } + + assertFalse("no file should be created here", exploitFile.exists()); + } finally { + exploitFile.delete(); + } + } + + private static class Stream extends ServletInputStream { + private final InputStream inner; + + public Stream(final InputStream inner) { + this.inner = inner; + } + + @Override + public int read() throws IOException { + return inner.read(); + } + } +}
test/src/test/resources/hudson/util/XStream2Security247Test/config.xml+27 −0 added@@ -0,0 +1,27 @@ +<map> + <entry> + <groovy.util.Expando> + <expandoProperties> + <entry> + <string>hashCode</string> + <org.codehaus.groovy.runtime.MethodClosure> + <delegate class="groovy.util.Expando" reference="../../../.."/> + <owner class="java.lang.ProcessBuilder"> + <command> + <string>touch</string> + <string>@TOKEN@</string> + </command> + <redirectErrorStream>false</redirectErrorStream> + </owner> + <resolveStrategy>0</resolveStrategy> + <directive>0</directive> + <parameterTypes/> + <maximumNumberOfParameters>0</maximumNumberOfParameters> + <method>start</method> + </org.codehaus.groovy.runtime.MethodClosure> + </entry> + </expandoProperties> + </groovy.util.Expando> + <int>1</int> + </entry> +</map>
Vulnerability mechanics
Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
11- wiki.jenkins-ci.org/display/SECURITY/Jenkins+Security+Advisory+2016-02-24nvdPatchVendor AdvisoryWEB
- www.contrastsecurity.com/security-influencers/serialization-must-die-act-2-xstreamnvdExploitWEB
- github.com/advisories/GHSA-45rg-g72w-r393ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2016-0792ghsaADVISORY
- rhn.redhat.com/errata/RHSA-2016-1773.htmlnvdWEB
- access.redhat.com/errata/RHSA-2016:0711nvdWEB
- github.com/jenkinsci/jenkins/commit/7f202f0317e60cd3160f61467b8558f864f83f41ghsaWEB
- www.exploit-db.com/exploits/42394ghsaWEB
- www.exploit-db.com/exploits/43375ghsaWEB
- www.exploit-db.com/exploits/42394/nvd
- www.exploit-db.com/exploits/43375/nvd
News mentions
0No linked articles in our index yet.