Hi,
On Sat, Oct 22, 2016 at 08:27:56AM +0200, Salvatore Bonaccorso wrote:
> Source: mysql-connector-python
> Version: 2.1.3-1
> Severity: grave
> Tags: security upstream
> 
> Hi,
> 
> the following vulnerability was published for mysql-connector-python.
> 
> CVE-2016-5598[0].
> 
> If you fix the vulnerability please also make sure to include the
> CVE (Common Vulnerabilities & Exposures) id in your changelog entry.
> 
> For further information see:
> 
> [0] https://security-tracker.debian.org/tracker/CVE-2016-5598
> [1] 
> http://www.oracle.com/technetwork/security-advisory/cpuoct2016verbose-2881725.html#MSQL
> 
> Please adjust the affected versions in the BTS as needed.

While 2.1.4 has other changes the only change in 2.0.5 is the CVE
fix. It seems this is caused by format string expansion in
_format_params_dict. I've attached the diff between 2.0.4 → 2.0.5. I
think wheezy is affected since it uses pythons format expansion there
but I'd be glad about a second opinion.
Cheers,
 -- Guido
diff --git a/CHANGES.txt b/CHANGES.txt
index 7ee20de..fbdca18 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -3,11 +3,16 @@ MySQL Connector/Python 2.0 - Release Notes & Changes
 ====================================================
 
 MySQL Connector/Python
-Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
+Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
 
 Full release notes:
  http://dev.mysql.com/doc/relnotes/connector-python/en/
 
+
+v2.0.5
+======
+- BUG22529828: Fix potencial SQL injection
+
 v2.0.4
 ======
 
diff --git a/PKG-INFO b/PKG-INFO
index de8b3a5..46adcb2 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: mysql-connector-python
-Version: 2.0.4
+Version: 2.0.5
 Summary: MySQL driver written in Python
 Home-page: http://dev.mysql.com/doc/connector-python/en/index.html
 Author: Oracle and/or its affiliates
diff --git a/README.txt b/README.txt
index 2f88034..99be628 100644
--- a/README.txt
+++ b/README.txt
@@ -3,7 +3,7 @@ MySQL Connector/Python 2.0
 ==========================
 
 MySQL Connector/Python
-Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
+Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
 
 License information can be found in the LICENSE.txt file.
 
@@ -28,7 +28,7 @@ doubt, this particular copy of the software is released
 under the version 2 of the GNU General Public License.
 MySQL Connector/Python is brought to you by Oracle.
 
-Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
+Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
 
 License information can be found in the LICENSE.txt file.
 
diff --git a/lib/mysql/connector/cursor.py b/lib/mysql/connector/cursor.py
index 3e6a2b4..b9bfd9a 100644
--- a/lib/mysql/connector/cursor.py
+++ b/lib/mysql/connector/cursor.py
@@ -1,5 +1,5 @@
 # MySQL Connector/Python - MySQL driver written in Python.
-# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
 
 # MySQL Connector/Python is licensed under the terms of the GPLv2
 # <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -29,6 +29,7 @@ import re
 import weakref
 
 from . import errors
