Legacy systems are often the backbone of financial institutions, operating silently and reliably for years. However, under the hood, technical debt can easily morph into a significant security risk if left unchecked. During one of our engagements, a large legacy financial application was analyzed using a code review approach. Unfortunately, multiple critical vulnerabilities were discovered hidden within the older codebase. However, legacy doesn’t mean a lost cause; with some maintenance work, it is possible to remove the attack vectors, and as Securitum experts, we were able to help.
CASE 1 - A DESERIALIZATION HAT TRICK
Insecure deserialization is a notoriously devastating vulnerability, often leading directly to Remote Code Execution (RCE). During the engagement, three different insecure deserialization attack vectors were discovered.
RestEasy
In older JBoss releases, a @Consumes annotation was necessary to actually tell the server which Content-Type header values should be allowed to be consumed by the REST backend. An example of the vulnerable code without the annotation:

The project uses the resteasy-jaxrs library in version 3.0.19.Final, which is vulnerable by default, automatically registering a SerializableProvider class. Unfortunately, all the attacker needs to do is send a request to the vulnerable endpoint with a ysoserial generated payload containing a valid gadget chain, which is almost guaranteed in Java applications:

The payload is then deserialized during the ObjectInputStream.readObject() call of the SerializableProvider class. The deserialization occurs before the particular endpoint method is even called; therefore, it is always unauthenticated with zero requirements for exploitation. This makes it a critical, zero-click entry point into the system.
Remediation recommendations:
• RESTEasy must be updated to the latest stable version. In newer versions, handling the deserialization of native Java objects is disabled by default or significantly restricted.
• If the transmission of deserialized Java objects is not required by the application, the SerializableProvider component must be explicitly disabled in the JAX-RS configuration or through the application server context parameters.
• Accepted media types (e.g., @Consumes(MediaType.APPLICATION_JSON)) should be explicitly defined by every single endpoint. Leaving endpoints without this annotation should be strictly avoided.
• It is highly recommended that the mechanism introduced in JEP 290 be utilized, which allows for filters to be defined for incoming data streams. Class validation is enabled by this filter before instances are created. This can be implemented globally for the entire JVM or locally for a specific stream.
XML deserialization (not to be confused with XML External Entities)
The application parses XML to construct Java Beans objects, which relies on an insecure deserialization attack vector. Please note that even though the attack is XML-based, this is not an XML External Entities vulnerability. It is, rather, a feature of object construction from XML templates during runtime, which has dangerous side effects.
A controller accepts a user-controlled PayOffTransferDTO JSON object:

It is then saved by the saveDocument() method through another service layer, which maps the DTO to an XML-based model:

The XMLUtils class is the culprit here, because it uses java.beans.XMLDecoder to create objects from XML:

Note how, so far, the JSON has been reshaped into XML, and there is XML lying around in the database. Not ideal, but there is no deserialization yet — we’ve just been serializing the object, so what is the risk? Unfortunately, there are features that take the poisoned XML object template and parse it with the above-mentioned parseXML() method from the XMLUtils class.
The code for the document preview endpoint is as follows:

At this stage, it’s game over. The XML has been deserialized, poisoning the object construction process. An example payload that could be stored in the JSON field mentioned at the beginning is shown below:

XML is also a deserialization vector (along with JSON, YAML, and so on, in other instances of deserialization vulnerabilities). The payload does not need to be a binary stream.
Remediation recommendations:
• The XML engine should be changed entirely: Instead of relying on object deserialization (Java Beans), secure data mapping libraries like JAXB or Jackson XML, or standard parsers (SAX/DOM) deployed in a secure configuration, must be used.
• DTDs and external entities must be disabled: To prevent XML External Entity (XXE) attacks, DTD definitions and external entities must be explicitly disabled in every XML parser. An example configuration for DocumentBuilderFactory is shown below:

• Schema validation (XSD) should be implemented: Before the XML document is actually processed, its structure and data types should be strictly verified against a defined XSD schema file, acting as a whitelist.
• Existing data must be sanitized: An audit of the existing database records must be conducted for the presence of malicious tags such as < object >, < void >, < array >, or < class >, which might have been injected previously by XMLEncoder.
Please note that the XML external entities recommendation is just here for further hardening, as it was not the primary attack vector here. It’s just that parsing XML always carries risk.
ObjectInputStream - Plain object juggling
A third deserialization attack vector was identified, based on ObjectInputStream. A plain ObjectMessage object is read by the application in a message events queue:

A keen reader may notice that there is a !ObjectMessage.class.isAssignableFrom(message.getClass()) condition in the code. Could it prevent the deserialization from happening in the Object object = ((ObjectMessage) message).getObject() line? Unfortunately not. It only validates that the passed object is a Java Message Service object, but the malicious payload may be stored within the JMS container. Checking the type after the fact offers a false sense of security.
Remediation recommendations:
• It is generally highly recommended that object deserialization not be used at all, but if no other choice is left by business requirements, the code must be hardened. ObjectInputFilter (JEP 290) should be used to define a strict whitelist of permitted classes.
• The data transmission format should be changed to a secure, text-based format (such as JSON, XML, or Protobuf), and TextMessage should be utilized instead of ObjectMessage.
• Write permissions must be restricted in the directories from which files are loaded, and the implementation of digital signatures should be strongly considered.
• The application should be thoroughly purged of unused or outdated library versions from the commons-collections family to reduce available gadget chains.
CASE 2 - A JWT MESS
Managing identity and authorization securely is hard enough, but it becomes exponentially more difficult when foundational libraries are misconfigured.
Algorithm Confusion
The application uses JSON Web Tokens for authorization purposes. Unfortunately, the jjwt library used in version 0.9.0 is vulnerable to Algorithm Confusion by default and requires additional steps to secure it against such attacks.
An example of a token verifier class:

