Vulnerability: The squidclamav_check_preview_handler function in squidclamav.c in SquidClamav 5.x before 5.8 and 6.x before 6.7 passes an unescaped URL to a system command call, which allows remote attackers to cause a denial of service (daemon crash) via a URL with certain characters, as demonstrated using %0D or %0A.
Vulnerability ID: CVE-2012-3501
CWE: CWE-119
Severity score: 2.9
Project: https://github.com/darold/squidclamav
Published: 2012-08-25
File: src/squidclamav.c
Commit message: Add a workaround for a squidGuard bug that unescape the URL and send it back unescaped. This could conduct in wrong result and ssquidclamav crash especially with URL containing the %0D or %0A character. John Xue
Commit author: Darold Gilles

Vulnerable version (before fix):
 void cfgreload_command(char *, int, char **); int create_pipe(char *command); int dconnect (void); int connectINET(char *serverHost, uint16_t serverPort);   /* ----------------------------------------------------- */  int squidclamav_check_preview_handler(char *preview_data, int preview_data_len,            /* Check URL header against squidGuard */      if (usepipe == 1) { 	ci_debug_printf(2, "DEBUG squidclamav_check_preview_handler: Sending request to chained program: %s\n", squidguard); 	ci_debug_printf(2, "DEBUG squidclamav_check_preview_handler: Request: %s %s %s %s\n", httpinf.url,clientip,username,httpinf.method); 	fprintf(sgfpw,"%s %s %s %s\n",httpinf.url,clientip,username,httpinf.method); 	fflush(sgfpw); 	/* the chained redirector must return empty line if ok or the redirection url */ 	chain_ret = (char *)malloc(sizeof(char)*MAX_URL_SIZE); 	if (chain_ret != NULL) { connectINET(char *serverHost, uint16_t serverPort)         return asockd; } 

Fixed version (after fix):
 void cfgreload_command(char *, int, char **); int create_pipe(char *command); int dconnect (void); int connectINET(char *serverHost, uint16_t serverPort); char * replace(const char *s, const char *old, const char *new);  /* ----------------------------------------------------- */  int squidclamav_check_preview_handler(char *preview_data, int preview_data_len,            /* Check URL header against squidGuard */      if (usepipe == 1) { 	char *rbuff = NULL; 	ci_debug_printf(2, "DEBUG squidclamav_check_preview_handler: Sending request to chained program: %s\n", squidguard); 	ci_debug_printf(2, "DEBUG squidclamav_check_preview_handler: Request: %s %s %s %s\n", httpinf.url,clientip,username,httpinf.method); 	/* escaping escaped character to prevent unescaping by squidguard */ 	rbuff = replace(httpinf.url, "%", "%25"); 	fprintf(sgfpw,"%s %s %s %s\n",rbuff,clientip,username,httpinf.method); 	fflush(sgfpw); 	xfree(rbuff); 	/* the chained redirector must return empty line if ok or the redirection url */ 	chain_ret = (char *)malloc(sizeof(char)*MAX_URL_SIZE); 	if (chain_ret != NULL) { connectINET(char *serverHost, uint16_t serverPort)         return asockd; }   /**  * Searches all occurrences of old into s  * and replaces with new  */ char * replace(const char *s, const char *old, const char *new) { 	char *ret; 	int i, count = 0; 	size_t newlen = strlen(new); 	size_t oldlen = strlen(old);  	for (i = 0; s[i] != '\0'; i++) { 		if (strstr(&s[i], old) == &s[i]) { 			count++; 			i += oldlen - 1; 		} 	} 	ret = malloc(i + 1 + count * (newlen - oldlen)); 	if (ret != NULL) { 		i = 0; 		while (*s) { 			if (strstr(s, old) == s) { 				strcpy(&ret[i], new); 				i += newlen; 				s += oldlen; 			} else { 				ret[i++] = *s++; 			} 		} 		ret[i] = '\0'; 	}  	return ret; } 

Patch:
patch: @@ -133,7 +133,7 @@ void cfgreload_command(char *, int, char **);
 int create_pipe(char *command);
 int dconnect (void);
 int connectINET(char *serverHost, uint16_t serverPort);
-
+char * replace(const char *s, const char *old, const char *new);
 
 /* ----------------------------------------------------- */
 
@@ -365,10 +365,14 @@ int squidclamav_check_preview_handler(char *preview_data, int preview_data_len,
      
      /* Check URL header against squidGuard */
      if (usepipe == 1) {
+	char *rbuff = NULL;
 	ci_debug_printf(2, "DEBUG squidclamav_check_preview_handler: Sending request to chained program: %s\n", squidguard);
 	ci_debug_printf(2, "DEBUG squidclamav_check_preview_handler: Request: %s %s %s %s\n", httpinf.url,clientip,username,httpinf.method);
-	fprintf(sgfpw,"%s %s %s %s\n",httpinf.url,clientip,username,httpinf.method);
+	/* escaping escaped character to prevent unescaping by squidguard */
+	rbuff = replace(httpinf.url, "%", "%25");
+	fprintf(sgfpw,"%s %s %s %s\n",rbuff,clientip,username,httpinf.method);
 	fflush(sgfpw);
+	xfree(rbuff);
 	/* the chained redirector must return empty line if ok or the redirection url */
 	chain_ret = (char *)malloc(sizeof(char)*MAX_URL_SIZE);
 	if (chain_ret != NULL) {
@@ -1628,3 +1632,40 @@ connectINET(char *serverHost, uint16_t serverPort)
         return asockd;
 }
 
+
+/**
+ * Searches all occurrences of old into s
+ * and replaces with new
+ */
+char *
+replace(const char *s, const char *old, const char *new)
+{
+	char *ret;
+	int i, count = 0;
+	size_t newlen = strlen(new);
+	size_t oldlen = strlen(old);
+
+	for (i = 0; s[i] != '\0'; i++) {
+		if (strstr(&s[i], old) == &s[i]) {
+			count++;
+			i += oldlen - 1;
+		}
+	}
+	ret = malloc(i + 1 + count * (newlen - oldlen));
+	if (ret != NULL) {
+		i = 0;
+		while (*s) {
+			if (strstr(s, old) == s) {
+				strcpy(&ret[i], new);
+				i += newlen;
+				s += oldlen;
+			} else {
+				ret[i++] = *s++;
+			}
+		}
+		ret[i] = '\0';
+	}
+
+	return ret;
+}
+
path: src/squidclamav.c