CVE-2012-0036: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') (CWE-89)
value: curl and libcurl 7.2x before 7.24.0 do not properly consider special characters during extraction of a pathname from a URL, which allows remote attackers to conduct data-injection attacks via a crafted URL, as demonstrated by a CRLF injection attack on the (1) IMAP, (2) POP3, or (3) SMTP protocol.
CWE description: The product constructs all or part of an SQL command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended SQL command when it is sent to a downstream component. Without sufficient removal or quoting of SQL syntax in user-controllable inputs, the generated SQL query can cause those inputs to be interpreted as SQL instead of ordinary user data.
Published: 2012-04-13T20:55Z
Repository: https://github.com/bagder/curl
Language: C
Files changed: lib/escape.c, lib/escape.h, lib/imap.c, lib/pop3.c, lib/smtp.c
Commit date: 2012-01-24 08:54:26 +0100
Commit message: URL sanitize: reject URLs containing bad data

Protocols (IMAP, POP3 and SMTP) that use the path part of a URL in a
decoded manner now use the new Curl_urldecode() function to reject URLs
with embedded control codes (anything that is or decodes to a byte value
less than 32).

URLs containing such codes could easily otherwise be used to do harm and
allow users to do unintended actions with otherwise innocent tools and
applications. Like for example using a URL like
pop3://pop3.example.com/1%0d%0aDELE%201 when the app wants a URL to get
a mail and instead this would delete one.

This flaw is considered a security vulnerability: CVE-2012-0036

Security advisory at: http://curl.haxx.se/docs/adv_20120124.html

Reported by: Dan Fandrich

Diff: diff --git a/lib/escape.c b/lib/escape.c
index b0922bc93..0dd5a1d0a 100644
--- a/lib/escape.c
+++ b/lib/escape.c
@@ -29,10 +29,11 @@
 
 #include "curl_memory.h"
 #include "urldata.h"
 #include "warnless.h"
 #include "non-ascii.h"
+#include "escape.h"
 
 #define _MPRINTF_REPLACE /* use our functions only */
 #include <curl/mprintf.h>
 
 /* The last #include file should be: */
@@ -82,11 +83,11 @@ char *curl_easy_escape(CURL *handle, const char *string, int inlength)
   size_t alloc = (inlength?(size_t)inlength:strlen(string))+1;
   char *ns;
   char *testing_ptr = NULL;
   unsigned char in; /* we need to treat the characters unsigned */
   size_t newlen = alloc;
-  int strindex=0;
+  size_t strindex=0;
   size_t length;
   CURLcode res;
 
   ns = malloc(alloc);
   if(!ns)
@@ -130,27 +131,33 @@ char *curl_easy_escape(CURL *handle, const char *string, int inlength)
   ns[strindex]=0; /* terminate it */
   return ns;
 }
 
 /*
- * Unescapes the given URL escaped string of given length. Returns a
- * pointer to a malloced string with length given in *olen.
- * If length == 0, the length is assumed to be strlen(string).
- * If olen == NULL, no output length is stored.
+ * Curl_urldecode() URL decodes the given string.
+ *
+ * Optionally detects control characters (byte codes lower than 32) in the
+ * data and rejects such data.
+ *
+ * Returns a pointer to a malloced string in *ostring with length given in
+ * *olen. If length == 0, the length is assumed to be strlen(string).
+ *
  */
-char *curl_easy_unescape(CURL *handle, const char *string, int length,
-                         int *olen)
+CURLcode Curl_urldecode(struct SessionHandle *data,
+                        const char *string, size_t length,
+                        char **ostring, size_t *olen,
+                        bool reject_ctrl)
 {
-  int alloc = (length?length:(int)strlen(string))+1;
+  size_t alloc = (length?length:strlen(string))+1;
   char *ns = malloc(alloc);
   unsigned char in;
-  int strindex=0;
+  size_t strindex=0;
   unsigned long hex;
   CURLcode res;
 
   if(!ns)
-    return NULL;
+    return CURLE_OUT_OF_MEMORY;
 
   while(--alloc > 0) {
     in = *string;
     if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) {
       /* this is two hexadecimal digits following a '%' */
@@ -162,30 +169,60 @@ char *curl_easy_unescape(CURL *handle, const char *string, int length,
 
       hex = strtoul(hexstr, &ptr, 16);
 
       in = curlx_ultouc(hex); /* this long is never bigger than 255 anyway */
 
-      res = Curl_convert_from_network(handle, &in, 1);
+      res = Curl_convert_from_network(data, &in, 1);
       if(res) {
         /* Curl_convert_from_network calls failf if unsuccessful */
         free(ns);
-        return NULL;
+        return res;
       }
 
       string+=2;
       alloc-=2;
     }
+    if(reject_ctrl && (in < 0x20)) {
+      free(ns);
+      return CURLE_URL_MALFORMAT;
+    }
 
     ns[strindex++] = in;
     string++;
   }
   ns[strindex]=0; /* terminate it */
 
   if(olen)
     /* store output size */
     *olen = strindex;
-  return ns;
+
+  if(ostring)
+    /* store output string */
+    *ostring = ns;
+
+  return CURLE_OK;
+}
+
+/*
+ * Unescapes the given URL escaped string of given length. Returns a
+ * pointer to a malloced string with length given in *olen.
+ * If length == 0, the length is assumed to be strlen(string).
+ * If olen == NULL, no output length is stored.
+ */
+char *curl_easy_unescape(CURL *handle, const char *string, int length,
+                         int *olen)
+{
+  char *str = NULL;
+  size_t inputlen = length;
+  size_t outputlen;
+  CURLcode res = Curl_urldecode(handle, string, inputlen, &str, &outputlen,
+                                FALSE);
+  if(res)
+    return NULL;
+  if(olen)
+    *olen = curlx_uztosi(outputlen);
+  return str;
 }
 
 /* For operating systems/environments that use different malloc/free
    systems for the app and for this library, we provide a free that uses
    the library's memory system */