The setSigningKey() method in version 0.9.0 is severely vulnerable to Algorithm Confusion attacks. It accepts a cryptographic key and implicitly trusts the algorithm defined within the token’s header—a parameter that is completely controlled by an attacker.
An attacker can exploit this using two primary attack scenarios:
• The alg: none attack: A malicious token is crafted by the attacker, and the algorithm field in the header is modified to none. If the parser in version 0.9.0 isn’t correctly configured (for instance, the requireAlgorithm(SignatureAlgorithm.HS512) method constraint is missing), the token may be blindly accepted without any signature validation, being treated as legitimate.
• Algorithm Confusion (RS256 vs HS256): If a symmetric signature (HMAC) is anticipated by the application, but the algorithm in the header is switched to an asymmetric one (e.g., RS256) by the attacker, the library becomes thoroughly confused. In certain specific configurations, the server’s publicly available public key can be extracted by an attacker and used as the HMAC secret to cryptographically sign their own forged token.
This attack is critical because arbitrary data is allowed to be injected directly into the claims, facilitating a total bypass of the authentication mechanism.
Remediation recommendations:
• An immediate update of the JJWT library to the latest stable release (the 0.12.x series or newer) is strongly recommended. A completely overhauled API that strictly enforces secure practices by design is introduced by the newer versions.
• If an immediate update isn’t feasible, the parsing logic must be modified so that the algorithm is explicitly verified before attempting to read the data.
• Furthermore, it must be ensured that the secret keys boast adequate length and entropy (at least 512 bits for HS512).
• The application must be explicitly configured so the none algorithm is never accepted in the token header.
• It must be verified that public keys (if utilized) are never inadvertently exposed on public endpoints, such as Spring Boot Actuator paths.
Weak Secret
A weak, hardcoded secret was also found in the application:

The usage of the :1[...]6 syntax following the key name in the @Value annotation dictates that if the security.jwt.secret key fails to be located within the configuration files or environment variables, it will automatically fall back to the value 1[...]6. In the audited code, this represents a low-entropy password, leaving the tokens heavily susceptible to dictionary and brute-force attacks.
Another instance of the same secret was found in a different service:

Within the source code, a parser tasked with processing variables from configuration files was identified. Simultaneously, not a single instance of the jwt_key variable was discovered, heavily indicating that the application is entirely reliant on the default value defined within the code. This essentially renders the JWT cryptography useless against offline cracking.
Remediation recommendations:
• It is highly advised that hardcoded secrets be completely eradicated from the source code. A default fallback value for a cryptographic key must not be retained by the application. If a secret isn’t defined in a secure source, a fatal error should be thrown by the application during startup, and execution should be safely halted.
• A comprehensive rotation of the JWT secret must be executed across all deployment environments.
• Secure secret vaults (such as HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault) should be utilized for robust key management.
• Appropriate key strength must be guaranteed; for the HS512 algorithm utilized in the source code, a bare minimum of 512 bits of entropy must be possessed by the cryptographic key.
CASE 3 - XML EXTERNAL ENTITIES
An XML External Entities (XXE) type of vulnerability was also found during the code review. Multiple cases of insecure parser configuration were identified, which allow standard XML parsing behavior to be exploited by attackers to potentially read internal files or trigger Server-Side Request Forgery (SSRF).
DocumentBuilderFactory

In this context, there are five distinct parse() methods that accept various data types: InputSource, String, InputStream, StringReader, and File. Inside the method bodies, the input is forcibly cast to the InputSource type, and subsequently, the parse(InputStream) method is invoked. Strikingly, in none of these methods was any XML parser security configuration found to prevent external entities from being processed.
Another DocumentBuilderFactory
Yet another DocumentBuilderFactory

Remediation recommendations:
• Support for DOCTYPE declarations must be completely disabled across all XML parsers.
• Ideally, data exchange formats should be migrated to those that inherently do not support entities, such as JSON or Protobuf.
• If retaining the XML format is unavoidable, a secure configuration for DocumentBuilderFactory is required to be created by disabling DOCTYPE instructions and external entities entirely. As per OWASP guidelines, the following parameters must be set before newDocumentBuilder() is invoked:

• It must be ensured that all XML parsers discovered throughout the source code are configured using this exact pattern, or an equivalent secure method.
FINAL SUMMARY
Antiques are great, but they need maintenance. As this code review showed, older frameworks and unpatched libraries can easily hide critical vulnerabilities. During the engagement, we found a “hat trick” of insecure deserialization vectors, serious flaws in JWT authorization, and weak, hardcoded secrets. On top of that, insecure default XML parser configurations left the application open to XXE attacks.
The takeaway is simple: accumulated technical debt presents real risks, and older code needs continuous care to survive in today’s threat landscape. However, with some maintenance work, it is possible to fix these issues. By updating libraries, setting up safe default configurations, and removing hardcoded cryptographic keys, we can successfully remove the attack vectors and keep legacy systems operating safely.