Merge pull request #6781 from erik-krogh/ldap

JS: Move LDAP injection out of experimental
This commit is contained in:
Erik Krogh Kristensen
2021-11-02 13:35:32 +01:00
committed by GitHub
17 changed files with 251 additions and 353 deletions

View File

@@ -99,6 +99,7 @@ import semmle.javascript.frameworks.History
import semmle.javascript.frameworks.Immutable
import semmle.javascript.frameworks.Knex
import semmle.javascript.frameworks.LazyCache
import semmle.javascript.frameworks.LdapJS
import semmle.javascript.frameworks.LodashUnderscore
import semmle.javascript.frameworks.Logging
import semmle.javascript.frameworks.HttpFrameworks

View File

@@ -0,0 +1,71 @@
/**
* Provides classes for working with [LDAPjs](https://www.npmjs.com/package/ldapjs)
*/
import javascript
/**
* A module providing sinks and sanitizers for LDAP injection.
*/
module LdapJS {
/** Gets a reference to the ldapjs library. */
API::Node ldapjs() { result = API::moduleImport("ldapjs") }
/** Gets an LDAPjs client. */
private API::Node ldapClient() { result = ldapjs().getMember("createClient").getReturn() }
/** A call to a LDAPjs Client API method. */
class ClientCall extends API::CallNode {
string methodName;
ClientCall() {
methodName = ["add", "bind", "compare", "del", "modify", "modifyDN", "search"] and
this = ldapClient().getMember(methodName).getACall()
}
/** Gets the name of the LDAPjs Client API method. */
string getMethodName() { result = methodName }
}
/** A reference to a LDAPjs client `search` options. */
class SearchOptions extends API::Node {
ClientCall call;
SearchOptions() { call.getMethodName() = "search" and this = call.getParameter(1) }
}
/** A creation of an LDAPjs filter, or object containing a filter, that doesn't sanitizes the input. */
abstract class TaintPreservingLdapFilterStep extends DataFlow::Node {
/** The input that creates (part of) an LDAPjs filter. */
abstract DataFlow::Node getInput();
/** The resulting LDAPjs filter. */
abstract DataFlow::Node getOutput();
}
/** A call to the LDAPjs utility method "parseFilter". */
private class ParseFilter extends TaintPreservingLdapFilterStep, API::CallNode {
ParseFilter() { this = ldapjs().getMember("parseFilter").getACall() }
override DataFlow::Node getInput() { result = this.getArgument(0) }
override DataFlow::Node getOutput() { result = this }
}
/**
* A filter used in call to "search" on an LDAPjs client.
* We model that as a step from the ".filter" write to the options object itself.
*/
private class SearchFilter extends TaintPreservingLdapFilterStep {
SearchOptions options;
SearchFilter() {
options = ldapClient().getMember("search").getACall().getParameter(1) and
this = options.getARhs()
}
override DataFlow::Node getInput() { result = options.getMember("filter").getARhs() }
override DataFlow::Node getOutput() { result = this }
}
}

View File

@@ -41,4 +41,35 @@ module SqlInjection {
class GraphqlInjectionSink extends Sink {
GraphqlInjectionSink() { this instanceof GraphQL::GraphQLString }
}
/**
* An LDAPjs sink.
*/
class LdapJSSink extends Sink {
LdapJSSink() {
// A distinguished name (DN) used in a call to the client API.
this = any(LdapJS::ClientCall call).getArgument(0)
or
// A search options object, which contains a filter and a baseDN.
this = any(LdapJS::SearchOptions opt).getARhs()
or
// A call to "parseDN", which parses a DN from a string.
this = LdapJS::ldapjs().getMember("parseDN").getACall().getArgument(0)
}
}
import semmle.javascript.security.IncompleteBlacklistSanitizer as IncompleteBlacklistSanitizer
/**
* A chain of replace calls that replaces all unsafe chars for ldap injection.
* For simplicity it's used as a sanitizer for all of `js/sql-injection`.
*/
class LdapStringSanitizer extends Sanitizer,
IncompleteBlacklistSanitizer::StringReplaceCallSequence {
LdapStringSanitizer() {
forall(string char | char = ["*", "(", ")", "\\", "/"] |
this.getAMember().getAReplacedString() = char
)
}
}
}

View File