diff --git a/lib/escape.h b/lib/escape.h
index 04b06a973..4c7f84133 100644
--- a/lib/escape.h
+++ b/lib/escape.h
@@ -1,16 +1,16 @@
-#ifndef __ESCAPE_H
-#define __ESCAPE_H
+#ifndef HEADER_CURL_ESCAPE_H
+#define HEADER_CURL_ESCAPE_H
 
 /***************************************************************************
  *                                  _   _ ____  _
  *  Project                     ___| | | |  _ \| |
  *                             / __| | | | |_) | |
  *                            | (__| |_| |  _ <| |___
  *                             \___|\___/|_| \_\_____|
  *
- * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
  *
  * This software is licensed as described in the file COPYING, which
  * you should have received as part of this distribution. The terms
  * are also available at http://curl.haxx.se/docs/copyright.html.
  *
@@ -23,7 +23,11 @@
  *
  ***************************************************************************/
 /* Escape and unescape URL encoding in strings. The functions return a new
  * allocated string or NULL if an error occurred.  */
 
+CURLcode Curl_urldecode(struct SessionHandle *data,
+                        const char *string, size_t length,
+                        char **ostring, size_t *olen,
+                        bool reject_crlf);
 
 #endif
diff --git a/lib/imap.c b/lib/imap.c
index c39664d86..c6701417d 100644
--- a/lib/imap.c
+++ b/lib/imap.c
@@ -951,21 +951,16 @@ static CURLcode imap_parse_url_path(struct connectdata *conn)
 {
   /* the imap struct is already inited in imap_connect() */
   struct imap_conn *imapc = &conn->proto.imapc;
   struct SessionHandle *data = conn->data;
   const char *path = data->state.path;
-  int len;
 
   if(!*path)
     path = "INBOX";
 
   /* url decode the path and use this mailbox */
-  imapc->mailbox = curl_easy_unescape(data, path, 0, &len);
-  if(!imapc->mailbox)
-    return CURLE_OUT_OF_MEMORY;
-
-  return CURLE_OK;
+  return Curl_urldecode(data, path, 0, &imapc->mailbox, NULL, TRUE);
 }
 
 /* call this when the DO phase has completed */
 static CURLcode imap_dophase_done(struct connectdata *conn,
                                   bool connected)
diff --git a/lib/pop3.c b/lib/pop3.c
index b7781109b..cc360b8b9 100644
--- a/lib/pop3.c
+++ b/lib/pop3.c
@@ -912,15 +912,11 @@ static CURLcode pop3_parse_url_path(struct connectdata *conn)
   struct pop3_conn *pop3c = &conn->proto.pop3c;
   struct SessionHandle *data = conn->data;
   const char *path = data->state.path;
 
   /* url decode the path and use this mailbox */
-  pop3c->mailbox = curl_easy_unescape(data, path, 0, NULL);
-  if(!pop3c->mailbox)
-    return CURLE_OUT_OF_MEMORY;
-
-  return CURLE_OK;
+  return Curl_urldecode(data, path, 0, &pop3c->mailbox, NULL, TRUE);
 }
 
 /* call this when the DO phase has completed */
 static CURLcode pop3_dophase_done(struct connectdata *conn,
                                   bool connected)