+from .catch23 import PY2
 
 SQL_COMMENT = r"\/\*.*?\*\/"
 RE_SQL_COMMENT = re.compile(
@@ -42,6 +43,14 @@ RE_SQL_INSERT_STMT = re.compile(
     re.I | re.M | re.S)
 RE_SQL_INSERT_VALUES = re.compile(r'.*VALUES\s*(\(.*\)).*', re.I | re.M | re.S)
 RE_PY_PARAM = re.compile(b'(%s)')
+RE_PY_MAPPING_PARAM = re.compile(
+    br'''
+    %
+    \((?P<mapping_key>[^)]+)\)
+    (?P<conversion_type>[diouxXeEfFgGcrs%])
+    ''',
+    re.X
+)
 RE_SQL_SPLIT_STMTS = re.compile(
     b''';(?=(?:[^"'`]*["'`][^"'`]*["'`])*[^"'`]*$)''')
 RE_SQL_FIND_PARAM = re.compile(
@@ -73,6 +82,35 @@ class _ParamSubstitutor(object):
         return len(self.params) - self.index
 
 
+def _bytestr_format_dict(bytestr, value_dict):
+    """
+    >>> _bytestr_format_dict(b'%(a)s', {b'a': b'foobar'})
+    b'foobar
+    >>> _bytestr_format_dict(b'%%(a)s', {b'a': b'foobar'})
+    b'%%(a)s'
+    >>> _bytestr_format_dict(b'%%%(a)s', {b'a': b'foobar'})
+    b'%%foobar'
+    >>> _bytestr_format_dict(b'%(x)s %(y)s',
+    ...                      {b'x': b'x=%(y)s', b'y': b'y=%(x)s'})
+    b'x=%(y)s y=%(x)s'
+    """
+    def replace(matchobj):
+        value = None
+        groups = matchobj.groupdict()
+        if groups["conversion_type"] == b"%":
+            value = b"%"
+        if groups["conversion_type"] == b"s":
+            key = groups["mapping_key"].encode("utf-8") \
+                  if PY2 else groups["mapping_key"]
+            value = value_dict[key]
+        if value is None:
+            raise ValueError("Unsupported conversion_type: {0}"
+                             "".format(groups["conversion_type"]))
+        return value.decode("utf-8") if PY2 else value
+    return RE_PY_MAPPING_PARAM.sub(replace, bytestr.decode("utf-8")
+                                   if PY2 else bytestr)
+
+
 class CursorBase(object):
     """
     Base for defining MySQLCursor. This class is a skeleton and defines
@@ -355,7 +393,10 @@ class MySQLCursor(CursorBase):
                 conv = to_mysql(conv)
                 conv = escape(conv)
                 conv = quote(conv)
-                res["%({0})s".format(key).encode()] = conv
+                if PY2:
+                    res[key] = conv
+                else:
+                    res[key.encode()] = conv
         except Exception as err:
             raise errors.ProgrammingError(
                 "Failed processing pyformat-parameters; %s" % err)
@@ -488,8 +529,8 @@ class MySQLCursor(CursorBase):
 
         if params is not None:
             if isinstance(params, dict):
-                for key, value in self._process_params_dict(params).items():
-                    stmt = stmt.replace(key, value)
+                stmt = _bytestr_format_dict(
+                    stmt, self._process_params_dict(params))
             elif isinstance(params, (list, tuple)):
                 psub = _ParamSubstitutor(self._process_params(params))
                 stmt = RE_PY_PARAM.sub(psub, stmt)
@@ -543,8 +584,8 @@ class MySQLCursor(CursorBase):
             for params in seq_params:
                 tmp = fmt
                 if isinstance(params, dict):
-                    for key, value in self._process_params_dict(params).items():
-                        tmp = tmp.replace(key, value)
+                    tmp = _bytestr_format_dict(
+                        tmp, self._process_params_dict(params))
                 else:
                     psub = _ParamSubstitutor(self._process_params(params))
                     tmp = RE_PY_PARAM.sub(psub, tmp)
diff --git a/lib/mysql/connector/version.py b/lib/mysql/connector/version.py
index 99267f2..9431b1b 100644
--- a/lib/mysql/connector/version.py
+++ b/lib/mysql/connector/version.py
@@ -1,5 +1,5 @@
 # MySQL Connector/Python - MySQL driver written in Python.
-# Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2012, 2016, Oracle and/or its affiliates. All rights reserved.
 
 # MySQL Connector/Python is licensed under the terms of the GPLv2
 # <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -26,7 +26,7 @@ The file version.py gets installed and is available after installation
 as mysql.connector.version.
 """
 
-VERSION = (2, 0, 4, '', 0)
+VERSION = (2, 0, 5, '', 0)
 
 if VERSION[3] and VERSION[4]:
     VERSION_TEXT = '{0}.{1}.{2}{3}{4}'.format(*VERSION)
diff --git a/tests/test_cursor.py b/tests/test_cursor.py
index e07342c..e2aef9f 100644
--- a/tests/test_cursor.py
+++ b/tests/test_cursor.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2009, 2014, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
 
 # MySQL Connector/Python is licensed under the terms of the GPLv2
 # <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
@@ -360,6 +360,8 @@ class MySQLCursorTests(tests.TestsCursor):
             datetime.time(20, 3, 23),
             st_now,
             datetime.timedelta(hours=40, minutes=30, seconds=12),
+            'foo %(t)s',
+            'foo %(s)s',
         )
         exp = (
             b'NULL',
@@ -381,6 +383,8 @@ class MySQLCursorTests(tests.TestsCursor):
             b"'" + time.strftime('%Y-%m-%d %H:%M:%S', st_now).encode('ascii')
             + b"'",
             b"'40:30:12'",
+            b"'foo %(t)s'",
+            b"'foo %(s)s'",
         )
 
         self.cnx = connection.MySQLConnection(**tests.get_mysql_config())
@@ -420,28 +424,32 @@ class MySQLCursorTests(tests.TestsCursor):
             'p': datetime.time(20, 3, 23),
             'q': st_now,
             'r': datetime.timedelta(hours=40, minutes=30, seconds=12),
+            's': 'foo %(t)s',
+            't': 'foo %(s)s',
         }
         exp = {
-            b'%(a)s': b'NULL',
-            b'%(b)s': b'128',
-            b'%(c)s': b'1281288',
-            b'%(d)s': repr(float(3.14)) if PY2 else b'3.14',
-            b'%(e)s': b"'3.14'",
-            b'%(f)s': b"'back\\\\slash'",
-            b'%(g)s': b"'newline\\n'",
-            b'%(h)s': b"'return\\r'",
-            b'%(i)s': b"'\\'single\\''",
-            b'%(j)s': b'\'\\"double\\"\'',
-            b'%(k)s': b"'windows\\\x1a'",
-            b'%(l)s': b"'Strings are sexy'",
-            b'%(m)s': b"'\xe8\x8a\xb1'",
-            b'%(n)s': b"'2008-05-07 20:01:23'",
-            b'%(o)s': b"'2008-05-07'",
-            b'%(p)s': b"'20:03:23'",
-            b'%(q)s': b"'" +
+            b'a': b'NULL',
+            b'b': b'128',
+            b'c': b'1281288',
+            b'd': repr(float(3.14)) if PY2 else b'3.14',
+            b'e': b"'3.14'",
+            b'f': b"'back\\\\slash'",
+            b'g': b"'newline\\n'",
+            b'h': b"'return\\r'",
+            b'i': b"'\\'single\\''",
+            b'j': b'\'\\"double\\"\'',
+            b'k': b"'windows\\\x1a'",
+            b'l': b"'Strings are sexy'",
+            b'm': b"'\xe8\x8a\xb1'",
+            b'n': b"'2008-05-07 20:01:23'",
+            b'o': b"'2008-05-07'",
+            b'p': b"'20:03:23'",
+            b'q': b"'" +
             time.strftime('%Y-%m-%d %H:%M:%S', st_now).encode('ascii')
             + b"'",
-            b'%(r)s': b"'40:30:12'",
+            b'r': b"'40:30:12'",
+            b's': b"'foo %(t)s'",
+            b't': b"'foo %(s)s'",
         }
 
         self.cnx = connection.MySQLConnection(**tests.get_mysql_config())

Reply via email to