@@ -24,4 +24,11 @@ class Configuration extends TaintTracking::Configuration {
super.isSanitizer(node) or
node instanceof Sanitizer
}
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(LdapJS::TaintPreservingLdapFilterStep filter |
pred = filter.getInput() and
succ = filter.getOutput()
)
}
}

View File

@@ -9,6 +9,8 @@
* @id js/sql-injection
* @tags security
* external/cwe/cwe-089
* external/cwe/cwe-090
* external/cwe/cwe-943
*/
import javascript

View File

@@ -1,50 +0,0 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>If an LDAP query is built using string concatenation or string formatting, and the
components of the concatenation include user input without any proper sanitization, a user
is likely to be able to run malicious LDAP queries.</p>
</overview>
<recommendation>
<p>If user input must be included in an LDAP query, it should be escaped to
avoid a malicious user providing special characters that change the meaning
of the query. In NodeJS, it is possible to build the LDAP query using frameworks like <code>ldapjs</code>.
The library provides a <code>Filter API</code>, however it's still possibile to pass a string version of an LDAP filter.
A good practice is to escape filter characters that could change the meaning of the query (https://tools.ietf.org/search/rfc4515#section-3).</p>
</recommendation>
<example>
<p>In the following examples, the code accepts a <code>username</code> from the user, which it uses in a LDAP query.</p>
<p>The first and the second example uses the unsanitized user input directly
in the search filter for the LDAP query.
A malicious user could provide special characters to change the meaning of these
queries, and search for a completely different set of values.
</p>
<sample src="examples/example_bad1.js" />
<sample src="examples/example_bad2.js" />
<p>In the third example the <code>username</code> is sanitized before it is included in the search filters.
This ensures the meaning of the query cannot be changed by a malicious user.</p>
<sample src="examples/example_good1.js" />
<p>In the fourth example the <code>username</code> is passed to an <code>OrFilter</code> filter before it is included in the search filters.
This ensures the meaning of the query cannot be changed by a malicious user.</p>
<sample src="examples/example_good2.js" />
</example>
<references>
<li>OWASP: <a href="https://cheatsheetseries.owasp.org/cheatsheets/LDAP_Injection_Prevention_Cheat_Sheet.html">LDAP Injection Prevention Cheat Sheet</a>.</li>
<li>LDAPjs: <a href="http://ldapjs.org/index.html">Documentation for LDAPjs</a>.</li>
<li>Github: <a href="https://github.com/ldapjs/node-ldapjs">ldapjs</a>.</li>
<li>Wikipedia: <a href="https://en.wikipedia.org/wiki/LDAP_injection">LDAP injection</a>.</li>
<li>BlackHat: <a href="https://www.blackhat.com/presentations/bh-europe-08/Alonso-Parada/Whitepaper/bh-eu-08-alonso-parada-WP.pdf">LDAP Injection and Blind LDAP Injection</a>.</li>
<li>LDAP: <a href="https://ldap.com/2018/05/04/understanding-and-defending-against-ldap-injection-attacks/">Understanding and Defending Against LDAP Injection Attacks</a>.</li>
</references>
</qhelp>

View File

@@ -1,20 +0,0 @@
/**
* @name LDAP query built from user-controlled sources
* @description Building an LDAP query from user-controlled sources is vulnerable to insertion of
* malicious LDAP code by the user.
* @kind path-problem
* @problem.severity error
* @precision high
* @id javascript/ldap-injection
* @tags security
* external/cwe/cwe-090
*/
import javascript
import DataFlow::PathGraph
import LdapInjection::LdapInjection
from LdapInjectionConfiguration config, DataFlow::PathNode source, DataFlow::PathNode sink
where config.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "$@ might include code from $@.",
sink.getNode().(Sink).getQueryCall(), "LDAP query call", source.getNode(), "user-provided value"

View File

@@ -1,25 +0,0 @@
import javascript
module LdapInjection {
import LdapInjectionCustomizations::LdapInjection
/**
* A taint-tracking configuration for reasoning about LDAP injection vulnerabilities.
*/
class LdapInjectionConfiguration extends TaintTracking::Configuration {
LdapInjectionConfiguration() { this = "LdapInjection" }
override predicate isSource(DataFlow::Node source) { source instanceof Source }
override predicate isSink(DataFlow::Node sink) { sink instanceof Sink }
override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer }
override predicate isAdditionalTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(LdapjsParseFilter filter |
pred = filter.getArgument(0) and
succ = filter
)
}
}
}

