From d60ac723a521a571f367c43e16c8cd97e4bf3c94 Mon Sep 17 00:00:00 2001 From: Vladimir Dubrovin <3proxy@3proxy.ru> Date: Mon, 20 Jul 2026 19:52:10 +0300 Subject: [PATCH] Multiple fixes, including security-related ones (in smtpp and LDAP plugin) --- src/auth.c | 26 ++++++-- src/pcre.c | 32 ++++++++-- src/plugins/LdapPlugin/ldapauth.c | 77 ++++++++++++++++++++--- src/plugins/StringsPlugin/StringsPlugin.c | 8 ++- src/proxy.c | 27 ++++++-- src/smtpp.c | 3 +- src/tlspr.c | 2 +- 7 files changed, 150 insertions(+), 25 deletions(-) diff --git a/src/auth.c b/src/auth.c index 628503e..72ae332 100644 --- a/src/auth.c +++ b/src/auth.c @@ -216,6 +216,24 @@ int dnsauth(struct clientparam * param){ return param->username? 0:3; } +static int ctmemcmp(const void *a, const void *b, size_t len){ + const unsigned char *pa = (const unsigned char *)a, *pb = (const unsigned char *)b; + unsigned char diff = 0; + size_t i; + for(i = 0; i < len; i++) diff |= (unsigned char)(pa[i] ^ pb[i]); + return diff; +} + +static int ctstrcmp(const char *a, const char *b, size_t maxlen){ + unsigned char diff = 0; + size_t i; + for(i = 0; i < maxlen; i++){ + diff |= (unsigned char)((unsigned char)a[i] ^ (unsigned char)b[i]); + if(!a[i] && !b[i]) break; + } + return diff; +} + int strongauth(struct clientparam * param){ static char dummy; unsigned char buf[256]; @@ -229,7 +247,7 @@ int strongauth(struct clientparam * param){ int pwlen = strlen((char *)param->password); if(pwlen > 255) pwlen = 255; if((unsigned)pwlen < pwl_table.recsize) { - if(!strncmp(pass + 1, (char *)param->password, pwl_table.recsize - 1)) return 0; + if(strlen(pass + 1) == strlen((char *)param->password) && !ctmemcmp(pass + 1, param->password, pwl_table.recsize - 1)) return 0; } else { mdh_ctx *bctx; unsigned hashsz; @@ -242,18 +260,18 @@ int strongauth(struct clientparam * param){ blen = hashsz; mdh_final(bctx, buf, &blen); mdh_free(bctx); - if(!memcmp(pass + 1, buf, pwl_table.recsize - 1)) return 0; + if(!ctmemcmp(pass + 1, buf, pwl_table.recsize - 1)) return 0; } return 6; } case CR: if (mycrypt(param->password, (unsigned char *)pass + 1, buf) && - !strcmp(pass + 1, (char *)buf)) + !ctstrcmp(pass + 1, (char *)buf, sizeof(pass) - 1)) return 0; else return 7; #ifdef WITH_SSL case NT: - if(ntpwdhash(buf, param->password, 1) && !strcmp(pass + 1, (char *)buf)) return 0; + if(ntpwdhash(buf, param->password, 1) && !ctstrcmp(pass + 1, (char *)buf, sizeof(pass) - 1)) return 0; else return 8; #endif default: diff --git a/src/pcre.c b/src/pcre.c index 05e9ea4..164817e 100644 --- a/src/pcre.c +++ b/src/pcre.c @@ -214,6 +214,7 @@ struct pcre_filter_data { int users; pcre2_code * re; pcre2_match_data * match_data; + pcre2_match_context * match_context; int action; char * replace; struct ace *acl; @@ -223,6 +224,7 @@ static void pcre_data_free(struct pcre_filter_data *pcrefd){ _3proxy_mutex_lock(&pcre_mutex); pcrefd->users--; if(!pcrefd->users){ + if(pcrefd->match_context) pcre2_match_context_free(pcrefd->match_context); if(pcrefd->match_data) pcre2_match_data_free(pcrefd->match_data); if(pcrefd->re) pcre2_code_free(pcrefd->re); if(pcrefd->acl) pl->freeacl(pcrefd->acl); @@ -283,10 +285,12 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns if(!match) return CONTINUE; if(!pcrefd->re) return pcrefd->action; for(; offset < *length_p; nreplaces++){ + int repsz; - count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcrefd->match_data, NULL); + count = pcre2_match(pcrefd->re, (PCRE2_SPTR)*buf_p, *length_p, offset, 0, pcrefd->match_data, pcrefd->match_context); if(count <= 0) break; ovector = pcre2_get_ovector_pointer(pcrefd->match_data); + if(ovector[0] > (PCRE2_SIZE)*length_p || ovector[1] > (PCRE2_SIZE)*length_p || ovector[1] < ovector[0]) break; if(!(replace = pcrefd->replace) || param->nooverwritefilter) return pcrefd->action; replen = *length_p - ovector[1]; @@ -300,6 +304,8 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns num = atoi(replace); while(isnumber(*replace)) replace++; if(num > (count - 1)) continue; + if(ovector[(num<<1)] == PCRE2_UNSET) continue; + if(ovector[(num<<1) + 1] > (PCRE2_SIZE)*length_p || ovector[(num<<1)] > ovector[(num<<1) + 1]) continue; replen += (ovector[(num<<1) + 1] - ovector[(num<<1)]); } else { @@ -319,6 +325,8 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns replace ++; num = atoi(replace); if(num > (count - 1)) continue; + if(ovector[(num<<1)] == PCRE2_UNSET) continue; + if(ovector[(num<<1) + 1] > (PCRE2_SIZE)*length_p || ovector[(num<<1)] > ovector[(num<<1) + 1]) continue; memcpy(target, *buf_p + ovector[(num<<1)], ovector[(num<<1) + 1] - ovector[(num<<1)]); target += (ovector[(num<<1) + 1] - ovector[(num<<1)]); while(isnumber(*replace)) replace++; @@ -327,6 +335,7 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns *target++ = *replace++; } } + repsz = (int)(target - tmpbuf); memcpy(target, *buf_p + ovector[1], *length_p - ovector[1]); if((ovector[0] + replen + 1) > *bufsize_p){ newbuf = pl->mallocfunc(ovector[0] + replen + 1); @@ -343,10 +352,11 @@ static FILTER_ACTION pcre_filter_buffer(void *fc, struct clientparam *param, uns pl->freefunc(tmpbuf); (*buf_p)[ovector[0] + replen] = 0; *length_p = ovector[0] + replen; - if(ovector[0] + replen <= offset){ - break; + offset = ovector[0] + repsz; + if(ovector[1] == ovector[0] && offset <= (int)ovector[0]){ + /* zero-width match with empty replacement: force progress */ + offset = ovector[1] + 1; } - offset = ovector[0] + (int)strlen(pcrefd->replace); } return nreplaces? pcrefd->action : CONTINUE; #undef pcrefd @@ -442,6 +452,13 @@ static int h_pcre(int argc, unsigned char **argv){ flt->acl = acl; flt->replace = replace; flt->users = 1; + if(re){ + flt->match_context = pcre2_match_context_create(NULL); + if(flt->match_context){ + pcre2_set_match_limit(flt->match_context, 1000000); + pcre2_set_depth_limit(flt->match_context, 10000); + } + } newf->instance = "pcre"; newf->data = flt; newf->filter_open = pcre_filter_open; @@ -549,6 +566,13 @@ static int h_pcre_rewrite(int argc, unsigned char **argv){ flt->acl = acl; flt->replace = replace; flt->users = 1; + if(re){ + flt->match_context = pcre2_match_context_create(NULL); + if(flt->match_context){ + pcre2_set_match_limit(flt->match_context, 1000000); + pcre2_set_depth_limit(flt->match_context, 10000); + } + } newf->instance = "pcre"; newf->data = flt; newf->filter_open = pcre_filter_open; diff --git a/src/plugins/LdapPlugin/ldapauth.c b/src/plugins/LdapPlugin/ldapauth.c index 2d98bd0..c9ee40e 100644 --- a/src/plugins/LdapPlugin/ldapauth.c +++ b/src/plugins/LdapPlugin/ldapauth.c @@ -42,7 +42,7 @@ struct schedule myschedule; void lower (char *string) { int length, i; - + length = strlen(string); for (i=0; i' || c == ';' || c == '=' || c < 0x20 || c == 0x7f){ + if(j + 3 >= dstlen) return NULL; + sprintf(dst + j, "\\%02X", c); + j += 3; + } + else { + if(j + 1 >= dstlen) return NULL; + dst[j++] = (char)c; + } + } + dst[j] = 0; + return dst; +} + +/* RFC 4515 escaping for search filter values. Returns dst, NULL on overflow. */ +static char * ldap_escape_filter(const char *src, char *dst, int dstlen) +{ + int j = 0; + unsigned char c; + for (; *src; src++){ + c = (unsigned char)*src; + if(c == '*' || c == '(' || c == ')' || c == '\\' || c == 0 || c < 0x20 || c >= 0x7f){ + if(j + 3 >= dstlen) return NULL; + sprintf(dst + j, "\\%02X", c); + j += 3; + } + else { + if(j + 1 >= dstlen) return NULL; + dst[j++] = (char)c; + } + } + dst[j] = 0; + return dst; +} + +/* reject names unsafe for use in filesystem paths */ +static int unsafe_filename(const char *name) +{ + return (strchr(name, '/') || strchr(name, '\\') || strstr(name, "..") || !*name); +} + /* -------------------------------------------------------------------------- */ -int savecounters(void) +int savecounters(void *v) { struct trafcount *tc=mypluginlink->conf->trafcounter; struct trafcount *tcd; @@ -67,8 +115,8 @@ int savecounters(void) tcd = tc; tc = tc->next; f=NULL; - if(strcmp(tcd->comment,"ldapcounters")==0) { - tmpbuf=malloc(strlen(pat_file)+strlen(ldap_dircount)+strlen(tcd->ace->users->user)); + if(strcmp(tcd->comment,"ldapcounters")==0 && !unsafe_filename(tcd->ace->users->user)) { + tmpbuf=malloc(strlen(ldap_dircount)+strlen(tcd->ace->users->user)+sizeof(".lc")); sprintf(tmpbuf,pat_file,ldap_dircount,tcd->ace->users->user); f=fopen(tmpbuf,"w+b"); fseek(f,0,SEEK_SET); @@ -97,9 +145,10 @@ static int ldapfunc(struct clientparam *param) { LDAP *ld = NULL; - LDAPMessage *res = NULL; + LDAPMessage *res = NULL; int rc = -1; char tmpbuf[1024]; + char escuser[512]; /* test proxy user auth ------------------------*/ if(!param->username || !param->password) return 4; @@ -126,7 +175,12 @@ static int ldapfunc(struct clientparam *param) /* create user for test auth */ - sprintf(tmpbuf,"%.200s=%.200s,%.200s",attrs[0],param->username,ldap_userenv); + if(!ldap_escape_dn(param->username, escuser, sizeof(escuser))) + { + ldap_unbind_s(ld); + return 5; + } + snprintf(tmpbuf, sizeof(tmpbuf), "%.200s=%.510s,%.200s",attrs[0],escuser,ldap_userenv); rc = ldap_bind_s( ld, tmpbuf, param->password, LDAP_AUTH_SIMPLE ); @@ -162,7 +216,12 @@ static int ldapfunc(struct clientparam *param) /* test enter user in filter ------------------------------ create filter for search*/ - sprintf(tmpbuf,"(&(%.200s=%.200s)(%.200s=%.200s))",attrs[0],param->username, + if(!ldap_escape_filter(param->username, escuser, sizeof(escuser))) + { + ldap_unbind_s(ld); + return 5; + } + snprintf(tmpbuf, sizeof(tmpbuf), "(&(%.200s=%.510s)(%.200s=%.200s))",attrs[0],escuser, ldap_group_attr,ldap_access); @@ -326,7 +385,7 @@ int h_trafgroup(int argc, unsigned char ** argv) while (rc > 0) { vals=ldap_get_values(ld, msg, getattr); - if (vals != NULL && vals[0] != NULL ) + if (vals != NULL && vals[0] != NULL && !unsafe_filename(vals[0]) ) { /* -------------bandlim---------- @@ -378,7 +437,7 @@ int h_trafgroup(int argc, unsigned char ** argv) newtrafcount->traflim64 = traflimit; newtrafcount->comment=(*mypluginlink->strdupfunc)("ldapcounters"); newtrafcount->number=0; - tmpbuf=malloc(strlen(pat_file)+strlen(ldap_dircount)+strlen(vals[0])); + tmpbuf=malloc(strlen(ldap_dircount)+strlen(vals[0])+sizeof(".lc")); sprintf(tmpbuf,pat_file,ldap_dircount,vals[0]); f=NULL; f=fopen(tmpbuf,"rb"); diff --git a/src/plugins/StringsPlugin/StringsPlugin.c b/src/plugins/StringsPlugin/StringsPlugin.c index ccc8705..14d8507 100644 --- a/src/plugins/StringsPlugin/StringsPlugin.c +++ b/src/plugins/StringsPlugin/StringsPlugin.c @@ -49,9 +49,13 @@ char **load_string(FILE *f,int max_count_str, int *countloadstr, /*load from file new strings */ i=0; - while ( !feof(f) || i< max_count_str) + while ( !feof(f) && i < max_count_str) { - if(!fgets(tmpbuf1, 1023,f)) return NULL; + if(!fgets(tmpbuf1, 1023,f)){ + if(pt) free(pt); + free(old_table); + return NULL; + } if ((strstr(tmpbuf1,stop))!=NULL) { break; } diff --git a/src/proxy.c b/src/proxy.c index 17854d7..cfe3e98 100644 --- a/src/proxy.c +++ b/src/proxy.c @@ -240,6 +240,7 @@ void * proxychild(struct clientparam* param) { int keepalive = 0; uint64_t contentlength64 = 0; int hascontent =0; + int clhdrofs = -1; int isconnect = 0; int redirect = 0; int prefix = 0, ckeepalive=0; @@ -413,6 +414,10 @@ for(;;){ param->password = (unsigned char *)strdup((char *)sb+1); param->pwtype = 0; } + else if(param->password){ + free(param->password); + param->password = NULL; + } if(param->username)free(param->username); param->username = (unsigned char *)strdup((char *)username); continue; @@ -458,13 +463,14 @@ for(;;){ if(parsehostname((char *)sb, param, 80)) RETURN(100); } newbuf = malloc(strlen((char *)req) + strlen((char *)(buf+inbuf)) + 8); - if(newbuf){ - sp = (unsigned char *)strchr((char *)req+1, '/'); + sp = (unsigned char *)strchr((char *)req+1, '/'); + if(newbuf && sp){ memcpy(newbuf, req, (sp - req)); sprintf((char*)newbuf + (sp - req), "http://%s%s",sb,sp); free(req); req = newbuf; } + else if(newbuf) free(newbuf); if(se)*se = c; } if(ftp && i > 13 && (!strncasecmp((char *)(buf+inbuf), "authorization", 13))){ @@ -485,6 +491,10 @@ for(;;){ if(param->extpassword)free(param->extpassword); param->extpassword = (unsigned char *)strdup((char *)sb+1); } + else if(param->extpassword){ + free(param->extpassword); + param->extpassword = NULL; + } if(param->extusername)free(param->extusername); param->extusername = (unsigned char *)strdup((char *)username); continue; @@ -565,7 +575,7 @@ for(;;){ if (conf.filtermaxsize && contentlength64 > (uint64_t)conf.filtermaxsize) { param->nolongdatfilter = 1; } - else if(param->ndatfilterscli > 0 && contentlength64 > 0){ + else if(param->ndatfilterscli > 0 && contentlength64 > 0 && contentlength64 == (uint64_t)(unsigned long)contentlength64){ uint64_t newlen64; newlen64 = (uint64_t) sockfillbuffcli(param, (unsigned long)contentlength64, CONNECTION_S); if(newlen64 == contentlength64) { @@ -921,6 +931,7 @@ for(;;){ authenticate = 0; param->chunked = 0; hascontent = 0; + clhdrofs = -1; while( (i = sockgetlinebuf(param, SERVER, buf + inbuf, LINESIZE - 1, '\n', conf.timeouts[(res)?STRING_S:STRING_L])) > 2) { if(!res && i>9)param->status = res = atoi((char *)buf + inbuf + 9); @@ -942,6 +953,7 @@ for(;;){ authenticate = 1; } else if(i > 15 && (!strncasecmp((char *)(buf+inbuf), "content-length", 14))){ + if(param->chunked) continue; buf[inbuf+i]=0; sb = (unsigned char *)strchr((char *)(buf+inbuf), ':'); if(!sb)continue; @@ -949,6 +961,7 @@ for(;;){ while(isspace(*sb))sb++; sscanf((char *)sb, "%"SCNu64"", &contentlength64); hascontent = 1; + clhdrofs = inbuf; if(param->unsafefilter && param->ndatfilterssrv > 0) { hascontent = 2; continue; @@ -965,6 +978,12 @@ for(;;){ while(isspace(*sb))sb++; if(!strncasecmp((char *)sb, "chunked", 7)){ param->chunked = 1; + if(clhdrofs >= 0){ + buf[clhdrofs] = 'X'; + clhdrofs = -1; + contentlength64 = 0; + hascontent = 0; + } } } inbuf += i; @@ -1009,7 +1028,7 @@ for(;;){ if (conf.filtermaxsize && contentlength64 > (uint64_t)conf.filtermaxsize) { param->nolongdatfilter = 1; } - else if(param->ndatfilterssrv > 0 && contentlength64 > 0 && param->operation != HTTP_HEAD && res != 204 && res != 304){ + else if(param->ndatfilterssrv > 0 && contentlength64 > 0 && contentlength64 == (uint64_t)(unsigned long)contentlength64 && param->operation != HTTP_HEAD && res != 204 && res != 304){ uint64_t newlen; newlen = (uint64_t)sockfillbuffsrv(param, (unsigned long) contentlength64, CONNECTION_S); if(newlen == contentlength64) { diff --git a/src/smtpp.c b/src/smtpp.c index 1f57514..e90df35 100644 --- a/src/smtpp.c +++ b/src/smtpp.c @@ -233,9 +233,10 @@ void * smtppchild(struct clientparam* param) { if(i<4 || strncasecmp((char *)buf, "334", 3)) {RETURN(682);} *username = 0; i = (int)strlen((char *)param->extusername) + 1; + res = (int)strlen((char *)param->extpassword); + if(i + 1 + res >= (int)sizeof(username) || ((i + 1 + res + 2) / 3) * 4 + 1 > (int)sizeof(buf)) {RETURN(683);} memcpy(username+1, param->extusername, i); i++; - res = (int)strlen((char *)param->extpassword); memcpy(username + i, param->extpassword, res); i+=res; en64(username, buf, i); diff --git a/src/tlspr.c b/src/tlspr.c index 45f7966..0c69810 100644 --- a/src/tlspr.c +++ b/src/tlspr.c @@ -69,7 +69,7 @@ int parsehello(int type, unsigned char *hello, unsigned len, char *sni, int * sn elen = size16(hello+offset); offset += 2; if(elen+offset != len) return -9; - while(elen > 1){ + while(elen > 3){ unsigned xlen; xlen = size16(hello+offset+2); if(xlen+4 > elen) return -10;