[mod_auth] LDAP escape username in DN and filters

(replaces restriction on characters allowed in username)
personal/stbuehler/mod-csrf
Glenn Strauss 6 years ago
parent 8981ca0467
commit eda72ebfc7

@ -215,6 +215,137 @@ static void mod_authn_ldap_opt_err(server *srv, const char *file, unsigned long
mod_authn_ldap_err(srv, file, line, fn, err);
}
static void mod_authn_append_ldap_dn_escape(buffer * const filter, const buffer * const raw) {
/* [RFC4514] 2.4 Converting an AttributeValue from ASN.1 to a String
*
* https://www.ldap.com/ldap-dns-and-rdns
* http://social.technet.microsoft.com/wiki/contents/articles/5312.active-directory-characters-to-escape.aspx
*/
const char * const b = raw->ptr;
const size_t rlen = buffer_string_length(raw);
if (0 == rlen) return;
if (b[0] == ' ') { /* || b[0] == '#' handled below for MS Active Directory*/
/* escape leading ' ' */
buffer_append_string_len(filter, CONST_STR_LEN("\\"));
}
for (size_t i = 0; i < rlen; ++i) {
size_t len = i;
int bs = 0;
do {
/* encode all UTF-8 chars with high bit set
* (instead of validating UTF-8 and escaping only invalid UTF-8) */
if (((unsigned char *)b)[len] > 0x7f)
break;
switch (b[len]) {
default:
continue;
case '"': case '+': case ',': case ';': case '\\':
case '<': case '>':
case '=': case '#': /* (for MS Active Directory) */
bs = 1;
break;
case '\0':
break;
}
break;
} while (++len < rlen);
len -= i;
if (len) {
buffer_append_string_len(filter, b+i, len);
if ((i += len) == rlen) break;
}
if (bs) {
buffer_append_string_len(filter, CONST_STR_LEN("\\"));
buffer_append_string_len(filter, b+i, 1);
}
else {
/* escape NUL ('\0') (and all UTF-8 chars with high bit set) */
char *f;
buffer_string_prepare_append(filter, 3);
f = filter->ptr + buffer_string_length(filter);
f[0] = '\\';
f[1] = "0123456789abcdef"[(((unsigned char *)b)[i] >> 4) & 0xf];
f[2] = "0123456789abcdef"[(((unsigned char *)b)[i] ) & 0xf];
buffer_commit(filter, 3);
}
}
if (rlen > 1 && b[rlen-1] == ' ') {
/* escape trailing ' ' */
filter->ptr[buffer_string_length(filter)-1] = '\\';
buffer_append_string_len(filter, CONST_STR_LEN(" "));
}
}
static void mod_authn_append_ldap_filter_escape(buffer * const filter, const buffer * const raw) {
/* [RFC4515] 3. String Search Filter Definition
*
* [...]
*
* The <valueencoding> rule ensures that the entire filter string is a
* valid UTF-8 string and provides that the octets that represent the
* ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII
* 0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a
* backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits
* representing the value of the encoded octet.
*
* [...]
*
* As indicated by the <valueencoding> rule, implementations MUST escape
* all octets greater than 0x7F that are not part of a valid UTF-8
* encoding sequence when they generate a string representation of a
* search filter. Implementations SHOULD accept as input strings that
* are not valid UTF-8 strings. This is necessary because RFC 2254 did
* not clearly define the term "string representation" (and in
* particular did not mention that the string representation of an LDAP
* search filter is a string of UTF-8-encoded Unicode characters).
*
*
* https://www.ldap.com/ldap-filters
* Although not required, you may escape any other characters that you want
* in the assertion value (or substring component) of a filter. This may be
* accomplished by prefixing the hexadecimal representation of each byte of
* the UTF-8 encoding of the character to escape with a backslash character.
*/
const char * const b = raw->ptr;
const size_t rlen = buffer_string_length(raw);
for (size_t i = 0; i < rlen; ++i) {
size_t len = i;
char *f;
do {
/* encode all UTF-8 chars with high bit set
* (instead of validating UTF-8 and escaping only invalid UTF-8) */
if (((unsigned char *)b)[len] > 0x7f)
break;
switch (b[len]) {
default:
continue;
case '\0': case '(': case ')': case '*': case '\\':
break;
}
break;
} while (++len < rlen);
len -= i;
if (len) {
buffer_append_string_len(filter, b+i, len);
if ((i += len) == rlen) break;
}
/* escape * ( ) \ NUL ('\0') (and all UTF-8 chars with high bit set) */
buffer_string_prepare_append(filter, 3);
f = filter->ptr + buffer_string_length(filter);
f[0] = '\\';
f[1] = "0123456789abcdef"[(((unsigned char *)b)[i] >> 4) & 0xf];
f[2] = "0123456789abcdef"[(((unsigned char *)b)[i] ) & 0xf];
buffer_commit(filter, 3);
}
}
static LDAP * mod_authn_ldap_host_init(server *srv, plugin_config *s) {
LDAP *ld;
int ret;
@ -400,7 +531,7 @@ static handler_t mod_authn_ldap_memberOf(server *srv, plugin_config *s, const ht
CONST_STR_LEN("member"))) {
buffer_append_string(filter, userdn);
} else { /*(assume "memberUid"; consider validating in SETDEFAULTS_FUNC)*/
buffer_append_string_buffer(filter, username);
mod_authn_append_ldap_filter_escape(filter, username);
}
buffer_append_string_len(filter, CONST_STR_LEN(")"));
@ -433,33 +564,6 @@ static handler_t mod_authn_ldap_basic(server *srv, connection *con, void *p_d, c
if (pw[0] == '\0' && !p->conf.auth_ldap_allow_empty_pw)
return HANDLER_ERROR;
/* check username
*
* we have to protect againt username which modifies our filter in
* an unpleasant way
*/
for (size_t i = 0, len = buffer_string_length(username); i < len; i++) {
char c = username->ptr[i];
if (!isalpha(c) &&
!isdigit(c) &&
(c != ' ') &&
(c != '@') &&
(c != '-') &&
(c != '_') &&
(c != '.') ) {
log_error_write(srv, __FILE__, __LINE__, "sbd",
"ldap: invalid character (- _.@a-zA-Z0-9 allowed) "
"in username:", username, i);
con->http_status = 400; /* Bad Request */
con->mode = DIRECT;
return HANDLER_FINISHED;
}
}
template = p->conf.auth_ldap_filter;
if (buffer_string_is_empty(template)) {
return HANDLER_ERROR;
@ -470,14 +574,14 @@ static handler_t mod_authn_ldap_basic(server *srv, connection *con, void *p_d, c
if (*template->ptr == ',') {
/* special-case filter template beginning with ',' to be explicit DN */
buffer_append_string_len(p->ldap_filter, CONST_STR_LEN("uid="));
buffer_append_string_buffer(p->ldap_filter, username);
mod_authn_append_ldap_dn_escape(p->ldap_filter, username);
buffer_append_string_buffer(p->ldap_filter, template);
dn = p->ldap_filter->ptr;
} else {
for (char *b = template->ptr, *d; *b; b = d+1) {
if (NULL != (d = strchr(b, '$'))) {
buffer_append_string_len(p->ldap_filter, b, (size_t)(d - b));
buffer_append_string_buffer(p->ldap_filter, username);
mod_authn_append_ldap_filter_escape(p->ldap_filter, username);
} else {
d = template->ptr + buffer_string_length(template);
buffer_append_string_len(p->ldap_filter, b, (size_t)(d - b));

Loading…
Cancel
Save