View File

@@ -1,69 +0,0 @@
/**
* Provides default sources, sinks and sanitizers for reasoning about
* LDAP injection vulnerabilities, as well as extension points for
* adding your own.
*/
import javascript
module LdapInjection {
import Ldapjs::Ldapjs
/**
* A data flow source for LDAP injection vulnerabilities.
*/
abstract class Source extends DataFlow::Node { }
/**
* A data flow sink for LDAP injection vulnerabilities.
*/
abstract class Sink extends DataFlow::Node {
/**
* Gets the LDAP query call that the sink flows into.
*/
abstract DataFlow::Node getQueryCall();
}
/**
* A sanitizer for LDAP injection vulnerabilities.
*/
abstract class Sanitizer extends DataFlow::Node { }
/**
* A source of remote user input, considered as a flow source for LDAP injection.
*/
class RemoteSource extends Source {
RemoteSource() { this instanceof RemoteFlowSource }
}
/**
* An LDAP filter for an API call that executes an operation against the LDAP server.
*/
class LdapjsSearchFilterAsSink extends Sink instanceof LdapjsSearchFilter {
override DataFlow::InvokeNode getQueryCall() {
result = LdapjsSearchFilter.super.getQueryCall()
}
}
/**
* An LDAP DN argument for an API call that executes an operation against the LDAP server.
*/
class LdapjsDNArgumentAsSink extends Sink instanceof LdapjsDNArgument {
override DataFlow::InvokeNode getQueryCall() { result = LdapjsDNArgument.super.getQueryCall() }
}
/**
* A call to a function whose name suggests that it escapes LDAP search query parameter.
*/
class FilterOrDNSanitizationCall extends Sanitizer, DataFlow::CallNode {
FilterOrDNSanitizationCall() {
exists(string sanitize, string input |
sanitize = "(?:escape|saniti[sz]e|validate|filter)" and
input = "[Ii]nput?"
|
this.getCalleeName()
.regexpMatch("(?i)(" + sanitize + input + ")" + "|(" + input + sanitize + ")")
)
}
}
}

View File

@@ -1,92 +0,0 @@
/**
* Provides classes for working with [ldapjs](https://github.com/ldapjs/node-ldapjs) (Client only)
*/
import javascript
module Ldapjs {
/**
* Gets a method name on an LDAPjs client that accepts a DN as the first argument.
*/
private string getLdapjsClientDNMethodName() {
result = ["add", "bind", "compare", "del", "modify", "modifyDN", "search"]
}
/**
* Gets a data flow source node for an LDAP client.
*/
abstract class LdapClient extends DataFlow::SourceNode { }
/**
* Gets a data flow source node for the ldapjs library.
*/
private DataFlow::SourceNode ldapjs() { result = DataFlow::moduleImport("ldapjs") }
/**
* Gets a data flow source node for the ldapjs client.
*/
class LdapjsClient extends LdapClient {
LdapjsClient() { this = ldapjs().getAMemberCall("createClient") }
}
/**
* Gets a data flow node for the client `search` options.
*/
class LdapjsSearchOptions extends DataFlow::SourceNode {
DataFlow::CallNode queryCall;
LdapjsSearchOptions() {
queryCall = any(LdapjsClient client).getAMemberCall("search") and
this = queryCall.getArgument(1).getALocalSource()
}
/**
* Gets the LDAP query call that these options are used in.
*/
DataFlow::InvokeNode getQueryCall() { result = queryCall }
}
/**
* A filter used in a `search` operation against the LDAP server.
*/
class LdapjsSearchFilter extends DataFlow::Node {
LdapjsSearchOptions options;
LdapjsSearchFilter() { this = options.getAPropertyWrite("filter").getRhs() }
/**
* Gets the LDAP query call that this filter is used in.
*/
DataFlow::InvokeNode getQueryCall() { result = options.getQueryCall() }
}
/**
* A call to the ldapjs Client API methods.
*/
class LdapjsClientAPICall extends DataFlow::CallNode {
LdapjsClientAPICall() {
this = any(LdapjsClient client).getAMemberCall(getLdapjsClientDNMethodName())
}
}
/**
* A distinguished name (DN) used in a Client API call against the LDAP server.
*/
class LdapjsDNArgument extends DataFlow::Node {
LdapjsClientAPICall queryCall;
LdapjsDNArgument() { this = queryCall.getArgument(0) }
/**
* Gets the LDAP query call that this DN is used in.
*/
DataFlow::InvokeNode getQueryCall() { result = queryCall }
}
/**
* Ldapjs parseFilter method call.
*/
class LdapjsParseFilter extends DataFlow::CallNode {
LdapjsParseFilter() { this = ldapjs().getAMemberCall("parseFilter") }
}
}

