flawopen.com/Incidents/equifax-struts-ognl-rce-july-2017
Equifax 침해: 거의 1억 5천만 명 데이터 영향
CVE-2017-5638은 패치되지 않은 Equifax Struts 포털에서 Content-Type 헤더를 통한 OGNL 주입을 허용했습니다. 공격은 약 76일간 이어졌고 거의 1억 5천만 명의 데이터가 영향을 받았습니다.[1], [4]
배송 소프트웨어가 거부된 배송 라벨을 오류 메시지에 넣는다고 상상해 보세요. 그 메시지가 라벨을 실행 가능한 지시문 템플릿으로 해석하면, 보낸 사람은 잘못된 주소를 알리는 데 그치지 않고 소프트웨어가 지시를 따르게 할 수 있습니다. Struts 패치는 이미 나와 있었지만 Equifax는 오래된 관리자 목록에 공지를 보냈고 네트워크 검사도 포털의 취약점을 탐지하지 못했습니다.[1], [4]
핵심 개념 및 용어
OGNL- Object-Graph Navigation Language. Struts가 Java 객체 속성을 읽고 설정할 때 쓰는 식 언어입니다. 공격자의 텍스트를 OGNL로 평가하면 공격자의 코드를 실행하게 됩니다.[1], [6]
Jakarta 멀티파트 파서- 파일 업로드를 처리하는 Struts 구성 요소입니다. 잘못된 Content-Type을 거부할 때 이후 평가되는 오류 메시지를 만들었습니다.[1], [3], [2]
식 인젝션(CWE-917)- 신뢰할 수 없는 입력이 인터프리터가 평가하는 식 안으로 들어가, 데이터가 명령이 되는 것입니다.[6]
자산 목록- 어떤 시스템에서 어떤 소프트웨어의 어떤 버전이 돌아가는지 기록한 것입니다. 이것이 없으면 패치 공지를 필요한 장비와 연결할 수 없습니다.[4]
프레임워크 매핑
- 이 가이드에서 설명하는 약점의 CWE 참조: CWE-20 CWE-917
- CISA KEV — 추가일 2021-11-03 — CVE-2017-5638
사고 타임라인
Apache가 보안 공지 S2-045와 수정된 Struts 2.3.32, 2.5.10.1을 발표합니다.[1]
Equifax는 이후 GAO에 별도의 침입이 5월 13일 시작되어 공격자들이 그때부터 개인정보를 추출했다고 설명했습니다. 그보다 앞서 무단 접근이 있었지만 당시 데이터는 탈취되지 않았습니다.[4]
Equifax가 트래픽 검사 장비의 만료된 인증서를 갱신한 뒤 수상한 트래픽을 발견합니다.[4]
Equifax가 침해를 공개합니다. 이후 GAO는 미국 내 최소 1억 4,550만 명과 미국 밖 약 100만 명의 데이터가 영향을 받았다고 보고합니다.[4]
단계별 공격 실행 흐름
소스 코드 비교: 취약한 구현 vs 보안 패치
설명용 Java 코드이며 Struts 소스가 아닙니다. 2.3.32와 2.5.10.1의 실제 수정은 멀티파트 오류를 만드는 방식을 바꿨습니다. 강화된 버전은 고정된 메시지 키를 쓰고 헤더는 위치 인자로만 넘기므로, 텍스트로 서식화될 뿐 식의 일부가 되지 않습니다. 또 미디어 타입처럼 보이지 않는 값은 자리 표시자로 바꾸므로, 나중에 메시지를 바꾸더라도 헤더가 평가되는 문자열로 돌아올 수 없습니다.[3], [2], [6]
import com.opensymphony.xwork2.TextProvider;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private final TextProvider text; // resolves messages and evaluates expressions in them
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
// 원시 헤더가 평가되는 메시지의 일부가 됩니다.
return text.getText("Invalid content type: " + contentType);
}
}import com.opensymphony.xwork2.TextProvider;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private static final Pattern MEDIA_TYPE = Pattern.compile(
"^[a-z]+/[a-z0-9.+-]+(;\\s*[a-z0-9-]+=[a-z0-9._-]+)*$");
private final TextProvider text;
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
boolean wellFormed = contentType != null && contentType.length() <= 256
&& MEDIA_TYPE.matcher(contentType.toLowerCase(Locale.ROOT)).matches();
// 고정 메시지 키를 사용하고 헤더는 위치 인수로만 전달합니다.
// 이때도 미디어 타입 형식일 경우에만 전달합니다.
return text.getText("upload.invalid.content.type",
new String[] { wellFormed ? contentType : "(malformed)" });
}
}엔지니어링 및 시스템 보안 강화 체크리스트
- ✓배포된 모든 애플리케이션의 SBOM을 유지하고, S2-045 같은 공지가 나오면 몇 시간 안에 "Struts 2.3.x는 어디서 돌아가는가?"에 답할 수 있게 합니다.[4]
- ✓취약점 스캐너 대상을 자산 목록과 대조합니다. 범위에 한 번도 들어가지 않은 호스트의 깨끗한 스캔 결과는 패치의 증거가 아닙니다.[4]
- ✓TLS 검사 및 모니터링 장비의 인증서 만료를 최소 30일 전에 경보합니다. 이번 사고에서는 만료된 인증서가 몇 달 치 유출을 가렸습니다.[4]
- ✓코드 리뷰에서 신뢰할 수 없는 텍스트를 이어 붙여 메시지 해석기, 템플릿 엔진, 식 평가기에 넘기는 호출은 반려하고 인자로 넘기게 합니다.[6]
- ✓데이터베이스를 인터넷에 노출된 애플리케이션과 분리하고, 자격 증명을 애플리케이션 파일에 두지 말며, 애플리케이션 계정의 비정상적인 쿼리 양에 경보를 울립니다.[4]
출처
- Apache Struts — 보안 공지 S2-045 (CVE-2017-5638)
- Apache Struts — multipart parser error-message fix (commit 3523064)
- Apache Struts — upstream multipart error-handling fix (commit 6b8272c)
- 미국 GAO — 데이터 보호: 2017년 침해에 대한 Equifax와 연방 기관의 조치 (GAO-18-559)
- CVE-2017-5638 — CVE 레코드
- MITRE CWE-917 — 식 언어 인젝션
- Log4Shell: 로그 한 줄이 원격 코드 실행이 되었을 때
- 명령 인젝션 — 전체 클래스 참고 자료