Expression Language (EL) 삽입

Expression Language (EL) Injection

설명

Java/Jakarta Expression Language(EL)는 문자열로 작성된 표현식에서 객체의 속성을 읽거나 변경하고 메서드를 호출할 수 있게 합니다. 신뢰할 수 없는 입력을 표현식 소스로 사용하면 입력 데이터와 실행 구조의 경계가 사라져 EL 삽입이 발생합니다.

현재 Jakarta Expression Language 6.0 명세는 java.lang.*를 기본으로 임포트하며, 공개 정적 필드·메서드와 공개 생성자 참조를 지원합니다. 실제 영향은 평가 컨텍스트에 등록된 빈, 함수, 리졸버 및 임포트에 따라 달라지지만, 공격자는 접근 가능한 애플리케이션 데이터를 읽거나 변경하고 메서드를 호출할 수 있습니다. 노출된 기능에 따라 운영 체제 명령 실행으로 이어질 수도 있습니다.

주요 API의 동작은 다음과 같습니다.

API 표현식 인수 동작
ELProcessor.eval(expression) 첫 번째 표현식을 즉시 평가합니다.
ELProcessor.getValue(expression, type) 첫 번째 표현식을 평가하고 결과를 지정한 타입으로 변환합니다.
ELProcessor.setValue(expression, value) 첫 번째 대상 표현식을 평가한 뒤 마지막 속성이나 변수를 변경합니다. 두 번째 인수는 데이터 값입니다.
ELProcessor.setVariable(name, expression) 두 번째 나중에 참조할 표현식을 파싱해 변수에 연결합니다. 첫 번째 인수는 변수 이름입니다.
ExpressionFactory.createValueExpression(context, expression, type) 두 번째 나중에 평가할 값 표현식을 파싱합니다.
ExpressionFactory.createMethodExpression(context, expression, ...) 두 번째 나중에 호출할 메서드 표현식을 파싱합니다.

javax.el은 이전 Java EE 네임스페이스이고 jakarta.el은 Jakarta EE 네임스페이스입니다.

잠재적 영향

  • 평가 컨텍스트에 노출된 애플리케이션 데이터의 무단 조회 또는 변경
  • 접근 가능한 객체·정적 메서드·생성자의 호출과 애플리케이션 기능 오용
  • 파일, 네트워크 또는 운영 체제 명령 API가 도달 가능한 경우 서버 권한으로 동작 수행
  • 계산량이 큰 표현식이나 스트림 연산에 따른 자원 소모

해결 방법

  1. EL 표현식 텍스트는 개발자가 관리하는 고정 문자열로 유지합니다. 요청 값이나 외부 설정값을 표현식에 이어 붙이거나 그대로 파싱·평가하지 마십시오.
  2. 신뢰할 수 없는 값은 표현식 소스가 아니라 데이터로 바인딩합니다. ELProcessor.defineBean 또는 VariableMapper를 사용하고, 바인딩된 값을 참조하는 고정 표현식만 평가합니다.
  3. 사용자가 작업을 선택해야 한다면 서버가 관리하는 유한한 키를 검토된 상수 표현식에 매핑합니다. 임의 문자열을 허용하는 정규식이나 일반적인 이스케이프 함수는 EL 문법 전체에 대한 안전성을 보장하지 않습니다.
  4. 고정 표현식에 필요한 빈, 함수, 리졸버 및 임포트만 컨텍스트에 노출하고 최소 권한으로 실행합니다. 이는 심층 방어이며, 공격자가 제어하는 표현식을 안전하게 만드는 새니타이저가 아닙니다.
  5. EL이 필요하지 않은 기능에서는 평가를 제거하거나 비활성화합니다. Jakarta EL 6.0은 Java SecurityManager 관련 참조를 제거했으므로 이를 표준 샌드박스 대책으로 권장해서는 안 됩니다.

예시

변경 전

java
import jakarta.el.ELProcessor;
import jakarta.servlet.http.HttpServletRequest;

final class UnsafeElEvaluation {
    Object evaluate(HttpServletRequest request) {
        String expression = request.getParameter("expression");

        ELProcessor processor = new ELProcessor();
        return processor.eval(expression); // 요청값을 EL 소스로 평가
    }
}

요청값이 ELProcessor.eval의 표현식 인수로 직접 전달됩니다. 공격자는 평가 환경에서 접근 가능한 속성이나 메서드를 선택하는 EL 문법을 주입할 수 있습니다.

변경 후

java
import jakarta.el.ELProcessor;
import jakarta.servlet.http.HttpServletRequest;

final class SafeElEvaluation {
    Object display(HttpServletRequest request) {
        String displayName = request.getParameter("displayName");

        ELProcessor processor = new ELProcessor();
        processor.defineBean("displayName", displayName); // 요청값은 데이터로 바인딩
        return processor.eval("displayName");             // 표현식은 개발자가 고정
    }
}

요청값은 빈의 데이터 값으로만 등록되고, 평가되는 표현식은 고정되어 있습니다. 사용자가 여러 기능 중 하나를 선택해야 하는 경우에도 선택 키를 서버가 관리하는 상수 표현식에 명시적으로 매핑해야 합니다.

적용 시 주의사항

ExpressionFactory.create*Expression과 ELProcessor.setVariable은 이후에 사용할 표현식을 파싱합니다. 반환된 표현식이나 변수가 실제로 평가되는 지점까지 확인하세요. 입력은 고정 표현식의 데이터로 전달하고, 바인딩한 값을 다시 표현식 소스로 평가하지 마세요.

참조