View File

@@ -1,21 +0,0 @@
const http = require('http');
const url = require('url');
const ldap = require('ldapjs');
const client = ldap.createClient({
url: 'ldap://127.0.0.1:1389'
});
const server = http.createServer((req, res) => {
let q = url.parse(req.url, true);
let username = q.query.username;
var opts = {
// BAD
filter: `(|(name=${username})(username=${username}))`
};
client.search('o=example', opts, function (err, res) {
});
});

View File

@@ -1,16 +0,0 @@
const http = require('http');
const url = require('url');
const ldap = require('ldapjs');
const client = ldap.createClient({
url: 'ldap://127.0.0.1:1389'
});
const server = http.createServer((req, res) => {
let q = url.parse(req.url, true);
let username = q.query.username;
// BAD
client.search('o=example', { filter: `(|(name=${username})(username=${username}))` }, function (err, res) {
});
});

View File

@@ -1,31 +0,0 @@
const http = require('http');
const url = require('url');
const ldap = require('ldapjs');
const client = ldap.createClient({
url: 'ldap://127.0.0.1:1389'
});
// https://github.com/vesse/node-ldapauth-fork/commit/3feea43e243698bcaeffa904a7324f4d96df60e4
const sanitizeInput = function (input) {
return input
.replace(/\*/g, '\\2a')
.replace(/\(/g, '\\28')
.replace(/\)/g, '\\29')
.replace(/\\/g, '\\5c')
.replace(/\0/g, '\\00')
.replace(/\//g, '\\2f');
};
const server = http.createServer((req, res) => {
let q = url.parse(req.url, true);
let username = q.query.username;
// GOOD
username = sanitizeInput(username);
client.search('o=example', { filter: `(|(name=${username})(username=${username}))` }, function (err, res) {
});
});

View File

@@ -1,29 +0,0 @@
const http = require('http');
const url = require('url');
const ldap = require('ldapjs');
const client = ldap.createClient({
url: 'ldap://127.0.0.1:1389'
});
const server = http.createServer((req, res) => {
let q = url.parse(req.url, true);
let username = q.query.username;
// GOOD (https://github.com/ldapjs/node-ldapjs/issues/181)
let f = new OrFilter({
filters: [
new EqualityFilter({
attribute: 'name',
value: username
}),
new EqualityFilter({
attribute: 'username',
value: username
})
]
});
client.search('o=example', { filter: f }, function (err, res) {
});
});

View File

@@ -68,6 +68,35 @@ nodes
| json-schema-validator.js:59:22:59:26 | query |
| json-schema-validator.js:61:22:61:26 | query |
| json-schema-validator.js:61:22:61:26 | query |
| ldap.js:20:7:20:34 | q |
| ldap.js:20:11:20:34 | url.par ... , true) |
| ldap.js:20:21:20:27 | req.url |
| ldap.js:20:21:20:27 | req.url |
| ldap.js:22:7:22:33 | username |
| ldap.js:22:18:22:18 | q |
| ldap.js:22:18:22:24 | q.query |
| ldap.js:22:18:22:33 | q.query.username |
| ldap.js:25:13:25:57 | `(\|(nam ... ame}))` |
| ldap.js:25:24:25:31 | username |
| ldap.js:25:46:25:53 | username |
| ldap.js:28:30:28:34 | opts1 |
| ldap.js:28:30:28:34 | opts1 |
| ldap.js:32:5:32:61 | { filte ... e}))` } |
| ldap.js:32:5:32:61 | { filte ... e}))` } |
| ldap.js:32:15:32:59 | `(\|(nam ... ame}))` |
| ldap.js:32:26:32:33 | username |
| ldap.js:32:48:32:55 | username |
| ldap.js:63:9:65:3 | parsedFilter |
| ldap.js:63:24:65:3 | ldap.pa ... ))`\\n ) |
| ldap.js:64:5:64:49 | `(\|(nam ... ame}))` |
| ldap.js:64:16:64:23 | username |
| ldap.js:64:38:64:45 | username |
| ldap.js:66:30:66:53 | { filte ... ilter } |
| ldap.js:66:30:66:53 | { filte ... ilter } |
| ldap.js:66:40:66:51 | parsedFilter |
| ldap.js:68:27:68:42 | `cn=${username}` |
| ldap.js:68:27:68:42 | `cn=${username}` |
| ldap.js:68:33:68:40 | username |
| marsdb-flow-to.js:10:9:10:18 | query |
| marsdb-flow-to.js:10:17:10:18 | {} |
| marsdb-flow-to.js:11:17:11:24 | req.body |
@@ -444,6 +473,37 @@ edges
| json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) | json-schema-validator.js:50:15:50:48 | query |
| json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
| json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:50:23:50:48 | JSON.pa ... y.data) |
| ldap.js:20:7:20:34 | q | ldap.js:22:18:22:18 | q |
| ldap.js:20:11:20:34 | url.par ... , true) | ldap.js:20:7:20:34 | q |
| ldap.js:20:21:20:27 | req.url | ldap.js:20:11:20:34 | url.par ... , true) |
| ldap.js:20:21:20:27 | req.url | ldap.js:20:11:20:34 | url.par ... , true) |
| ldap.js:22:7:22:33 | username | ldap.js:25:24:25:31 | username |
| ldap.js:22:7:22:33 | username | ldap.js:25:46:25:53 | username |
| ldap.js:22:7:22:33 | username | ldap.js:32:26:32:33 | username |
| ldap.js:22:7:22:33 | username | ldap.js:32:48:32:55 | username |
| ldap.js:22:7:22:33 | username | ldap.js:64:16:64:23 | username |
| ldap.js:22:7:22:33 | username | ldap.js:64:38:64:45 | username |
| ldap.js:22:7:22:33 | username | ldap.js:68:33:68:40 | username |
| ldap.js:22:18:22:18 | q | ldap.js:22:18:22:24 | q.query |
| ldap.js:22:18:22:24 | q.query | ldap.js:22:18:22:33 | q.query.username |
| ldap.js:22:18:22:33 | q.query.username | ldap.js:22:7:22:33 | username |
| ldap.js:25:13:25:57 | `(\|(nam ... ame}))` | ldap.js:28:30:28:34 | opts1 |
| ldap.js:25:13:25:57 | `(\|(nam ... ame}))` | ldap.js:28:30:28:34 | opts1 |
| ldap.js:25:24:25:31 | username | ldap.js:25:13:25:57 | `(\|(nam ... ame}))` |
| ldap.js:25:46:25:53 | username | ldap.js:25:13:25:57 | `(\|(nam ... ame}))` |
| ldap.js:32:15:32:59 | `(\|(nam ... ame}))` | ldap.js:32:5:32:61 | { filte ... e}))` } |
| ldap.js:32:15:32:59 | `(\|(nam ... ame}))` | ldap.js:32:5:32:61 | { filte ... e}))` } |
| ldap.js:32:26:32:33 | username | ldap.js:32:15:32:59 | `(\|(nam ... ame}))` |
| ldap.js:32:48:32:55 | username | ldap.js:32:15:32:59 | `(\|(nam ... ame}))` |
| ldap.js:63:9:65:3 | parsedFilter | ldap.js:66:40:66:51 | parsedFilter |
| ldap.js:63:24:65:3 | ldap.pa ... ))`\\n ) | ldap.js:63:9:65:3 | parsedFilter |
| ldap.js:64:5:64:49 | `(\|(nam ... ame}))` | ldap.js:63:24:65:3 | ldap.pa ... ))`\\n ) |
| ldap.js:64:16:64:23 | username | ldap.js:64:5:64:49 | `(\|(nam ... ame}))` |
| ldap.js:64:38:64:45 | username | ldap.js:64:5:64:49 | `(\|(nam ... ame}))` |
| ldap.js:66:40:66:51 | parsedFilter | ldap.js:66:30:66:53 | { filte ... ilter } |
| ldap.js:66:40:66:51 | parsedFilter | ldap.js:66:30:66:53 | { filte ... ilter } |
| ldap.js:68:33:68:40 | username | ldap.js:68:27:68:42 | `cn=${username}` |
| ldap.js:68:33:68:40 | username | ldap.js:68:27:68:42 | `cn=${username}` |
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
| marsdb-flow-to.js:10:9:10:18 | query | marsdb-flow-to.js:14:17:14:21 | query |
| marsdb-flow-to.js:10:17:10:18 | {} | marsdb-flow-to.js:10:9:10:18 | query |
@@ -852,6 +912,10 @@ edges
| json-schema-validator.js:55:22:55:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:55:22:55:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
| json-schema-validator.js:59:22:59:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:59:22:59:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
| json-schema-validator.js:61:22:61:26 | query | json-schema-validator.js:50:34:50:47 | req.query.data | json-schema-validator.js:61:22:61:26 | query | This query depends on $@. | json-schema-validator.js:50:34:50:47 | req.query.data | a user-provided value |
| ldap.js:28:30:28:34 | opts1 | ldap.js:20:21:20:27 | req.url | ldap.js:28:30:28:34 | opts1 | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
| ldap.js:32:5:32:61 | { filte ... e}))` } | ldap.js:20:21:20:27 | req.url | ldap.js:32:5:32:61 | { filte ... e}))` } | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
| ldap.js:66:30:66:53 | { filte ... ilter } | ldap.js:20:21:20:27 | req.url | ldap.js:66:30:66:53 | { filte ... ilter } | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
| ldap.js:68:27:68:42 | `cn=${username}` | ldap.js:20:21:20:27 | req.url | ldap.js:68:27:68:42 | `cn=${username}` | This query depends on $@. | ldap.js:20:21:20:27 | req.url | a user-provided value |
| marsdb-flow-to.js:14:17:14:21 | query | marsdb-flow-to.js:11:17:11:24 | req.body | marsdb-flow-to.js:14:17:14:21 | query | This query depends on $@. | marsdb-flow-to.js:11:17:11:24 | req.body | a user-provided value |
| marsdb.js:16:12:16:16 | query | marsdb.js:13:17:13:24 | req.body | marsdb.js:16:12:16:16 | query | This query depends on $@. | marsdb.js:13:17:13:24 | req.body | a user-provided value |
| minimongo.js:18:12:18:16 | query | minimongo.js:15:17:15:24 | req.body | minimongo.js:18:12:18:16 | query | This query depends on $@. | minimongo.js:15:17:15:24 | req.body | a user-provided value |

View File

@@ -0,0 +1,71 @@
const http = require("http");
const url = require("url");
const ldap = require("ldapjs");
const client = ldap.createClient({
url: "ldap://127.0.0.1:1389",
});
// https://github.com/vesse/node-ldapauth-fork/commit/3feea43e243698bcaeffa904a7324f4d96df60e4
const sanitizeInput = function (input) {
return input
.replace(/\*/g, "\\2a")
.replace(/\(/g, "\\28")
.replace(/\)/g, "\\29")
.replace(/\\/g, "\\5c")
.replace(/\0/g, "\\00")
.replace(/\//g, "\\2f");
};
const server = http.createServer((req, res) => {
let q = url.parse(req.url, true);
let username = q.query.username;
var opts1 = {
filter: `(|(name=${username})(username=${username}))`,
};
client.search("o=example", opts1, function (err, res) {}); // NOT OK
client.search(
"o=example",
{ filter: `(|(name=${username})(username=${username}))` }, // NOT OK
function (err, res) {}
);
// GOOD
client.search(
"o=example",
{ // OK
filter: `(|(name=${sanitizeInput(username)})(username=${sanitizeInput(
username
)}))`,
},
function (err, res) {}
);
// GOOD (https://github.com/ldapjs/node-ldapjs/issues/181)
let f = new OrFilter({
filters: [
new EqualityFilter({
attribute: "name",
value: username,
}),
new EqualityFilter({
attribute: "username",
value: username,
}),
],
});
client.search("o=example", { filter: f }, function (err, res) {});
const parsedFilter = ldap.parseFilter(
`(|(name=${username})(username=${username}))`
);
client.search("o=example", { filter: parsedFilter }, function (err, res) {}); // NOT OK
const dn = ldap.parseDN(`cn=${username}`, function (err, dn) {}); // NOT OK
});
server.listen(389, () => {});