@@ -8636,3 +8636,152 @@ async def test_buffered_raw_cursor(self) -> None:
8636
8636
],
8637
8637
await cur .fetchall (),
8638
8638
)
8639
+
8640
+
8641
+ class BugOra37013057 (tests .MySQLConnectorTests ):
8642
+ """BUG#37013057: mysql-connector-python Parameterized query SQL injection
8643
+
8644
+ Malicious strings can be injected when utilizing
8645
+ dictionary-based query parameterization via the
8646
+ `cursor.execute()` API command and the C-based
8647
+ implementation of the connector.
8648
+
8649
+ This patch fixes the injection issue.
8650
+ """
8651
+
8652
+ table_name = "BugOra37013057"
8653
+
8654
+ cur_flavors = [
8655
+ {},
8656
+ {"prepared" : True },
8657
+ {"raw" : True },
8658
+ {"buffered" : True },
8659
+ {"dictionary" : True },
8660
+ ]
8661
+
8662
+ sql_dict = f"INSERT INTO { table_name } (username, password, city) VALUES (%(username)s, %(password)s, %(city)s)"
8663
+ sql_tuple = (
8664
+ f"INSERT INTO { table_name } (username, password, city) VALUES (%s, %s, %s)"
8665
+ )
8666
+
8667
+ cases = [
8668
+ {
8669
+ "values_dict" : {
8670
+ "username" : "%(password)s" ,
8671
+ "password" : ", sleep(10));--" ,
8672
+ "city" : "Montevideo" ,
8673
+ },
8674
+ "values_tuple" : ("%(password)s" , ", sleep(10));--" , "Montevideo" ),
8675
+ },
8676
+ {
8677
+ "values_dict" : {
8678
+ "username" : "%(password)s" ,
8679
+ "password" : ", curdate());" ,
8680
+ "city" : "Rio" ,
8681
+ },
8682
+ "values_tuple" : ("%(password)s" , ", curdate());" , "Rio" ),
8683
+ },
8684
+ {
8685
+ "values_dict" : {
8686
+ "username" : "%(password)s" ,
8687
+ "password" : "%(city)s" ,
8688
+ "city" : ", database());" ,
8689
+ },
8690
+ "values_tuple" : ("%(password)s" , "%(city)s" , ", database());" ),
8691
+ },
8692
+ ]
8693
+
8694
+ def setUp (self ):
8695
+ with mysql .connector .connect (** tests .get_mysql_config ()) as cnx :
8696
+ with cnx .cursor () as cur :
8697
+ cur .execute (
8698
+ f"CREATE TABLE { self .table_name } "
8699
+ "(username varchar(50), password varchar(50), city varchar(50))"
8700
+ )
8701
+
8702
+ def tearDown (self ):
8703
+ with mysql .connector .connect (** tests .get_mysql_config ()) as cnx :
8704
+ with cnx .cursor () as cur :
8705
+ cur .execute (f"DROP TABLE IF EXISTS { self .table_name } " )
8706
+
8707
+ def _prepare_cur_dict_res (self , case ):
8708
+ if isinstance (case ["values" ], dict ):
8709
+ return case ["values_dict" ]
8710
+
8711
+ def _run_execute (self , dict_based = True , cur_config = {}):
8712
+ sql = self .sql_dict if dict_based else self .sql_tuple
8713
+ for case in self .cases :
8714
+ if dict_based :
8715
+ values = case ["values_dict" ]
8716
+ else :
8717
+ values = case ["values_tuple" ]
8718
+
8719
+ if "dictionary" in cur_config :
8720
+ exp_res = case ["values_dict" ].copy ()
8721
+ elif "raw" in cur_config :
8722
+ exp_res = tuple ([x .encode () for x in case ["values_tuple" ]])
8723
+ else :
8724
+ exp_res = case ["values_tuple" ]
8725
+
8726
+ with self .cnx .cursor (** cur_config ) as cur :
8727
+ cur .execute (sql , values )
8728
+ cur .execute (f"select * from { self .table_name } " )
8729
+ res = cur .fetchone ()
8730
+ cur .execute (f"TRUNCATE TABLE { self .table_name } " )
8731
+ self .assertEqual (res , exp_res )
8732
+
8733
+ @foreach_cnx ()
8734
+ def test_execute_dict_based_injection (self ):
8735
+ for cur_config in self .cur_flavors :
8736
+ self ._run_execute (dict_based = True , cur_config = cur_config )
8737
+
8738
+ @foreach_cnx ()
8739
+ def test_execute_tuple_based_injection (self ):
8740
+ for cur_config in self .cur_flavors :
8741
+ self ._run_execute (dict_based = False , cur_config = cur_config )
8742
+
8743
+
8744
+ class BugOra37013057_async (tests .MySQLConnectorAioTestCase ):
8745
+ """BUG#37013057: mysql-connector-python Parameterized query SQL injection
8746
+
8747
+ For a description see `test_bugs.BugOra37013057`.
8748
+ """
8749
+
8750
+ def setUp (self ) -> None :
8751
+ self .bug_37013057 = BugOra37013057 ()
8752
+ self .bug_37013057 .setUp ()
8753
+
8754
+ def tearDown (self ) -> None :
8755
+ self .bug_37013057 .tearDown ()
8756
+
8757
+ async def _run_execute (self , dict_based = True , cur_config = {}):
8758
+ sql = self .bug_37013057 .sql_dict if dict_based else self .bug_37013057 .sql_tuple
8759
+ for case in self .bug_37013057 .cases :
8760
+ if dict_based :
8761
+ values = case ["values_dict" ]
8762
+ else :
8763
+ values = case ["values_tuple" ]
8764
+
8765
+ if "dictionary" in cur_config :
8766
+ exp_res = case ["values_dict" ].copy ()
8767
+ elif "raw" in cur_config :
8768
+ exp_res = tuple ([x .encode () for x in case ["values_tuple" ]])
8769
+ else :
8770
+ exp_res = case ["values_tuple" ]
8771
+
8772
+ async with await self .cnx .cursor (** cur_config ) as cur :
8773
+ await cur .execute (sql , values )
8774
+ await cur .execute (f"select * from { self .bug_37013057 .table_name } " )
8775
+ res = await cur .fetchone ()
8776
+ await cur .execute (f"TRUNCATE TABLE { self .bug_37013057 .table_name } " )
8777
+ self .assertEqual (res , exp_res )
8778
+
8779
+ @foreach_cnx_aio ()
8780
+ async def test_execute_dict_based_injection (self ):
8781
+ for cur_config in self .bug_37013057 .cur_flavors :
8782
+ await self ._run_execute (dict_based = True , cur_config = cur_config )
8783
+
8784
+ @foreach_cnx_aio ()
8785
+ async def test_execute_tuple_based_injection (self ):
8786
+ for cur_config in self .bug_37013057 .cur_flavors :
8787
+ await self ._run_execute (dict_based = False , cur_config = cur_config )
0 commit comments