diff --git a/2.2.27.tar.gz b/3.2.12.tar.gz similarity index 52% rename from 2.2.27.tar.gz rename to 3.2.12.tar.gz index 492e7481299394f54f40716f4486e5acc6f532b3..6233704040e9580429c8f4a1a77a5311ed24db39 100644 Binary files a/2.2.27.tar.gz and b/3.2.12.tar.gz differ diff --git a/CVE-2022-28346.patch b/CVE-2022-28346.patch deleted file mode 100644 index 0559915fb579ab42c51605b7f925a17a74773290..0000000000000000000000000000000000000000 --- a/CVE-2022-28346.patch +++ /dev/null @@ -1,168 +0,0 @@ -From 2c09e68ec911919360d5f8502cefc312f9e03c5d Mon Sep 17 00:00:00 2001 -From: Mariusz Felisiak -Date: Fri, 1 Apr 2022 08:10:22 +0200 -Subject: [PATCH] [2.2.x] Fixed CVE-2022-28346 -- Protected - QuerySet.annotate(), aggregate(), and extra() against SQL injection in column - aliases. - -Thanks Splunk team: Preston Elder, Jacob Davis, Jacob Moore, -Matt Hanson, David Briggs, and a security researcher: Danylo Dmytriiev -(DDV_UA) for the report. - -Backport of 93cae5cb2f9a4ef1514cf1a41f714fef08005200 from main. ---- - django/db/models/sql/query.py | 14 ++++++++++ - docs/releases/2.2.28.txt | 8 ++++++ - tests/aggregation/tests.py | 9 ++++++ - tests/annotations/tests.py | 34 +++++++++++++++++++++++ - tests/expressions/test_queryset_values.py | 9 ++++++ - tests/queries/tests.py | 9 ++++++ - 6 files changed, 83 insertions(+) - -diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py -index b99f0e90efad..412e817f107e 100644 ---- a/django/db/models/sql/query.py -+++ b/django/db/models/sql/query.py -@@ -8,6 +8,7 @@ - """ - import difflib - import functools -+import re - from collections import Counter, OrderedDict, namedtuple - from collections.abc import Iterator, Mapping - from itertools import chain, count, product -@@ -40,6 +41,10 @@ - - __all__ = ['Query', 'RawQuery'] - -+# Quotation marks ('"`[]), whitespace characters, semicolons, or inline -+# SQL comments are forbidden in column aliases. -+FORBIDDEN_ALIAS_PATTERN = re.compile(r"['`\"\]\[;\s]|--|/\*|\*/") -+ - - def get_field_names_from_opts(opts): - return set(chain.from_iterable( -@@ -994,8 +999,16 @@ def join_parent_model(self, opts, model, alias, seen): - alias = seen[int_model] = join_info.joins[-1] - return alias or seen[None] - -+ def check_alias(self, alias): -+ if FORBIDDEN_ALIAS_PATTERN.search(alias): -+ raise ValueError( -+ "Column aliases cannot contain whitespace characters, quotation marks, " -+ "semicolons, or SQL comments." -+ ) -+ - def add_annotation(self, annotation, alias, is_summary=False): - """Add a single annotation expression to the Query.""" -+ self.check_alias(alias) - annotation = annotation.resolve_expression(self, allow_joins=True, reuse=None, - summarize=is_summary) - self.append_annotation_mask([alias]) -@@ -1873,6 +1886,7 @@ def add_extra(self, select, select_params, where, params, tables, order_by): - else: - param_iter = iter([]) - for name, entry in select.items(): -+ self.check_alias(name) - entry = str(entry) - entry_params = [] - pos = entry.find("%s") -diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py -index 3820496c9fd5..501a18700baf 100644 ---- a/tests/aggregation/tests.py -+++ b/tests/aggregation/tests.py -@@ -1114,3 +1114,12 @@ def test_arguments_must_be_expressions(self): - Book.objects.aggregate(is_book=True) - with self.assertRaisesMessage(TypeError, msg % ', '.join([str(FloatField()), 'True'])): - Book.objects.aggregate(FloatField(), Avg('price'), is_book=True) -+ -+ def test_alias_sql_injection(self): -+ crafted_alias = """injected_name" from "aggregation_author"; --""" -+ msg = ( -+ "Column aliases cannot contain whitespace characters, quotation marks, " -+ "semicolons, or SQL comments." -+ ) -+ with self.assertRaisesMessage(ValueError, msg): -+ Author.objects.aggregate(**{crafted_alias: Avg("age")}) -diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py -index 021f59d2d71d..27cd7ebfb826 100644 ---- a/tests/annotations/tests.py -+++ b/tests/annotations/tests.py -@@ -598,3 +598,37 @@ def test_annotation_filter_with_subquery(self): - total_books=Subquery(long_books_qs, output_field=IntegerField()), - ).values('name') - self.assertCountEqual(publisher_books_qs, [{'name': 'Sams'}, {'name': 'Morgan Kaufmann'}]) -+ -+ def test_alias_sql_injection(self): -+ crafted_alias = """injected_name" from "annotations_book"; --""" -+ msg = ( -+ "Column aliases cannot contain whitespace characters, quotation marks, " -+ "semicolons, or SQL comments." -+ ) -+ with self.assertRaisesMessage(ValueError, msg): -+ Book.objects.annotate(**{crafted_alias: Value(1)}) -+ -+ def test_alias_forbidden_chars(self): -+ tests = [ -+ 'al"ias', -+ "a'lias", -+ "ali`as", -+ "alia s", -+ "alias\t", -+ "ali\nas", -+ "alias--", -+ "ali/*as", -+ "alias*/", -+ "alias;", -+ # [] are used by MSSQL. -+ "alias[", -+ "alias]", -+ ] -+ msg = ( -+ "Column aliases cannot contain whitespace characters, quotation marks, " -+ "semicolons, or SQL comments." -+ ) -+ for crafted_alias in tests: -+ with self.subTest(crafted_alias): -+ with self.assertRaisesMessage(ValueError, msg): -+ Book.objects.annotate(**{crafted_alias: Value(1)}) -diff --git a/tests/expressions/test_queryset_values.py b/tests/expressions/test_queryset_values.py -index e26459796807..0804531869d9 100644 ---- a/tests/expressions/test_queryset_values.py -+++ b/tests/expressions/test_queryset_values.py -@@ -27,6 +27,15 @@ def test_values_expression(self): - [{'salary': 10}, {'salary': 20}, {'salary': 30}], - ) - -+ def test_values_expression_alias_sql_injection(self): -+ crafted_alias = """injected_name" from "expressions_company"; --""" -+ msg = ( -+ "Column aliases cannot contain whitespace characters, quotation marks, " -+ "semicolons, or SQL comments." -+ ) -+ with self.assertRaisesMessage(ValueError, msg): -+ Company.objects.values(**{crafted_alias: F("ceo__salary")}) -+ - def test_values_expression_group_by(self): - # values() applies annotate() first, so values selected are grouped by - # id, not firstname. -diff --git a/tests/queries/tests.py b/tests/queries/tests.py -index e72ecaa654c8..99ab57f4fc2e 100644 ---- a/tests/queries/tests.py -+++ b/tests/queries/tests.py -@@ -1737,6 +1737,15 @@ def test_extra_select_literal_percent_s(self): - 'bar %s' - ) - -+ def test_extra_select_alias_sql_injection(self): -+ crafted_alias = """injected_name" from "queries_note"; --""" -+ msg = ( -+ "Column aliases cannot contain whitespace characters, quotation marks, " -+ "semicolons, or SQL comments." -+ ) -+ with self.assertRaisesMessage(ValueError, msg): -+ Note.objects.extra(select={crafted_alias: "1"}) -+ - - class SelectRelatedTests(TestCase): - def test_tickets_3045_3288(self): diff --git a/CVE-2022-28347.patch b/CVE-2022-28347.patch deleted file mode 100644 index c7b106cc7ca9e7e192b6b5accccfe1537d0984de..0000000000000000000000000000000000000000 --- a/CVE-2022-28347.patch +++ /dev/null @@ -1,155 +0,0 @@ -From 29a6c98b4c13af82064f993f0acc6e8fafa4d3f5 Mon Sep 17 00:00:00 2001 -From: Mariusz Felisiak -Date: Fri, 1 Apr 2022 13:48:47 +0200 -Subject: [PATCH] [2.2.x] Fixed CVE-2022-28347 -- Protected - QuerySet.explain(**options) against SQL injection on PostgreSQL. - -Backport of 6723a26e59b0b5429a0c5873941e01a2e1bdbb81 from main. ---- - django/db/backends/postgresql/features.py | 1 - - django/db/backends/postgresql/operations.py | 27 +++++++++++++---- - django/db/models/sql/query.py | 10 +++++++ - docs/releases/2.2.28.txt | 7 +++++ - tests/queries/test_explain.py | 33 +++++++++++++++++++-- - 5 files changed, 70 insertions(+), 8 deletions(-) - -diff --git a/django/db/backends/postgresql/features.py b/django/db/backends/postgresql/features.py -index 5c8701c396d4..9f63ca6b0ce1 100644 ---- a/django/db/backends/postgresql/features.py -+++ b/django/db/backends/postgresql/features.py -@@ -53,7 +53,6 @@ class DatabaseFeatures(BaseDatabaseFeatures): - supports_over_clause = True - supports_aggregate_filter_clause = True - supported_explain_formats = {'JSON', 'TEXT', 'XML', 'YAML'} -- validates_explain_options = False # A query will error on invalid options. - - @cached_property - def is_postgresql_9_5(self): -diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py -index 66e5482be6ba..66ac2d5d108c 100644 ---- a/django/db/backends/postgresql/operations.py -+++ b/django/db/backends/postgresql/operations.py -@@ -8,6 +8,18 @@ - class DatabaseOperations(BaseDatabaseOperations): - cast_char_field_without_max_length = 'varchar' - explain_prefix = 'EXPLAIN' -+ explain_options = frozenset( -+ [ -+ "ANALYZE", -+ "BUFFERS", -+ "COSTS", -+ "SETTINGS", -+ "SUMMARY", -+ "TIMING", -+ "VERBOSE", -+ "WAL", -+ ] -+ ) - cast_data_types = { - 'AutoField': 'integer', - 'BigAutoField': 'bigint', -@@ -267,15 +279,20 @@ def window_frame_range_start_end(self, start=None, end=None): - return start_, end_ - - def explain_query_prefix(self, format=None, **options): -- prefix = super().explain_query_prefix(format) - extra = {} -- if format: -- extra['FORMAT'] = format -+ # Normalize options. - if options: -- extra.update({ -+ options = { - name.upper(): 'true' if value else 'false' - for name, value in options.items() -- }) -+ } -+ for valid_option in self.explain_options: -+ value = options.pop(valid_option, None) -+ if value is not None: -+ extra[valid_option.upper()] = value -+ prefix = super().explain_query_prefix(format, **options) -+ if format: -+ extra['FORMAT'] = format - if extra: - prefix += ' (%s)' % ', '.join('%s %s' % i for i in extra.items()) - return prefix -diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py -index 412e817f107e..1e823cfe74b1 100644 ---- a/django/db/models/sql/query.py -+++ b/django/db/models/sql/query.py -@@ -45,6 +45,10 @@ - # SQL comments are forbidden in column aliases. - FORBIDDEN_ALIAS_PATTERN = re.compile(r"['`\"\]\[;\s]|--|/\*|\*/") - -+# Inspired from -+# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS -+EXPLAIN_OPTIONS_PATTERN = re.compile(r"[\w\-]+") -+ - - def get_field_names_from_opts(opts): - return set(chain.from_iterable( -@@ -528,6 +532,12 @@ def has_results(self, using): - - def explain(self, using, format=None, **options): - q = self.clone() -+ for option_name in options: -+ if ( -+ not EXPLAIN_OPTIONS_PATTERN.fullmatch(option_name) or -+ "--" in option_name -+ ): -+ raise ValueError("Invalid option name: '%s'." % option_name) - q.explain_query = True - q.explain_format = format - q.explain_options = options -diff --git a/tests/queries/test_explain.py b/tests/queries/test_explain.py -index 9428bd88e9c3..209c1923071e 100644 ---- a/tests/queries/test_explain.py -+++ b/tests/queries/test_explain.py -@@ -41,8 +41,8 @@ def test_basic(self): - - @skipUnlessDBFeature('validates_explain_options') - def test_unknown_options(self): -- with self.assertRaisesMessage(ValueError, 'Unknown options: test, test2'): -- Tag.objects.all().explain(test=1, test2=1) -+ with self.assertRaisesMessage(ValueError, "Unknown options: TEST, TEST2"): -+ Tag.objects.all().explain(**{"TEST": 1, "TEST2": 1}) - - def test_unknown_format(self): - msg = 'DOES NOT EXIST is not a recognized format.' -@@ -71,6 +71,35 @@ def test_postgres_options(self): - option = '{} {}'.format(name.upper(), 'true' if value else 'false') - self.assertIn(option, captured_queries[0]['sql']) - -+ def test_option_sql_injection(self): -+ qs = Tag.objects.filter(name="test") -+ options = {"SUMMARY true) SELECT 1; --": True} -+ msg = "Invalid option name: 'SUMMARY true) SELECT 1; --'" -+ with self.assertRaisesMessage(ValueError, msg): -+ qs.explain(**options) -+ -+ def test_invalid_option_names(self): -+ qs = Tag.objects.filter(name="test") -+ tests = [ -+ 'opt"ion', -+ "o'ption", -+ "op`tion", -+ "opti on", -+ "option--", -+ "optio\tn", -+ "o\nption", -+ "option;", -+ "你 好", -+ # [] are used by MSSQL. -+ "option[", -+ "option]", -+ ] -+ for invalid_option in tests: -+ with self.subTest(invalid_option): -+ msg = "Invalid option name: '%s'" % invalid_option -+ with self.assertRaisesMessage(ValueError, msg): -+ qs.explain(**{invalid_option: True}) -+ - @unittest.skipUnless(connection.vendor == 'mysql', 'MySQL specific') - def test_mysql_text_to_traditional(self): - # Initialize the cached property, if needed, to prevent a query for diff --git a/python-django.spec b/python-django.spec index 35636fb90e6e38dc7ed9d47b5a423c172d73f54d..d2d6697aa1f61091a28b64e4f3b7d14b4820917f 100644 --- a/python-django.spec +++ b/python-django.spec @@ -1,16 +1,12 @@ %global _empty_manifest_terminate_build 0 Name: python-django -Version: 2.2.27 -Release: 2 +Version: 3.2.12 +Release: 1 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. License: Apache-2.0 and Python-2.0 and OFL-1.1 and MIT URL: https://www.djangoproject.com/ -Source0: https://github.com/django/django/archive/refs/tags/2.2.27.tar.gz +Source0: https://github.com/django/django/archive/refs/tags/3.2.12.tar.gz -#https://github.com/django/django/commit/2c09e68ec911919360d5f8502cefc312f9e03c5d -Patch0: CVE-2022-28346.patch -#https://github.com/django/django/commit/29a6c98b4c13af82064f993f0acc6e8fafa4d3f5 -Patch1: CVE-2022-28347.patch BuildArch: noarch %description @@ -77,6 +73,9 @@ mv %{buildroot}/doclist.lst . %{_docdir}/* %changelog +* Wed May 18 2022 xigaoxinyan - 3.2.12-1 +- Update to 3.2.12 + * Thu Apr 21 2022 yaoxin - 2.2.27-2 - Fix CVE-2022-28346 CVE-2022-28347