Security News

Cybersecurity news aggregator

🔓
HIGH Vulnerabilities Reddit r/netsec

Almost Impossible: Java Deserialization Through Broken Crypto in OpenText Directory Services

  • Element 1 (What):* Researchers identified a critical Java deserialization vulnerability in OpenText Directory
Read Full Article →

February 16, 2026 Almost Impossible: Java Deserialization Through Broken Crypto in OpenText Directory Services February 16, 2026 Dylan Pindur , Adam Kues , Tomais Williamson Almost Impossible: Java Deserialization Through Broken Crypto in OpenText Directory Services Introduction We recently found ourselves looking into OpenText Directory Services (OTDS). We had seen it present on our customer’s attack surface, and it seemed to be an interesting target. OTDS is a Java web application providing authentication and user management for OpenText applications. OpenText provides a number of information management products, and finding a security flaw in OTDS would provide quite a big impact, as it could lead to compromising authentication for all the integrated applications as well. Much to our dismay, OTDS did not have any immediately obvious vulnerabilities. However, there was one small mistake that led us down an almost CTF-like rabbit hole, which ultimately resulted in achieving unsafe deserialisation. The vulnerability was exploitable in the default configuration of OTDS without authentication. As always, customers of our Attack Surface Management platform were the first to know when this vulnerability affected them. We continue to perform original security research to inform our customers about zero-day vulnerabilities in their attack surface. What is the Mistake? One of the first things we do when auditing a Java application is search for any usage of Java deserialization with attacker-controlled input. A simple search for some common culprits, such as calls to readObject or usage of ObjectInputStream will shake out any low-hanging fruit that may be vulnerable. In the case of OTDS, we did find one such usage in the method cookieToMap . The name was a promising indicator that this would be a controllable value if it came from a cookie. The method with error handling omitted can be seen below. public static Map<String, Object> cookieToMap(String cookieName, String cookieVal) { byte[] compressed = OtdsUtils.getByteArrayFromSignedArray(OtdsUtils.safeDecode(cookieVal), OtdsAsConfig.getCookieSecretKey()); byte[] decompressed = CompressionUtils.decompress(compressed); ByteArrayInputStream bais = new ByteArrayInputStream(decompressed); ObjectInputStream ois = new ObjectInputStream(bais); Map<String, Object> theMap = (Map<String, Object>)ois.readObject(); return theMap; } In this method readObject is called and then cast to the target type after deserialisation occurs, an almost textbook example of this class of vulnerability. However, what prevents this from being immediately exploitable is the call to getByteArrayFromSignedArray . It appears that some validation is being done, so we cannot simply pack a ysoserial payload into the cookie and achieve remote code execution. Digging into getByteArrayFromSignedArray , we found a subtle mistake that enabled us to bypass the signature check and exploit the deserialisation. Inside OtdsUtils are the following two methods, again some error handling has been omitted for brevity. public static byte[] getByteArrayFromSignedArray(byte[] signed, SecretKey key) throws Exception { byte[][] parts = splitByteArray(signed); byte[] signature = parts[0]; byte[] iv = parts[1]; byte[] message = parts[2]; Mac mac = Mac.getInstance("HmacSHA1"); mac.init(key); mac.update(iv); if (!MessageDigest.isEqual(signature, mac.doFinal(message))) { throw new OtdsException("invalid content"); } return message; } public static byte[][] splitByteArray(byte[] arr, int pos, int endpos) { List<byte[]> v = new ArrayList<byte[]>(); ByteBuffer buf = ByteBuffer.wrap(arr); buf.position(pos); while (pos < endpos) { short len = buf.getShort(); if (len > buf.remaining() || len < 0) { v.clear(); break; } byte[] bytes = new byte[len]; buf.get(bytes); pos += len + 2; v.add(bytes); } byte[][] result = new byte[v.size()][]; result = v.toArray(result); return result; } There are a few interesting things happening here. First, the signature calculation includes an initialisation vector (IV) which comes from the cookie. This is not strictly necessary, and it is not clear what purpose it serves. However, on its own, the addition of an IV should not alter the security of the signature. The second issue is how the data is split into signature, IV, and message. When this quirk is combined with the addition of the IV, the security of the signature is impacted. The data is divided into three parts, where the length of each part is specified at the start with a 2-byte short. If we break this down, after the cookie is decoded, it has the following format. 2 bytes, length of the signature Signature 2 bytes, length of the IV IV 2 bytes, length of the message Message (a serialised Java object) Importantly, the lengths are attacker-controlled, not verified, and not included in the signature calculation. The signature is only calculated from the concatenation of the IV and the message, HMAC(IV || message) . This meant we could truncate ...

Share this article