설명
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가 도달 가능한 경우 서버 권한으로 동작 수행
- 계산량이 큰 표현식이나 스트림 연산에 따른 자원 소모
해결 방법
- EL 표현식 텍스트는 개발자가 관리하는 고정 문자열로 유지합니다. 요청 값이나 외부 설정값을 표현식에 이어 붙이거나 그대로 파싱·평가하지 마십시오.
- 신뢰할 수 없는 값은 표현식 소스가 아니라 데이터로 바인딩합니다.
ELProcessor.defineBean또는VariableMapper를 사용하고, 바인딩된 값을 참조하는 고정 표현식만 평가합니다. - 사용자가 작업을 선택해야 한다면 서버가 관리하는 유한한 키를 검토된 상수 표현식에 매핑합니다. 임의 문자열을 허용하는 정규식이나 일반적인 이스케이프 함수는 EL 문법 전체에 대한 안전성을 보장하지 않습니다.
- 고정 표현식에 필요한 빈, 함수, 리졸버 및 임포트만 컨텍스트에 노출하고 최소 권한으로 실행합니다. 이는 심층 방어이며, 공격자가 제어하는 표현식을 안전하게 만드는 새니타이저가 아닙니다.
- EL이 필요하지 않은 기능에서는 평가를 제거하거나 비활성화합니다. Jakarta EL 6.0은 Java
SecurityManager관련 참조를 제거했으므로 이를 표준 샌드박스 대책으로 권장해서는 안 됩니다.
예시
변경 전
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 문법을 주입할 수 있습니다.
변경 후
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은 이후에 사용할 표현식을 파싱합니다. 반환된 표현식이나 변수가 실제로 평가되는 지점까지 확인하세요. 입력은 고정 표현식의 데이터로 전달하고, 바인딩한 값을 다시 표현식 소스로 평가하지 마세요.
참조
- Jakarta Expression Language 6.0 명세
- Jakarta EL 6.0
ELProcessorAPI - Jakarta EL 6.0
ExpressionFactoryAPI - CWE-917: Expression Language Injection
- OWASP Expression Language Injection
- OWASP Top 10:2025 A05 - Injection
- OWASP Top 10:2021 A03 - Injection
- OWASP ASVS 5.0.0 V1 - Encoding and Sanitization
- KISA 소프트웨어 보안약점 진단가이드(2021)