Ldap Injection in Adonisjs with Api Keys
Ldap Injection in Adonisjs with Api Keys
Ldap Injection in an AdonisJS application that uses API keys for initial client authentication can create a compound vulnerability where an attacker who obtains or guesses a valid API key may still exploit unsafe LDAP query construction to bypass authorization, escalate privileges, or access sensitive directory data. AdonisJS does not provide built-in LDAP utilities; integrations typically rely on third‑party Node LDAP clients. If developer code builds LDAP filters by concatenating user‑controlled values (e.g., from request parameters or headers) without escaping or validation, the API key gate can be bypassed by malicious input that changes the semantics of the LDAP query.
Consider an endpoint that first validates an API key and then searches LDAP to find a user’s group memberships. A vulnerable implementation might look like this:
const { Client } = require('ldapjs');
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
async function handle(req, res) {
const { apiKey } = req.headers;
const { username } = req.params; // attacker-controlled
if (!isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'unauthorized' });
}
// Unsafe: direct concatenation into filter
const filter = `(uid=${username})`;
const opts = {
filter: filter,
scope: 'sub',
attributes: ['cn', 'memberOf']
};
client.bind('cn=app,dc=example,dc=com', 'appPassword', (err) => {
if (err) { return res.status(500).json({ error: 'ldap bind failed' }); }
client.search('dc=example,dc=com', opts, (err, search) => {
if (err) { return res.status(500).json({ error: 'ldap search failed' }); }
const entries = [];
search.on('searchEntry', (entry) => { entries.push(entry.object); });
search.on('error', (error) => { res.status(500).json({ error: 'ldap error' }); });
search.on('end', () => { res.json({ username, groups: entries }); });
});
});
}
An attacker can supply username values such as admin)(objectClass=*) to manipulate the filter into (uid=admin)(objectClass=*)), potentially returning all entries, or use encoded characters to bypass group-based access checks. Because the API key validated the caller, the app may incorrectly assume the LDAP search is safe for that identity, leading to information disclosure or unauthorized group-based logic. This pattern maps to the broader BOLA/IDOR and Input Validation checks in middleBrick’s 12 parallel security checks, where unsafe consumption of parameters combined with authorization flaws can expose directory data even when an authentication token like an API key is present.
Additionally, if the API key itself is leaked via logs, error messages, or insecure transport, and the LDAP filter is further manipulated, an attacker might pivot to SSRF or data exfiltration depending on server network configuration. The key takeaway is that API keys handle perimeter authentication but do not sanitize or authorize data-plane queries; developers must treat all input as untrusted and apply strict validation and escaping before using it in LDAP filters.
Api Keys-Specific Remediation in Adonisjs
Remediation focuses on never trusting user input for LDAP filter construction, even after API key validation. Use parameterized or library-supported filter building, strict allowlists for identifiers, and avoid dynamic filter assembly. Below are concrete, safe patterns for AdonisJS with an LDAP client.
1. Use an LDAP filter builder or strict escaping
Instead of concatenation, construct filters programmatically or escape special LDAP characters (*, (, ), \, NUL). Example with a simple escape helper:
function escapeLdapFilter(value) {
return String(value)
.replace(/\\/g, '\\\\')
.replace(/*/g, '\\2a')
.replace(/(/g, '\\28')
.replace(/)/g, '\\29')
.replace(/\x00/g, '\\00');
}
async function handleSafe(req, res) {
const { apiKey } = req.headers;
const { username } = req.params;
if (!isValidApiKey(apiKey)) {
return res.status(401).json({ error: 'unauthorized' });
}
const safeUsername = escapeLdapFilter(username);
const filter = `(uid=${safeUsername})`;
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
client.bind('cn=app,dc=example,dc=com', 'appPassword', (err) => {
if (err) { return res.status(500).json({ error: 'ldap bind failed' }); }
const opts = {
filter: filter,
scope: 'sub',
attributes: ['cn', 'memberOf']
};
client.search('dc=example,dc=com', opts, (err, search) => {
if (err) { return res.status(500).json({ error: 'ldap search failed' }); }
const entries = [];
search.on('searchEntry', (entry) => { entries.push(entry.object); });
search.on('error', () => { res.status(500).json({ error: 'ldap error' }); });
search.on('end', () => { res.json({ username, groups: entries }); });
});
});
}
2. Prefer directory searches by unique, validated identifiers
Bind with a service account and search using an indexed, normalized attribute that you control (e.g., a local user ID mapped to a directory UUID). Avoid searching by raw usernames supplied by the client:
async function handleByMappedId(req, res) {
const { apiKey } = req.headers;
const { userId } = req.params; // numeric, validated server-side
if (!isValidApiKey(apiKey) || !/
\d{1,10}/.test(userId)) {
return res.status(400).json({ error: 'invalid input' });
}
const client = ldap.createClient({ url: 'ldap://ldap.example.com' });
client.bind('cn=app,dc=example,dc=com', 'appPassword', (err) => {
if (err) { return res.status(500).json({ error: 'bind failed' }); }
// Search by a mapped attribute, not raw input
const filter = `(mappedUserId=${userId})`;
const opts = { filter, scope: 'sub', attributes: ['cn', 'memberOf'] };
client.search('dc=example,dc=com', opts, (err, search) => {
if (err) { return res.status(500).json({ error: 'search failed' }); }
const entries = [];
search.on('searchEntry', (entry) => { entries.push(entry.object); });
search.on('end', () => { res.json({ userId, groups: entries }); });
});
});
}
3. Centralize validation and use allowlists
Validate identifiers against a strict pattern (letters, digits, underscores) and reject anything that does not conform before touching LDAP. Combine API key validation with application-level authorization checks to ensure the authenticated principal can only query data they are permitted to see.
These remediation steps align with the input validation and authorization checks performed by middleBrick. Even when using API keys, insecure consumption of parameters remains a risk; the scanner’s findings will highlight such unsafe LDAP consumption patterns and map them to relevant frameworks like OWASP API Top 10.