JNDI는 Java Naming and Directory Interface의 약자로 Java 애플리케이션이 네트워크에서 객체를 찾고 가져올 수 있도록 하는 표준 API이다. 응용프로그램은 JNDI를 호출하여 자원과 다른 프로그램 객체를 찾는다. 모든 자원 객체는 사용자에게 친숙하고 고유한 JNDI로 식별되며 이 자원의 객체와 JNDI 이름은 애플리케이션 서버에 포함된 이름지정 및 디렉토리 서비스에 의해 함께 바인딩된다.
Java 애플리케이션은 JNDI로 네이밍 및 디렉토리 서비스에 접근한다. JNDI를 사용하려면 JNDI 클래스와 하나 이상의 서비스 프로바이더가 있어야 하는데 이 예시로는 LDAP, CORBA, JAVA RMI 등이 있다.
Java 애플리케이션을 주소 데이터베이스나 LDAP 서버같은 외부 디렉토리 서비스에 연결할 때 사용한다. 데이터베이스가 Oracle 등으로 바뀔 가능성이 있거나 JDBC 설정이 바뀔 필요가 있을 때 JNDI를 이용하면 프로그래머나 DB 관리자는 편리하게 데이터베이스를 이용할 수 있다.
JNDILookup 클래스에서 취약점이 발생한다.
package org.apache.logging.log4j.core.lookup;
import java.util.Objects;
import javax.naming.NamingException;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.net.JndiManager;
import org.apache.logging.log4j.status.StatusLogger;
# JNDI 자원에서 lookup key
@Plugin(name = "jndi", category = StrLookup.CATEGORY)
public class JndiLookup extends AbstractLookup { # AbstractLookup이 JndiLookup 상속
#final 필드는 초기값이 저장되면 최종값이 됨
private static final Logger LOGGER = StatusLogger.getLogger(); #statusLogger(로깅 시스템에서 발생하는 이벤트 기록) 상태 검색
private static final Marker LOOKUP = MarkerManager.getMarker("LOOKUP"); #LOOKUP이 나올 때 marker 검색
static final String CONTAINER_JNDI_RESOURCE_PATH_PREFIX = "java:comp/env/"; # JNDI 자원 나올 때 자원의 맨 앞에 경로는 저걸로 고정
public JndiLookup() {
if (!JndiManager.isJndiLookupEnabled()) { #JMS(Java Message Service)에 대한 JNDI 시스템 속성이 현재 활성화되어 있는지 테스트
throw new IllegalStateException("JNDI must be enabled by setting log4j2.enableJndiLookup=true"); # throw가 에러를 발생시켜라, IllegalStateExeption이 부적절한 시기에 메소드가 호출되었음을 알림
}
}
@Override
public String lookup(final LogEvent event, final String key) {
if (key == null) {
return null;
}
final String jndiName = convertJndiName(key);
try (final JndiManager jndiManager = JndiManager.getDefaultManager()) { # InitialContext 써서 기본 JndiManager 가져옴
return Objects.toString(jndiManager.lookup(jndiName), null);
# getClass().getName() + '@' + Integer.toHexString(hashCode())
} catch (final NamingException e) {
LOGGER.warn(LOOKUP, "Error looking up JNDI resource [{}].", jndiName, e);
return null;
}
}
private String convertJndiName(final String jndiName) {
if (!jndiName.startsWith(CONTAINER_JNDI_RESOURCE_PATH_PREFIX) && jndiName.indexOf(':') == -1) { # 맨 앞에가 java:comp/env/로 시작하지 않고, :가 없을 때(-1) java:comp/env/에다가 jndiName 붙인것 반환해라
return CONTAINER_JNDI_RESOURCE_PATH_PREFIX + jndiName;
}
return jndiName; # 맨 앞에 java:comp/env/ 있고 : 있으면 jndiname이 이미 완전한 형태이므로 jndiName 반환
}
}