Spring4shell in Adonisjs with Mutual Tls
Spring4shell in Adonisjs with Mutual Tls — how this specific combination creates or exposes the vulnerability
Spring4Shell (CVE-2022-22965) targets a flaw in Spring MVC and Spring WebFlux where data-binding on specific controller parameters can lead to remote code execution when certain conditions are met. In an AdonisJS project that opts to handle TLS at the reverse proxy or load balancer and terminates TLS there, the application itself may listen on HTTP rather than HTTPS. When mutual TLS (mTLS) is enforced at the infrastructure layer (e.g., the proxy validates client certificates before forwarding requests), the AdonisJS app sees only authenticated, proxied requests. This can inadvertently expose endpoints to Spring4Shell-style attacks if the app or its dependencies inadvertently accept user-controlled objects for data binding, because the trust boundary is shifted upstream and the app assumes the client has already been authenticated and authorized.
Consider an AdonisJS API that parses incoming JSON and binds it directly to models or command objects. If an attacker can send a proxied request that passes mTLS inspection but contains malicious payloads crafted for Spring4Shell (e.g., nested objects with specific property patterns), and the app or an embedded Spring component uses reflection-based binding without strict whitelisting, the gadget chain can be triggered. Common patterns include exploitation via DataBinder, org.springframework.expression.ExpressionParser, or modules that expose functions or evaluation paths. The mTLS setup does not prevent this if the request content is not validated for unexpected object graphs once it reaches the application. Even if AdonisJS itself is not a Spring runtime, this scenario is relevant when integrating with backend services or microservices written in Spring that sit behind the same mTLS-terminated ingress. The risk is elevated when endpoints accept complex input structures without schema validation, enabling attackers to traverse object properties and invoke methods unintentionally exposed through configuration or legacy code.
To illustrate, an attacker might send a proxied request with a crafted JSON body designed to exploit Spring’s expression language through nested fields, leveraging mTLS to bypass IP-based restrictions. Since the proxy handles certificate verification, the AdonisJS app may see a valid client certificate context and proceed to deserialize or bind the payload. If the backend microservice does not employ strict whitelisting on allowed parameters, gadget chains involving templates, file operations, or runtime evaluation may be triggered. This highlights the importance of validating and sanitizing input even when mTLS is in place, and of auditing any integrated Spring-based services for unsafe data-binding configurations.
Mutual Tls-Specific Remediation in Adonisjs — concrete code fixes
Remediation focuses on tightening input validation, avoiding unsafe deserialization or binding, and ensuring mTLS is correctly implemented at the application layer where feasible. For AdonisJS, use schema validation for all incoming payloads and avoid binding raw request bodies directly to models. If your app participates in mTLS as a client or server, enforce certificate checks and inspect peer details before processing requests.
Example: AdonisJS server with mTLS verification using Node.js tls module and strict request validation:
const fs = require('fs');
const https = require('https');
const { validateSchema } = require('@ioc:AdonisJS/Validator');
const schema = {
type: 'object',
required: ['username', 'action'],
properties: {
username: { type: 'string', minLength: 1, maxLength: 50 },
action: { type: 'string', enum: ['read', 'write', 'delete'] }
}
};
const server = https.createServer({
key: fs.readFileSync('server.key'),
cert: fs.readFileSync('server.crt'),
ca: fs.readFileSync('ca.crt'),
requestCert: true,
rejectUnauthorized: true
}, (req, res) => {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
try {
const data = JSON.parse(body);
validateSchema(schema, data);
// Proceed with authenticated, authorized logic
res.writeHead(200);
res.end(JSON.stringify({ ok: true }));
} catch (err) {
res.writeHead(400);
res.end(JSON.stringify({ error: err.message }));
}
});
});
server.listen(8443, () => console.log('mTLS server running on 8443'));
Example: AdonisJS client verifying server certificate and presenting client cert for mTLS:
const fs = require('fs');
const https = require('https');
const agent = new https.Agent({
key: fs.readFileSync('client.key'),
cert: fs.readFileSync('client.crt'),
ca: fs.readFileSync('ca.crt'),
rejectUnauthorized: true
});
const options = {
hostname: 'api.example.com',
port: 443,
path: '/v1/secure',
method: 'POST',
agent,
headers: { 'Content-Type': 'application/json' }
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => console.log(data));
});
req.on('error', (e) => { console.error(e); });
req.write(JSON.stringify({ username: 'alice', action: 'read' }));
req.end();
These examples ensure that only requests with valid client certificates are processed and that incoming data conforms to a strict schema, mitigating risks like Spring4shell by eliminating ambiguous or unsafe binding paths.