Liferay Portal is a web platform that lets you "create and connect personalized digital experiences across web, mobile and connected devices". The software has both an open source “community edition and a commercial “Digital Experience” or “Enterprise” edition. While some of our engineers had never heard of Liferay Portal, they were impressed to see that it has been forked nearly 2000 times on GitHub, as well as when Shodan returned over 7,000 hosts running Liferay Portal.
While investigating LPS-67681 for a potential Nessus detection plugin, we found that there was a Java object deserialization blacklist. Given our history with deserialization vulnerabilities and a strong aversion to blacklists, due to them rarely working, we immediately wondered if we could bypass it.
We could not find a CVE associated with deserialization and Liferay Portal in public or private databases. However, we did find the initial commit into the 7.0 branch as well as an advisory for the 6.x branch related to Java serialization. It is interesting to note that Liferay assigns the severity of ‘1’, out of a two-point system, rather than using CVSS.
Additionally, note that Liferay’s advisory indicates that the deserialization endpoints (TunnelServlet
(/api/liferay
) and Spring-Remote
(/api/spring
)) are restricted to localhost by default, and only allows user’s to add specific IPs to the ACL (no wild cards are supported). In theory, that’s pretty solid. Unfortunately, users are often shortsighted. This is proven by looking through the first few entries of Shodan. On the first couple of pages, we were able to make an initial HTTP request to two of the servers restricted endpoints on four servers to verify they are Internet addressable. How does that occur, you ask? One hint is HTTP 403 messages we received that look like this:
HTTP Status 403 - Access denied for 192.168.146.20
As you can probably guess, that wasn't the IP we tested from. In fact, that’s an internal IP. Presumably, the vulnerable servers have a proxy in front of them and some admin has added the IP of the proxy to the ACL – therefore allowing the entire Internet access to the restricted endpoints. Good for attackers, bad for organizations. This makes any potential attack on those interfaces much more interesting.
The blacklist found in the latest version (Liferay CE Portal 7.0 GA3) covers all known gadgets except C3P0, DiskFileItem variants, JRMPListener, JRMPClient, and Java DoS gadgets. For example, attempting to poke a blacklisted gadget results in:
com.liferay.portal.kernel.io.ProtectedObjectInputStream.restricted.class.names=\
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,\
org.apache.commons.collections.functors.CloneTransformer,\
org.apache.commons.collections.functors.ForClosure,\
org.apache.commons.collections.functors.InvokerTransformer,\
org.apache.commons.collections.functors.InstantiateFactory,\
org.apache.commons.collections.functors.InstantiateTransformer,\
org.apache.commons.collections.functors.PrototypeFactory$PrototypeCloneFactory,\
org.apache.commons.collections.functors.PrototypeFactory$PrototypeSerializationFactory,\
org.apache.commons.collections.functors.WhileClosure,\
org.apache.commons.collections4.functors.InvokerTransformer,\
org.codehaus.groovy.runtime.ConvertedClosure,\
org.codehaus.groovy.runtime.MethodClosure,\
org.springframework.beans.factory.ObjectFactory,\
org.springframework.core.SerializableTypeWrapper$MethodInvokeTypeProvider,\
sun.reflect.annotation.AnnotationInvocationHandler
That said, getting around the blacklist isn’t difficult. Why "getting around the blacklist" instead of "bypassing"? Typically, this involves using a byte[]
from the blacklisted ObjectInputStream
to create a new ObjectInputStream
that doesn’t have a blacklist.
If we can bypass the blacklist then what do we get? Liferay has updated both Commons Collections and Groovy to the "non-vulnerable" versions. However, there are still vulnerable libraries on the classpath
; for example, Commons BeanUtils. There is a project on GitHub that contains a list of the known bypass objects. It just so happens that Liferay includes one of these as objects via Commons Beanutils. We have written a PoC for Linux-based Liferay Portal called "beanutils_bypass.py
" and shared with the vendor. The PoC simply touches “/tmp/beans_bypass.py
” via sending an unauthenticated HTTP POST to the /api/liferay
endpoint.
We found another bypass object on the classpath
that we don’t believe anyone has yet found or reported. We can use a SerializableRenderedImage
from Oracle's jai_core.jar
to bypass the blacklist. We also wrote a PoC for Linux-based Liferay Portal called "image_bypass.py
" that simply touches “/tmp/image_bypass
” via sending an unauthenticated HTTP POST to the /api/liferay
endpoint.
In addition to the above, we can also use ysoserial’s "JRMPClient.java
" to create a connect back to an RMI Registry of our choice, from which we can then trigger remote code execution. We didn’t create a PoC for this one since it’s a little more complicated and the other PoC scripts demonstrate the bypass sufficiently. For a box of oatmeal cream pies and six-pack of Mr. Pibb, we'll loan you Jacob to write a PoC for that one too.