<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>

    Confluence文件讀取漏洞分析

    VSole2021-10-29 05:40:27

    前言

        前段時間Confluence發布了CVE-2021-26085補丁,剛好之前分析過Confluence的漏洞,免去了搭建漏洞分析環境的麻煩,因此分析下這個漏洞。

    分析過程

    漏洞點定位

        這個漏洞爆出來已經有一段時間了,所以已經有公開的POC了

    /s/123cfx/_/;/WEB-INF/web.xml
    

        首先大致測了一下,除了123cfx部分可以修改為其他內容,其他的部分修改或者刪除后都會導致無法讀取,/s/這部分比較特殊,所以猜測可能是由于以/s/開始會被當作靜態文件處理。在web.xml中找/s/部分的Filter或者Servlet

        在/WEB- INF/web.xml中對/s/對應的servlet做了配置,所以理論上來講可以在ConfluenceNoOpServlet#service方法打斷點查看執行流程。

            noop
            com.atlassian.confluence.servlet.ConfluenceNoOpServlet
            0
        
        
            noop
            /s/*
        
    

        但是當執行payload后并沒斷下來,將url改為/s/12xxxx則執行到了ConfluenceNoOpServlet,所以在Tomcat程序FilterServlet的必經之路ApplicationFilterChain#internalDoFilter方法this.servlet.service(request, response);打斷點,發現當我們執行payload時最后是由DefaultServlet來處理的,而DefaultServlet按理說是只處理根目錄的請求,為什么我們的payload會被DefaultServlet處理。

            default
            org.apache.catalina.servlets.DefaultServlet
        ...
        
        
            default
            /
        
    

        設置servlet的代碼在ApplicationFilterChain#setServlet中,再次運行測試,發現程序會兩次進入setServlet方法,第一次是ConfluenceNoOpServlet,第二次是DefaultServlet。所以猜測是當程序在Filter中對請求做了轉發,查看調用鏈,果然在UrlRewriteFilter中做了處理。

            UrlRewriteFilter
            org.tuckey.web.filters.urlrewrite.UrlRewriteFilter
        
        
            UrlRewriteFilter
            /s/*
        
    

    UrlRewriteFilter入門

        這里使用了UrlRewriteFilter組件,所以我們有必要先對這個組件簡單了解。

    UrlRewriteFilter是一個改寫URL的Java Web過濾器,可見將動態URL靜態化。適用于任何Java
    Web服務器(Resin,Jetty,JBoss,Tomcat,Orion等)。與其功能類似的還有Apache的mod_rewrite。

    將動態URL轉化為偽靜態URL的好處主要有三個:

    • 便于搜索引擎收錄。

    • 屏蔽url結構和參數信息,更安全。

    • 可以將冗雜的URL改寫得簡而美。

        一般在web.xml中配置后還需要配置一個urlrewriter.xml,在Confluence中,配置如下:

    xml version="1.0" encoding="utf-8"?>
    
    
         class='com.atlassian.confluence.servlet.rewrite.ConfluenceResourceDownloadRewriteRule' />
     
            /images/icons/attachments/file.gif
             type="permanent-redirect">%{context-path}/images/icons/contenttypes/attachment_16.png
        
    
    

        這個標簽中的內容比較好理解,大概是當訪問呢images/icons/attachments/file.gif會被重定向到%{context- path}/images/icons/contenttypes/attachment_16.png中,但中配置的類是如何工作的?

        查了官網的文檔,當我們要擴展基本規則時,可以繼承RewriteRule類并實現matches方法。

    UrlRewriteFilter解析流程分析

    初始化

        初始化init主要完成urlrewriter.xml的解析,這里會從FilterConfig中保存的配置中首先解析一些屬性,這里需要注意,當沒有配置modRewriteConf屬性時,則會判斷modRewriteStyleConf的值,這個值默認為False,所以會將confPath屬性設置為/WEB- INF/urlrewrite.xml,再往下會判斷modRewriteConfText屬性是否在FilterConfig中配置,如果沒有則通過loadUrlRewriter方法。

    private boolean modRewriteStyleConf = false;
    public void init(FilterConfig filterConfig) throws ServletException {
           ...
            String confPathStr = filterConfig.getInitParameter("confPath");
            ...
            //判斷是否在Filter中配置了modRewriteConf,如果沒有則modRewriteStyleConf的值為默認值false。
                    String modRewriteConf = filterConfig.getInitParameter("modRewriteConf");
                    if (!StringUtils.isBlank(modRewriteConf)) {
                        this.modRewriteStyleConf = "true".equals(StringUtils.trim(modRewriteConf).toLowerCase());
                    }
            //由于modRewriteStyleConf為False,默認加載/WEB-INF/urlrewrite.xml
                    if (!StringUtils.isBlank(confPathStr)) {
                        this.confPath = StringUtils.trim(confPathStr);
                    } else {
                        this.confPath = this.modRewriteStyleConf ? "/WEB-INF/.htaccess" : "/WEB-INF/urlrewrite.xml";
                    }
    ...
        //沒有在Filter中配置modRewriteConfText,則通過loadUrlRewriter加載配置。
                    String modRewriteConfText = filterConfig.getInitParameter("modRewriteConfText");
                    if (!StringUtils.isBlank(modRewriteConfText)) {
                        ModRewriteConfLoader loader = new ModRewriteConfLoader();
                        Conf conf = new Conf();
                        loader.process(modRewriteConfText, conf);
                        conf.initialise();
                        this.checkConf(conf);
                        this.confLoadedFromFile = false;
                    } else {
                        this.loadUrlRewriter(filterConfig);
                    }
                }
            }
        }
    

    loadUrlRewriter中主要通過調用loadUrlRewriterLocal完成實際的加載邏輯。

    • 通過confPath作為路徑加載內容到inputStream

    • 將資源路徑轉換為URL并保存到confUrlStr

    • 通過文件內容,URL,modRewriteStyleConf等屬性構建Conf對象

    • checkConf檢查Conf對象
    private void loadUrlRewriterLocal() {
            InputStream inputStream = this.context.getResourceAsStream(this.confPath);
            if (inputStream == null) {
                inputStream = ClassLoader.getSystemResourceAsStream(this.confPath);
            }
            URL confUrl = null;
            try {
                confUrl = this.context.getResource(this.confPath);
            } catch (MalformedURLException var5) {
                log.debug(var5);
            }
            String confUrlStr = null;
            if (confUrl != null) {
                confUrlStr = confUrl.toString();
            }
            if (inputStream == null) {
                log.error("unable to find urlrewrite conf file at " + this.confPath);
                if (this.urlRewriter != null) {
                    log.error("unloading existing conf");
                    this.urlRewriter = null;
                }
            } else {
                Conf conf = new Conf(this.context, inputStream, this.confPath, confUrlStr, this.modRewriteStyleConf);
                this.checkConf(conf);
            }
        }
    

        首先看下Conf對象創建的過程,前面的是一些屬性賦值的操作,在下面的If語句中判斷modRewriteStyleConf的值用不同的解析方式,這個也可以理解.htaccessurlrewrite.xml本來就應該用不同的方式解析,由于我們這里是使用urlrewrite.xml配置,因此會通過loadDom加載XML內容。

    public Conf(ServletContext context, InputStream inputStream, String fileName, String systemId, boolean modRewriteStyleConf) {
           ...
            if (modRewriteStyleConf) {
                this.loadModRewriteStyle(inputStream);
            } else {
                this.loadDom(inputStream);
            }
            if (this.docProcessed) {
                this.initialise();
            }
            this.loadedDate = new Date();
        }
    

    loadDom主要通過Dom方式解析XML內容,解析完成后通過processConfDoc處理解析后的內容,這里會根據標簽的不同做不同的處理,由于我們這里只用了rulerule- class標簽,所以其他部分的代碼先忽略。

    • 標簽為rule時則創建NormalRule對象 ,并將屬性封裝到這個對象中。

    • 標簽為class-rule創建ClassRule對象,并將classmethod屬性設置到這個對象中。

    • 通過標簽構造完對象后都會通過addRule將創建好的對象放到Conf.rules屬性中。
    protected void processConfDoc(Document doc) {
            Element rootElement = doc.getDocumentElement();
    ...
            NodeList rootElementList = rootElement.getChildNodes();
            for(int i = 0; i < rootElementList.getLength(); ++i) {
                Node node = rootElementList.item(i);
                Element ruleElement;
                Node toNode;
                if (node.getNodeType() == 1 && ((Element)node).getTagName().equals("rule")) {
                    ruleElement = (Element)node;
                    NormalRule rule = new NormalRule();
                    this.processRuleBasics(ruleElement, rule);
                    procesConditions(ruleElement, rule);
                    processRuns(ruleElement, rule);
                    toNode = ruleElement.getElementsByTagName("to").item(0);
                    rule.setTo(getNodeValue(toNode));
                    rule.setToType(getAttrValue(toNode, "type"));
                    rule.setToContextStr(getAttrValue(toNode, "context"));
                    rule.setToLast(getAttrValue(toNode, "last"));
                    rule.setQueryStringAppend(getAttrValue(toNode, "qsappend"));
                    if ("true".equalsIgnoreCase(getAttrValue(toNode, "encode"))) {
                        rule.setEncodeToUrl(true);
                    }
                    processSetAttributes(ruleElement, rule);
                    this.addRule(rule);
                } else if (node.getNodeType() == 1 && ((Element)node).getTagName().equals("class-rule")) {
                    ruleElement = (Element)node;
                    ClassRule classRule = new ClassRule();
                    if ("false".equalsIgnoreCase(getAttrValue(ruleElement, "enabled"))) {
                        classRule.setEnabled(false);
                    }
                    if ("false".equalsIgnoreCase(getAttrValue(ruleElement, "last"))) {
                        classRule.setLast(false);
                    }
                    classRule.setClassStr(getAttrValue(ruleElement, "class"));
                    classRule.setMethodStr(getAttrValue(ruleElement, "method"));
                    this.addRule(classRule);
                } 
            }
            this.docProcessed = true;
        }
    

        最后我們再看下checkConf方法,這個方法通過checkConfLocal完成具體的檢測,主要是通過Conf對象的一些屬性檢測是否加載成功,如果加載成功則通過Conf構建UrlRewriter對象并賦值給this.urlRewriter

    private void checkConfLocal(Conf conf) {
          ...
            this.confLastLoaded = conf;
            if (conf.isOk() && conf.isEngineEnabled()) {
                this.urlRewriter = new UrlRewriter(conf);
                log.info("loaded (conf ok)");
            } else {
                if (!conf.isOk()) {
                    log.error("Conf failed to load");
                }
                if (!conf.isEngineEnabled()) {
                    log.error("Engine explicitly disabled in conf");
                }
                if (this.urlRewriter != null) {
                    log.error("unloading existing conf");
                    this.urlRewriter = null;
                }
            }
        }
    

    攔截器處理過程

    攔截器的處理主要在UrlRewriteFilter#doFilter中,具體操作如下:

    • 獲取urlRewriter對象并封裝到urlRewriteWrappedResponse

    • 判斷servername是否為localhost,一般都不是所以先不看這里的處理邏輯

    • urlRewriter不為Null,執行processRequest方法
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            UrlRewriter urlRewriter = this.getUrlRewriter(request, response, chain);
            HttpServletRequest hsRequest = (HttpServletRequest)request;
            HttpServletResponse hsResponse = (HttpServletResponse)response;
            UrlRewriteWrappedResponse urlRewriteWrappedResponse = new UrlRewriteWrappedResponse(hsResponse, hsRequest, urlRewriter);
            if (this.statusEnabled && this.statusServerNameMatcher.isMatch(request.getServerName())) {
                String uri = hsRequest.getRequestURI();
                if (log.isDebugEnabled()) {
                    log.debug("checking for status path on " + uri);
                }
                String contextPath = hsRequest.getContextPath();
                if (uri != null && uri.startsWith(contextPath + this.statusPath)) {
                    this.showStatus(hsRequest, urlRewriteWrappedResponse);
                    return;
                }
            }
            boolean requestRewritten = false;
            if (urlRewriter != null) {
                requestRewritten = urlRewriter.processRequest(hsRequest, urlRewriteWrappedResponse, chain);
            } else if (log.isDebugEnabled()) {
                log.debug("urlRewriter engine not loaded ignoring request (could be a conf file problem)");
            }
            if (!requestRewritten) {
                chain.doFilter(hsRequest, urlRewriteWrappedResponse);
            }
        }
    

    processRequest首先獲取RuleChain,并執行doRules方法。

    public boolean processRequest(HttpServletRequest hsRequest, HttpServletResponse hsResponse, FilterChain parentChain) throws IOException, ServletException {
            //
            RuleChain chain = this.getNewChain(hsRequest, parentChain);
            if (chain == null) {
                return false;
            } else {
                chain.doRules(hsRequest, hsResponse);
                return chain.isResponseHandled();
            }
        }
    

    getNewChain主要是從conf中獲取rules,如果不為空,則將rules封裝到RuleChain對象中返回。

    private RuleChain getNewChain(HttpServletRequest hsRequest, FilterChain parentChain) {
            String originalUrl = this.getPathWithinApplication(hsRequest);
      ...
                if (!this.conf.isOk()) {
                    log.debug("configuration is not ok.  not rewriting request.");
                    return null;
                } else {
                    List rules = this.conf.getRules();
                    if (rules.size() == 0) {
                        log.debug("there are no rules setup.  not rewriting request.");
                        return null;
                    } else {
                        return new RuleChain(this, originalUrl, parentChain);
                    }
                }
            }
        }
        public RuleChain(UrlRewriter urlRewriter, String originalUrl, FilterChain parentChain) {
            this.finalToUrl = originalUrl;
            this.urlRewriter = urlRewriter;
            this.rules = urlRewriter.getConf().getRules();
            this.parentChain = parentChain;
        }
    

        下面分析比較重要的doRules方法,process主要是完成根據規則匹配URL,并重寫URLhandleRewrite根據重寫的URL發起請求。

    public void doRules(ServletRequest request, ServletResponse response) throws IOException, ServletException {
            try {
                this.process(request, response);
                this.handleRewrite(request, response);
            } catch (InvocationTargetException var4) {
                this.handleExcep(request, response, var4);
            } catch (ServletException var5) {
                if (!(var5.getCause() instanceof InvocationTargetException)) {
                    throw var5;
                }
                this.handleExcep(request, response, (InvocationTargetException)var5.getCause());
            }
        }
    

    下面分析這兩個方法的操作過程

    process
    • 循環調用ruleChains中的matches方法,匹配成功則將結果賦值給RewrittenUrl對象,并將rewrittenUrl對象賦值給finalRewrittenRequest。將rewrittenUrl的URL保存到finalToUrl中。
    public void process(ServletRequest request, ServletResponse response) throws IOException, ServletException, InvocationTargetException {
            while(this.ruleIdxToRun < this.rules.size()) {
                this.doRuleProcessing((HttpServletRequest)request, (HttpServletResponse)response);
            }
        }
        private void doRuleProcessing(HttpServletRequest hsRequest, HttpServletResponse hsResponse) throws IOException, ServletException, InvocationTargetException {
            int currentIdx = this.ruleIdxToRun++;
            Rule rule = (Rule)this.rules.get(currentIdx);
            RewrittenUrl rewrittenUrl = rule.matches(this.finalToUrl, hsRequest, hsResponse, this);
            if (rule.isFilter()) {
                this.dontProcessAnyMoreRules();
            }
            if (rewrittenUrl != null) {
                log.trace("got a rewritten url");
                this.finalRewrittenRequest = rewrittenUrl;
                this.finalToUrl = rewrittenUrl.getTarget();
                if (rule.isLast()) {
                    log.debug("rule is last");
                    this.dontProcessAnyMoreRules();
                }
            }
        }
    
    • 下面到了我們分析這次漏洞的重點ClassRulematches方法,主要是通過反射調用ConfluenceResourceDownloadRewriteRule#matches
    public RewrittenUrl matches(String url, HttpServletRequest hsRequest, HttpServletResponse hsResponse) throws ServletException, IOException {
            if (!this.initialised) {
                return null;
            } else {
                Object[] args = new Object[]{hsRequest, hsResponse};
                if (log.isDebugEnabled()) {
                    log.debug("running " + this.classStr + "." + this.methodStr + "(HttpServletRequest, HttpServletResponse)");
                }
                if (this.matchesMethod == null) {
                    return null;
                } else {
                    Object returnedObj;
                    try {
                        returnedObj = this.matchesMethod.invoke(this.localRule, (Object[])args);
    ...
            }
        }
    

        這里我解釋下matchesMethod為什么是ConfluenceResourceDownloadRewriteRule#matches,在初始化方法中,會通過反射獲取method對象并賦值給matchesMethodmethodStr默認為matches

    private String methodStr = "matches";
    public boolean initialise(ServletContext context) {
            ...
            try {
                ruleClass = Class.forName(this.classStr);
            ...
                   this.matchesMethod = ruleClass.getMethod(this.methodStr, methodParameterTypes);
    
    • ConfluenceResourceDownloadRewriteRule#matches設置兩個正則匹配,也就是說滿足這兩個任意一個正則,URL才會被重寫并轉發。
    private static final Pattern NO_CACHE_PATTERN = Pattern.compile("^/s/(.*)/NOCACHE(.*)/_/((?i)(?!WEB-INF)(?!META-INF).*)");
        private static final Pattern CACHE_PATTERN = Pattern.compile("^/s/(.*)/_/((?i)(?!WEB-INF)(?!META-INF).*)");
        public RewriteMatch matches(HttpServletRequest request, HttpServletResponse response) {
            String url;
            try {
                //路徑規范化,將../和./規范化
                url = this.getNormalisedPathFrom(request);
            } catch (URISyntaxException var8) {
                return null;
            }
            Matcher noCacheMatcher = NO_CACHE_PATTERN.matcher(url);
            Matcher cacheMatcher = CACHE_PATTERN.matcher(url);
            String rewrittenContextUrl;
            String rewrittenUrl;
            //首先匹配noCacheMatcher正則,匹配成功則改寫URL并設置到DisableCacheRewriteMatch
            if (noCacheMatcher.matches()) {
                rewrittenContextUrl = "/" + this.rewritePathMappings(noCacheMatcher.group(3));
                rewrittenUrl = request.getContextPath() + rewrittenContextUrl;
                return new DisableCacheRewriteMatch(rewrittenUrl, rewrittenContextUrl);
            //匹配cacheMatcher正則匹配成功改寫URL并設置到CachedRewriteMatch中
            } else if (cacheMatcher.matches()) {
                rewrittenContextUrl = "/" + this.rewritePathMappings(cacheMatcher.group(2));
                rewrittenUrl = request.getContextPath() + rewrittenContextUrl;
                return new CachedRewriteMatch(rewrittenUrl, rewrittenContextUrl, cacheMatcher.group(1));
            } else {
                return null;
            }
        }
    

        執行我們的payload后當然會進入cacheMatcher的匹配,會獲取/;/WEB- INF/web.xml設置給rewrittenContextUrl,將rewrittenContextUrlrequest.getContextPath()拼接得到rewrittenUrl,在Confluencerequest.getContextPath()為空,所以rewrittenContextUrl=rewrittenUrl,下面將這些屬性賦值到CachedRewriteMatch屬性中。

    public CachedRewriteMatch(String rewrittenUrl, String rewrittenContextUrl, String staticHash) {
            this.rewrittenUrl = rewrittenUrl;
            this.rewrittenContextUrl = rewrittenContextUrl;
            this.staticHash = staticHash;
        }
    
    handleRewrite

    下面我們分析handleRewrite方法

    • 判斷overiddenRequestParametersoveriddenMethod是否為空,為空則對request包裝
    • finalRewrittenRequest中保存了rewrittenUrl,所以這里會進入IF語句,執行doRewrite方法
    private void handleRewrite(ServletRequest request, ServletResponse response) throws ServletException, IOException {
            if (!this.rewriteHandled) {
                this.rewriteHandled = true;
                if (response instanceof UrlRewriteWrappedResponse && request instanceof HttpServletRequest) {
                    HashMap overiddenRequestParameters = ((UrlRewriteWrappedResponse)response).getOverridenRequestParameters();
                    String overiddenMethod = ((UrlRewriteWrappedResponse)response).getOverridenMethod();
                    if (overiddenRequestParameters != null || overiddenMethod != null) {
                        request = new UrlRewriteWrappedRequest((HttpServletRequest)request, overiddenRequestParameters, overiddenMethod);
                    }
                }
                if (this.finalRewrittenRequest != null) {
                    this.responseHandled = true;
                    this.requestRewritten = this.finalRewrittenRequest.doRewrite((HttpServletRequest)request, (HttpServletResponse)response, this.parentChain);
                }
                if (!this.requestRewritten) {
                    this.responseHandled = true;
                    this.parentChain.doFilter((ServletRequest)request, response);
                }
            }
        }
    

        下面分析doRewrite方法, 執行CachedRewriteMatch.execute方法,這里可以看到將請求轉發到/;/WEB- INF/web.xml中處理。

    public boolean doRewrite(HttpServletRequest hsRequest, HttpServletResponse hsResponse, FilterChain chain) throws IOException, ServletException {
            return this.rewriteMatch.execute(hsRequest, hsResponse);
        }
        public boolean execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            ResourceDownloadUtils.addPublicCachingHeaders(request, response);
            request.setAttribute("_statichash", this.staticHash);
            request.getRequestDispatcher(this.rewrittenContextUrl).forward(request, response);
            return true;
        }
    

    思考

        上面我們已經分析了我們的請求如何被UrlRewriteFilter處理并轉發,但是我還有一些問題?

    為什么不能直接訪問;/WEB-INF/web.xml觸發漏洞?

        當我直接訪問/;/WEB-INF/web.xml則返回404,但在目標通過Forward轉發到這個請求卻可以讀取文件,這是為什么?

    直接訪問過程

        在StandardContextValve中會判斷當前的路徑是否以/WEB-INF//META- INF/開始,如果是則返回404,不會執行后面的請求。那么有同學可能就要問了,我請求的地址明明是/;WEB- INF/,為什么到這里就變成了/WEB-INF/是在哪一步對請求的路徑做了處理呢?

    final class StandardContextValve extends ValveBase {
        private static final StringManager sm = StringManager.getManager(StandardContextValve.class);
        public StandardContextValve() {
            super(true);
        }
        public final void invoke(Request request, Response response) throws IOException, ServletException {
            MessageBytes requestPathMB = request.getRequestPathMB();
            if (!requestPathMB.startsWithIgnoreCase("/META-INF/", 0) && !requestPathMB.equalsIgnoreCase("/META-INF") && !requestPathMB.startsWithIgnoreCase("/WEB-INF/", 0) && !requestPathMB.equalsIgnoreCase("/WEB-INF")) {
                Wrapper wrapper = request.getWrapper();
                if (wrapper != null && !wrapper.isUnavailable()) {
                    try {
                        response.sendAcknowledgement();
                    } catch (IOException var6) {
                        this.container.getLogger().error(sm.getString("standardContextValve.acknowledgeException"), var6);
                        request.setAttribute("javax.servlet.error.exception", var6);
                        response.sendError(500);
                        return;
                    }
                    if (request.isAsyncSupported()) {
                        request.setAsyncSupported(wrapper.getPipeline().isAsyncSupported());
                    }
                    wrapper.getPipeline().getFirst().invoke(request, response);
                } else {
                    response.sendError(404);
                }
            } else {
                response.sendError(404);
            }
        }
    }
    

        在CoyoteAdapter#postParseRequest中, 會對傳入的路徑進行URL解碼和規范化,并判斷路徑是否為web-inf ,所以正常請求無法訪問WEB-INF 下的內容。

    MessageBytes decodedURI = req.decodedURI();
            if (undecodedURI.getType() == 2) {
                decodedURI.duplicate(undecodedURI);
                this.parsePathParameters(req, request);
                try {
                    req.getURLDecoder().convert(decodedURI, false);
                } catch (IOException var19) {
                    response.sendError(400, "Invalid URI: " + var19.getMessage());
                }
                if (!normalize(req.decodedURI())) {
                    response.sendError(400, "Invalid URI");
                }
                this.convertURI(decodedURI, request);
                if (!checkNormalize(req.decodedURI())) {
                    response.sendError(400, "Invalid URI");
                }
            } else {
                decodedURI.toChars();
                CharChunk uriCC = decodedURI.getCharChunk();
                int semicolon = uriCC.indexOf(';');
                if (semicolon > 0) {
                    decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(), semicolon);
                }
            }
    

    轉發訪問過程

        上面我們分析了正常請求下無法訪問WEB-INF下文件的原因,那么我們再思考一下,為什么轉發過去的URL就可以訪問web-inf下的內容呢?

    首先我們可以猜測一下,是否是因為轉發過的請求不會再經過StandardContextValve 的處理導致的?

        答案是肯定的,StandardContextValve只會在我們請求時處理一次,轉發的請求不會再經過StandardContextValve的處理,這也是轉發請求可以繞過限制訪問WEB- INF下的內容的原因。

    為什么轉發請求會被DefaultServlet處理?

        我們分析過轉發請求的地址時,轉發的地址是/;/WEB- INF/web.xml,而DefaultServlet匹配的地址應該是/,為什么這個請求會被DefaultServlet進行處理?

        在CachedRewriteMatch#execute中,通過request.getRequestDispatcher(this.rewrittenContextUrl).forward(request, response);完成轉發操作,而執行request.getRequestDispatcher(this.rewrittenContextUrl)wrapper.instance已經被賦值為DefaultServlet

        在ApplicationContext#getRequestDispatcher中首先對路徑規范化,這個過程會將我們的/;/去掉。

    public RequestDispatcher getRequestDispatcher(String path) {
            if (path == null) {
                return null;
            } else if (!path.startsWith("/")) {
                throw new IllegalArgumentException(sm.getString("applicationContext.requestDispatcher.iae", new Object[]{path}));
            } else {
                int pos = path.indexOf(63);
                String uri;
                String queryString;
                if (pos >= 0) {
                    uri = path.substring(0, pos);
                    queryString = path.substring(pos + 1);
                } else {
                    uri = path;
                    queryString = null;
                }
                //路徑規范化
                String uriNoParams = stripPathParams(uri);
                String normalizedUri = RequestUtil.normalize(uriNoParams);
                ...
                this.service.getMapper().map(this.context, uriMB, mappingData);
                ...
    

        在map方法中獲取Wrapper保存到mappingData中。在Mapper#internalMapWrapper中將獲取Wrapper,首先會根據路徑匹配獲取Wrapper,如果沒有匹配到則默認由DefautlWrapper處理。

    public void map(Context context, MessageBytes uri, MappingData mappingData) throws IOException {
            ...
            this.internalMapWrapper(contextVersion, uricc, mappingData);
        }
     private final void internalMapWrapper(Mapper.ContextVersion contextVersion, CharChunk path, MappingData mappingData) throws IOException {
            ...
         //如果沒匹配到則默認交給DefaultWrapper處理
       if (mappingData.wrapper == null && !checkJspWelcomeFiles) {
                    if (contextVersion.defaultWrapper != null) {
                        mappingData.wrapper = (Wrapper)contextVersion.defaultWrapper.object;
                        mappingData.requestPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
                        mappingData.wrapperPath.setChars(path.getBuffer(), path.getStart(), path.getLength());
                        mappingData.matchType = MappingMatch.DEFAULT;
                    }
        }
    

    為什么DefaultServlet會讀取web.xml中的內容?

        在DefaultServlet#service會根據請求的類型調用不同的方法,由于我們使用的GET請求,所以會調用doGet處理請求,而doGet又通過serveResource完成具體的處理操作,這里為了能讓大家看的比較清晰,我對代碼做了很多簡化,大致可以看出根據我們傳入的路徑加載資源,通過copy將讀取的內容輸出到response中。

    protected void serveResource(HttpServletRequest request, HttpServletResponse response, boolean content, String inputEncoding) throws IOException, ServletException {
          String path = this.getRelativePath(request, true);
          WebResource resource = this.resources.getResource(path);
          InputStream source = resource.getInputStream();
          ServletOutputStream ostream = null;
         ostream = response.getOutputStream();
         OutputStreamWriter osw = new OutputStreamWriter(ostream, charset);
         PrintWriter pw = new PrintWriter(osw);
         this.copy((InputStream)source, (PrintWriter)pw, (String)inputEncoding);
         pw.flush();
     }
    

    漏洞修復

    修復版本:

    • 7.4.10

    • 7.12.3

    • 7.13.0

    • 7.14.0

        對比修復版本的補丁,主要在ConfluenceResourceDownloadRewriteRule中,在matches之前,首先循環對URL解碼,并將;替換為%3b

    ,那么為什么把; URL編碼后可以修復漏洞呢?

        是因為在ApplicationContext#getRequestDispatcher中路徑規范化操作在解碼操作之前,所以可以正確修復漏洞。

    string文件屬性
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    個人比較偏向于滲透NET開發平臺的站點,因為其不區分大小寫,在生成字典的時候不需要花費太多時間。
    知道了殺軟在ring0的監測原理,我們該如何進行繞過呢?
    截住 APP 重打包就一定程度上防止了病毒的傳播。如果 PermissionGroup 的屬性為空,會導致權限定義無效,且其他 APP 無法使用該權限。
    滲透技巧總結
    2022-01-28 21:33:52
    聲明以下技巧不應用于非法用途Tips 1. 手動端口探測nmap的-sV可以探測出服務版本,但有些情況下必須手動探測去驗證使用Wireshark獲取響應包未免大材小用,可通過nc簡單判斷eg.對于8001端口,nc連接上去,隨便輸入一個字符串,
    因為程序肯定是病毒,我就不上傳殺毒網去查殺了。正常我們在分析一個未知惡意程序的時候,流程都是要先上傳殺毒網看看。 用PEID進行查殼,顯示未加殼,程序采用Delphi語言開發。
    在第一時間采取響應的措施,恢復業務到正常,調查安全事件發生的原因,避免同類事件發生,提供數字證據
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类