CVE-2020-7961: Dissecting a Liferay Portal Deserialization Exploit
Hook
A single unauthenticated HTTP request could give an attacker complete control over your Liferay Portal instance—and it all comes down to how Java handles deserialization.
Context
Liferay Portal is a widely-deployed enterprise portal platform used by Fortune 500 companies and government agencies to build intranets, customer portals, and collaboration platforms. Built on Java, it provides a robust framework for content management, document libraries, and workflow automation. However, its extensive use of Java serialization for session management and inter-service communication created a critical attack surface.
CVE-2020-7961, disclosed in early 2020, exposed a remote code execution vulnerability affecting Liferay Portal versions prior to 7.2.1 CE GA2. The vulnerability stemmed from unsafe deserialization of user-controlled data in the JSON web services layer. An unauthenticated attacker could craft a malicious serialized object, send it to a vulnerable endpoint, and execute arbitrary code with the privileges of the application server. This vulnerability belongs to a class of Java deserialization attacks that have plagued enterprise applications for years, similar to the infamous Apache Commons Collections exploits. The random-robbie/CVE-2020-7961-POC repository emerged as one of several proof-of-concept implementations demonstrating the exploitability of this flaw for security researchers and penetration testers.
Technical Insight
The core of CVE-2020-7961 lies in Liferay's JSON web service infrastructure, which accepts serialized Java objects through specific endpoints without proper validation. Liferay Portal uses Apache Axis and custom serialization mechanisms to handle SOAP and JSON-RPC requests. When processing these requests, the application deserializes objects from untrusted sources without implementing type checking or allowlisting.
The exploitation chain typically follows this pattern: First, an attacker identifies a vulnerable endpoint, usually at /api/jsonws/invoke. Second, they construct a gadget chain using libraries already present in Liferay's classpath—often leveraging Apache Commons Collections, Spring Framework, or other common enterprise Java libraries. Third, they serialize this payload using standard Java serialization. Finally, they send the crafted payload to the vulnerable endpoint, triggering deserialization and code execution.
A simplified POC might look like this:
import requests
import subprocess
import base64
# Generate malicious serialized payload using ysoserial
# This uses the CommonsBeanutils1 gadget chain
payload = subprocess.check_output([
'java', '-jar', 'ysoserial.jar',
'CommonsBeanutils1',
'touch /tmp/pwned'
])
# Base64 encode for transport
encoded_payload = base64.b64encode(payload).decode()
# Target vulnerable Liferay endpoint
target_url = 'http://vulnerable-liferay.example.com/api/jsonws/invoke'
# Craft the exploit request
data = {
'cmd': '{"p_auth": "' + encoded_payload + '"}'
}
response = requests.post(target_url, data=data)
print(f"Response status: {response.status_code}")
The real exploitation is more complex, requiring careful construction of the gadget chain to work around Java security manager restrictions and library version constraints. Tools like ysoserial provide pre-built gadget chains, but successful exploitation often requires customization based on the specific Liferay version and deployed libraries.
The vulnerability's root cause traces back to Liferay's use of ObjectInputStream.readObject() without overriding resolveClass() to implement allowlisting. When the application deserializes untrusted data, it instantiates classes specified in the serialized stream, triggering their initialization logic. Attackers exploit this by chaining together existing classes whose initialization methods can be abused to achieve arbitrary code execution.
Here's what vulnerable code might look like internally:
public Object processRequest(HttpServletRequest request) {
try {
String serializedData = request.getParameter("data");
byte[] decodedData = Base64.getDecoder().decode(serializedData);
// VULNERABLE: No validation of deserialized classes
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(decodedData)
);
return ois.readObject(); // RCE happens here
} catch (Exception e) {
log.error("Deserialization error", e);
return null;
}
}
The patch for CVE-2020-7961 implemented multiple defensive layers: input validation to reject unexpected serialized data, migration away from Java serialization for external APIs toward JSON-only parsing, and implementation of a custom ObjectInputStream subclass that validates class types against an allowlist before instantiation. Liferay also added Content Security Policy headers and strengthened authentication requirements for sensitive endpoints.
Gotcha
The random-robbie/CVE-2020-7961-POC repository suffers from several practical limitations that security professionals should understand. First, the repository provides minimal documentation and no clear usage instructions, meaning you'll need substantial background knowledge about Java deserialization attacks to use it effectively. Unlike mature exploit frameworks like Metasploit, which provide standardized interfaces and extensive options, this POC requires manual configuration and deep understanding of the vulnerability mechanics.
Second, the exploit's success depends heavily on the target environment's library versions and classpath composition. Liferay deployments vary significantly based on installed plugins, custom portlets, and enterprise integrations. A gadget chain that works perfectly against a vanilla Liferay 7.2.0 installation might fail completely against a deployment with custom security configurations or different library versions. The repository doesn't provide tooling to enumerate the target environment or automatically select appropriate gadget chains, forcing testers to manually adjust payloads through trial and error. Additionally, many production Liferay instances sit behind web application firewalls or reverse proxies that detect serialized Java objects in HTTP requests, causing the exploit to fail even when the underlying vulnerability exists. The POC doesn't include evasion techniques or guidance for bypassing these security controls, limiting its real-world effectiveness for authorized penetration testing engagements.
Verdict
Use if: You're a security researcher or penetration tester with explicit written authorization to test specific Liferay Portal instances, you already understand Java deserialization vulnerabilities and need a starting point for CVE-2020-7961 validation, or you're building internal security training materials to demonstrate why organizations must patch critical vulnerabilities. This repository serves as a basic reference implementation but requires significant expertise to operationalize. Skip if: You're looking for production-ready security testing tools (use Metasploit or Burp Suite instead), you don't have deep Java security knowledge (start with OWASP resources on deserialization), you need comprehensive exploit frameworks with documentation and support, or you're not working on an authorized engagement with legal protection. Most importantly, skip if you haven't verified that your target actually runs a vulnerable Liferay version—modern Liferay installations are patched, and your time is better spent identifying realistic attack vectors rather than chasing patched vulnerabilities. For defensive purposes, focus on ensuring your Liferay instances run version 7.2.1 CE GA2 or later, implement network segmentation to limit blast radius, and deploy application-layer firewalls with deserialization attack detection rules.