보안정보
전문화된 보안 관련 자료, 보안 트렌드를 엿볼 수 있는
차세대 통합보안관리 기업 이글루코퍼레이션 보안정보입니다.
Apache Struts2 원격코드실행 취약점 (CVE-2017-9791) 분석
2017.10.11
29,937
서비스사업본부 보안분석팀 박병욱
1. 개요
Apache Struts2는 Struts1의 Action을 사용하기 위해 Struts1 Plugin을 기본으로 제공하고 있다. CVE-2017-9791 취약점은 Struts1 Plugin이 활성화된 상황에서 ActionMessages Class를 통해 특정 입력 값을 처리할 때 공격자가 원격으로 임의의 코드를 실행할 수 있는 취약점이다. 해당 취약점은 현재 보안 패치 및 조치 가이드가 제공된 상태이나 공격방법이 매우 쉬우며 공격을 받을 시 시스템에 미치는 영향도가 크기 때문에 CVE-2017-9791 취약점에 대해 분석해 보았다.
2. 영향 받는 소프트웨어
CVE |
영향 받는 소프트웨어 |
위험도 |
보안 패치 버전 |
CVE-2017-9791 (S2-048) |
Apache Struts 2.3.x 버전에서 Struts1 Plugin을 사용하는 경우 |
High |
Apache Struts 2.5.12 |
3. 취약점 원리
CVE-2017-9791취약점의 핵심은 Apache Struts2-Struts1 Plugin의 Struts1Action.java파일에 있다. Struts1Action.java파일을 살펴 보면 [그림 1]과 같이 Struts1Action Class의 execute Method가 getText 함수를 호출 하는 것을 볼 수 있다. 이 getText 함수는 OGNL표현 형식을 실행 시키며 공격자는 getText의 입력 내용을 제어 할 수 있다.

Apache Struts2에서 제공하는 웹 애플리케이션 Showcase의 SaveGangsterAction페이지를 이용하여 CVE-2017-9791취약점이 어떻게 발생하는지 알아보기로 한다.
SaveGangsterAction페이지 살펴보면 이름, 나이 등을 입력하는 페이지로 사용자에게 제공되는 성공메시지는 Gangster Name 필드 값을 이용하여 사용자에게 보여 주는 것을 확인 할 수 있다.

[그림2] 결과 페이지에서 사용자에게 반환되는 메시지는 아래와 같이 saveGangsterAction.java 파일에 있는 execute Method에 의해 메시지가 작성되어 보여지게 된다. 여기서 Gangster Name 필드가 CVE-2017-9791 취약점에 취약하다는 것을 의심해 볼 수 있다.

취약점이 발생하는 흐름을 살펴 보면, Name 필드에 입력된 값은 requestMessage목록에 메시지를 저장하는 Action.java의 addMessage Method를 호출하는 단계를 수행하고 Struts1Action.java를 거처 TextProviderSupport.Java의 getText Method에 도달 하게 된다.
[그림 4]는 LocalizeTextUtil.Java에서 findText Method를 호출하는 코드로 findText Method는 키에 대한 로컬 메시지를 찾는 역할을 담당하며, OGNL 코드를 평가 한다.
만약 제공된 키에 대한 메시지를 찾을 수 없는 경우 findText Method는 getDefaultMessage Method를 호출 한다.

위 [그림 4]를 보면, TextParseUtil.translateVariables Method에 대한 호출이 있는 것을 확인 할 수 있다. translateVariables은 그림 [5]와 같다.

translateVariables Method는 [그림 4-6] 에서 볼 수 있듯이 이전에 찾은 키 또는 메시지를 사용하여 OgnlTextParser.java의 parser.evaluate Method를 호출한다.

[그림 6] OgnlTextParser.java의 parser.evaluate Method 호출
Parser.evaluate Method 는 메시지 필드에서 OGNL 코드를 Parsing하며 메시지에서 "$ {" 또는 "% {" 문자열을 확인하고 변수 "var"를 만드는 역할을 한다.

전체 메시지를 처리 한 후 마지막으로 변수 var의 값은 SaveGangsterAction.java에서 ("Gangster " + gform.getName() + " added successfully")의 gform.getName()에 값이 들어가게 되며 앞서 살펴본 [그림 4-2]와 같이 사용자에게 성공 메시지를 반환하게 된다. 이 과정에서 OGNL 코드가 존재한다면 성공메시지가 사용자에게 반환되기 전 OGNL 코드가 실행되어 원격에서 임의의 코드가 실행 될 수 있다.
4. 취약점 테스트
1) Proxy를 이용하여 취약점 테스트
[그림 2] SaveGangsterAction페이지에서 Proxy를 이용하여 취약한 Name 매개변수에 아래와 같이 OGNL 형식의 공격 코드를 삽입하여 서버로 전송 한다. 여기서는 / etc / passwd 파일을 읽도록 공격 코드를 삽입 하였다. (※ 주의 : 아래 코드는 허가 받지 않은 곳에 사용을 금지합니다.)