diff --git a/lib/smtp.c b/lib/smtp.c
index 4cb25efca..553c697d0 100644
--- a/lib/smtp.c
+++ b/lib/smtp.c
@@ -1242,11 +1242,10 @@ static CURLcode smtp_connect(struct connectdata *conn,
   CURLcode result;
   struct smtp_conn *smtpc = &conn->proto.smtpc;
   struct SessionHandle *data = conn->data;
   struct pingpong *pp = &smtpc->pp;
   const char *path = conn->data->state.path;
-  int len;
   char localhost[HOSTNAME_MAX + 1];
 
   *done = FALSE; /* default to not done yet */
 
   /* If there already is a protocol-specific struct allocated for this
@@ -1314,13 +1313,13 @@ static CURLcode smtp_connect(struct connectdata *conn,
     else
       path = "localhost";
   }
 
   /* url decode the path and use it as domain with EHLO */
-  smtpc->domain = curl_easy_unescape(conn->data, path, 0, &len);
-  if(!smtpc->domain)
-    return CURLE_OUT_OF_MEMORY;
+  result = Curl_urldecode(conn->data, path, 0, &smtpc->domain, NULL, TRUE);
+  if(result)
+    return result;
 
   /* When we connect, we start in the state where we await the server greeting
    */
   state(conn, SMTP_SERVERGREET);
 

Vulnerable code:   int strindex=0;
 * Unescapes the given URL escaped string of given length. Returns a
 * pointer to a malloced string with length given in *olen.
 * If length == 0, the length is assumed to be strlen(string).
 * If olen == NULL, no output length is stored.
char *curl_easy_unescape(CURL *handle, const char *string, int length,
                         int *olen)
  int alloc = (length?length:(int)strlen(string))+1;
  int strindex=0;
    return NULL;
      res = Curl_convert_from_network(handle, &in, 1);
        return NULL;
  return ns;
#ifndef __ESCAPE_H
#define __ESCAPE_H
 * Copyright (C) 1998 - 2006, Daniel Stenberg, <daniel@haxx.se>, et al.
  int len;
  imapc->mailbox = curl_easy_unescape(data, path, 0, &len);
  if(!imapc->mailbox)
    return CURLE_OUT_OF_MEMORY;

  return CURLE_OK;
  pop3c->mailbox = curl_easy_unescape(data, path, 0, NULL);
  if(!pop3c->mailbox)
    return CURLE_OUT_OF_MEMORY;

  return CURLE_OK;
  int len;
  smtpc->domain = curl_easy_unescape(conn->data, path, 0, &len);
  if(!smtpc->domain)
    return CURLE_OUT_OF_MEMORY;
Fixed code: #include "escape.h"
  size_t strindex=0;
 * Curl_urldecode() URL decodes the given string.
 *
 * Optionally detects control characters (byte codes lower than 32) in the
 * data and rejects such data.
 *
 * Returns a pointer to a malloced string in *ostring with length given in
 * *olen. If length == 0, the length is assumed to be strlen(string).
 *
CURLcode Curl_urldecode(struct SessionHandle *data,
                        const char *string, size_t length,
                        char **ostring, size_t *olen,
                        bool reject_ctrl)
  size_t alloc = (length?length:strlen(string))+1;
  size_t strindex=0;
    return CURLE_OUT_OF_MEMORY;
      res = Curl_convert_from_network(data, &in, 1);
        return res;
    if(reject_ctrl && (in < 0x20)) {
      free(ns);
      return CURLE_URL_MALFORMAT;
    }

  if(ostring)
    /* store output string */
    *ostring = ns;

  return CURLE_OK;
}

/*
 * Unescapes the given URL escaped string of given length. Returns a
 * pointer to a malloced string with length given in *olen.
 * If length == 0, the length is assumed to be strlen(string).
 * If olen == NULL, no output length is stored.
 */
char *curl_easy_unescape(CURL *handle, const char *string, int length,
                         int *olen)
{
  char *str = NULL;
  size_t inputlen = length;
  size_t outputlen;
  CURLcode res = Curl_urldecode(handle, string, inputlen, &str, &outputlen,
                                FALSE);
  if(res)
    return NULL;
  if(olen)
    *olen = curlx_uztosi(outputlen);
  return str;
#ifndef HEADER_CURL_ESCAPE_H
#define HEADER_CURL_ESCAPE_H
 * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
CURLcode Curl_urldecode(struct SessionHandle *data,
                        const char *string, size_t length,
                        char **ostring, size_t *olen,
                        bool reject_crlf);
  return Curl_urldecode(data, path, 0, &imapc->mailbox, NULL, TRUE);
  return Curl_urldecode(data, path, 0, &pop3c->mailbox, NULL, TRUE);
  result = Curl_urldecode(conn->data, path, 0, &smtpc->domain, NULL, TRUE);
  if(result)
    return result;