모든 메시지를 처리 후 사용자에게 보여주는 성공메시지가 사용자에게 반환되기 전 삽입한 공격코드에 의해 원격에서 임의의 코드가 실행 되는 것을 확인할 수 있다.

2) Python 기반의 Apache Struts2 공격 도구 이용하여 취약점 테스트
CVE-2017-9791 취약점을 공격할 수 있는 Python 기반의 공격 도구가 공개되어 있다. 사용법은 대상 URL과 명령어만 입력하면 되는 간단한 구조로 되어 있다.

와이어샤크를 이용하여 공격 패킷을 살펴 보면 앞서 Proxy를 이용 하여 테스트를 진행한 것과 동일하게 동작 하는 것을 확인할 수 있다.

현재 Apache Struts의 OGNL Injection 취약점이 많이 보고되고 있다. 공격자는 OGNL Injection 취약점을 악용하는 것이 다른 공격 경로에 비해 상대적으로 쉽고 간단하기 때문에 많이 이용하고 있는 것으로 보인다.
5. 대응 방안
1) Apache Struts2 업그레이드
Apache Struts2 버전 확인 및 업그레이드 방법은 월간보안동향 4월호에서 언급하였기에 여기서는 생략한다. 최신 버전으로 업그레이드를 진행하는데 있어 한가지 주의 해야 할 점은 Apache Struts2 최신 버전에서는 Apache Strus2-Strus1 Plugin이 삭제되어, 사용할 수 없기 때문에 현재 환경을 충분히 검토 후 업그레이드를 진행해야 한다.
2) ActionMessage 전달 방식 변경
ActionMessage 전달 방식을 [그림 4-11]과 같이 원시 메시지를 그대로 전달하는 방식(①) 대신 리소스 키를 사용하여 전달하는 방식(②)으로 변경 한다.

ActionMessage 전달 방식을 리소스 키를 이용하여 전달 하는 방식으로 변경 하는 경우 [그림 12]와 같이 OGNL 코드가 실행되는 것을 방지할 수 있다.

3) Snort Rules 적용
NO |
SNORT |
1 |
alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SERVER-APACHE Apache Struts remote code execution attempt"; flow:to_server,establishedfast_pattern:onlyhttp_headercontent:"newnocasehttp_headerpcre:"/news+(java|org|sun)/Hi"; reference:cve,2017-9791; classtype:attempted-admin |
2 |
alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SERVER-APACHE Apache Struts remote code execution attempt"; flow:to_server,establishedcontent:"Content-Typenocasehttp_headerhttp_headerognlfast_patternnocasehttp_headercontent:"multipart/form-data"; nocasehttp_headerclasstype:attempted-admin |
3 |
alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SERVER-APACHE Apache Struts remote code execution attempt"; flow:to_server,establishedfast_pattern:onlyhttp_client_bodycontent:"newnocasehttp_client_bodypcre:"/news+(java|org|sun)/Pi"; reference:cve,2017-9791; classtype:attempted-admin |
4 |
alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg:"SERVER-APACHE Apache Struts remote code execution attempt"; flow:to_server,establishedcontent:"Content-Dispositionnocasehttp_client_bodyhttp_client_bodyognlfast_patternnocasehttp_client_bodycontent:"form-datanocasehttp_client_bodyclasstype:attempted-admin |
[표 1] Snort Rules 목록
CVE-2017-9791 취약점은 월간동향보고 4월호에서 분석한 Struts2의 자카르타(Jakarta) 플러그인 오류 처리과정에서 임의코드가 실행되는 CVE-2017-5638 취약점과 비교하였을 때 취약점이 발생하는 포인트만 다를 뿐 공격 코드는 거의 비슷하다. 그렇게 때문에 CVE-2017-5638 취약점 Snort Rule이 적용되어 있다면 CVE-2017-9791 취약점 역시 탐지가 가능하다.
6. 참고 자료
[1] Apache Struts2 S2-048
https://cwiki.apache.org/confluence/display/WW/S2-048
[2] CVE-2017-9791 Exploit code
https://github.com/dragoneeg/Struts2-048
[3] Apache Struts 2.3.x Showcase - Remote Code Execution (PoC)
https://www.exploit-db.com/exploits/42324/
[4] Apache Struts2远程代码执行漏洞S2-048 CVE-2017-9791 分析和防护方案
http://toutiao.secjia.com/apache-struts2-rce-cve-2017-9791
[5] CVE-2017-9791: Analysis of RCE in the Struts Showcase App in Struts 1 Plugin
https://www.imperva.com/blog/2017/07/cve-2017-9791-rce-in-struts-showcase-app-in-struts-1-plugin/
[6] ActionMessage 전달 방식 변경 소스코드 비교
https://github.com/apache/struts/commit/73da12e723c2737bd515946588ddcd898acf584a