The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Уязвимость в web-фреймворке Django, которая может привести к подстановке SQL-кода

04.07.2022 15:22

Опубликованы корректирующие выпуски web-фреймворка Django 4.0.6 и 3.2.14, в которых устранена уязвимость (CVE-2022-34265), потенциально позволяющая выполнить подстановку своего SQL-кода. Проблема затрагивает приложения, использующие непроверенные внешние данные в параметрах kind и lookup_name, передаваемых в функции Trunc(kind) и Extract(lookup_name). Программы, которые допускают в значениях lookup_name и kind только проверенные данные уязвимость не затрагивает.

Проблема блокирована через запрет использования в аргументах функций Extract и Trunc символов отличных от букв, цифр, "-", "_", "(" и ")". Ранее в передаваемых значениях не вырезалась одинарная кавычка, что позволяло выполнить свои SQL-конструкции через передачу значений вида "day' FROM start_datetime)) OR 1=1;--" и "year', start_datetime)) OR 1=1;--". В следующем выпуске 4.1 планируется дополнительно усилить защиту методов извлечения и усечения дат, но внесённые в API изменения приведут к нарушению совместимости со сторонними бэкендами для работы с БД.

  1. Главная ссылка к новости (https://www.openwall.com/lists...)
  2. OpenNews: Выпуск web-фреймворка Django 3.0
  3. OpenNews: WordPress и Apache Struts среди web-платформ лидируют по числу уязвимостей с эксплоитами
  4. OpenNews: Удалённо эксплуатируемая уязвимость в форумном движке MyBB
  5. OpenNews: Отчёт о компрометации git-репозитория и базы пользователей проекта PHP
  6. OpenNews: Новый вариант атаки на Log4j 2, позволяющий обойти добавленную защиту
Лицензия: CC BY 3.0
Короткая ссылка: https://opennet.ru/57452-django
Ключевые слова: django, sql
При перепечатке указание ссылки на opennet.ru обязательно


Обсуждение (66) Ajax | 1 уровень | Линейный | +/- | Раскрыть всё | RSS
  • 1.1, Аноним (1), 15:32, 04/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    просто используешь построитель запросов и все. Но нет, давайте клеить sql-строку самому.
     
     
  • 2.4, Без аргументов (?), 15:50, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –6 +/
    Меня, как опытному в SQL, люто бомбит от использования ORM, в котором надо трижды извратиться, сделав более сложночитаемый код, чтобы сделать более менее сложную операцию, сильно связывает руки. Но, конечно, за такие уязвимости и актисанитарию пороть надо.
     
     
  • 3.6, Аноним (6), 16:06, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –7 +/
    У меня конструкции вида select(func.max(text("cast(key as integer)")),text('value')).select_from(cls, func.json_tree(cls.relations)).where(text("key and json_type(key) == 'integer' and type=='object'")) всё норм, ничего не бомбит. Легко читается, даже легче, чем чисто sql. Вы что-то делаете не так.
     
     
  • 4.8, Аноним (6), 16:11, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –2 +/
    Кстати, там то же самое рядом через ORM, если это зло и намного хуже, то я даже не знаю ем тут помочь.

    maxvalue = max([int(k) for k in self.relations])
    return (maxvalue, self.relations[str(maxvalue)],)

    а вот это orm-sql

    return select(func.max(text("cast(key as integer)"))).select_from(cls, func.json_tree(cls.relations)).where(text("key and json_type(key) == 'integer' and type=='object'"))

    ну и где читаемее?

     
     
  • 5.25, Без аргументов (?), 19:54, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    везде нифига не понятно.
    интересно, как это будет работать, если строк миллион. а можно ли передать ему подсказку, в каких случаях использовать и какие индексы? нет.
     
     
  • 6.31, Аноним (6), 20:19, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Прекрасно это работает. Откуда там миллиону взяться, если поле с жсоном обновляется пару раз в месяц? Причём, используется всегда только последнее значение, старые данные остаются для истории. Тут никаких проблем быть не может.
     
     
  • 7.48, ПерлухаБратуха (?), 04:46, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > тут никаких проблем быть не может.

    Вот так и начинаются кадастрофы.

     
     
  • 8.53, Аноним (6), 13:44, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    В данном случае это костыль, позволяющий избежать бессмысленного усложнения схем... текст свёрнут, показать
     
  • 5.39, Онаним (?), 22:11, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    max([int(k) for k in self.relations]) - это какая-то хитрая чёрная магия?
    Или просто у нас все объекты предвгружены? А если их там 100500000 и каждый по килобайту размером?
    В жирных специфично сдизайненных контейнерах ORM получится что-то типа self.relations.getMaxByKey('k'), тогда уже да, никакой магии, просто эксплицитная поддержка.
     
     
  • 6.40, Онаним (?), 22:13, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Но если к max() надо ещё условие присандалить, то будет получаться шиза вида self.relations.getSubCollection(...conditions).getMaxByKey('k'), и это шиза.
     
  • 6.43, Аноним (6), 22:45, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Это именно получение последнего по времени объекта из жсона, с ним можно работать как с обычным хеш тейблом. Только возвращает всё текстом, мне не понравился вариант пропатчить на поддержку других типов. При необходимости, он сначала подгружается (целиком, там undefer, да). Изменений там от 0 до нескольких за месяц, содержит категории и ссылки на данные (которые не обязательно имеются в одной из таблиц). Мне показалось это подходящим для моих задач, но я так и не смог нагуглить ничего даже отдалённо похожего. Потом можно будет скидывать старые данные куда-нибудь на диск, это же жсон.
     
  • 4.24, Без аргументов (?), 19:52, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Я ничего тут не понял что там у вас про JSON. Я говорю про человеческую нормализованную СУБД.
    Но сразу виду обезьянокод. Какие-то двойные преобразования типов из текста в число. И еще макс можно сделать в одном запросе без вложения.
     
     
  • 5.30, Аноним (6), 20:16, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Да не, тут нормально всё. Это json в sqlite, надо получить последнюю по времени запись из поля с жсоном, ключи в котором принудительно конвертируются в текст, а значит, последнее значение из текстового ключа не получить. Я это к тому, что даже если ORM не позволяет решить не стандартную задачу адекватно (вставки text), это всё ещё вполне прилично выглядит в результате, куда лучше чем голый зубодробительный sql с сотней уязвимостей на запрос.
     
     
  • 6.45, Без аргументов (?), 00:03, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Ну я же говорю, что это детский садик. Другое дело когда у вас оракл и 120ГБ ОЗУ и пол терабайта данных продаж с тысячи магазинов за пол года перелопатить надо.
     
     
  • 7.46, Аноним (6), 00:11, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Так если бы там были какие-то данные, я бы как минимум не стал использовать жсон в sqlite. Речь то совсем о другом. Я сомневаюсь, что перелопачивать пол терабайта надо прям на каждый запрос (для такого памяти маловато опять же), а значит, не исключено, что орм тут себя прекраснейшим образом проявит.
     
  • 6.49, GG (ok), 10:53, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • –2 +/
    ОРМ нужен не для того чтобы неадекватную архитектуру покрывать.
    Если кто-то модель данных спроектировал так что там без такого геморроя велосипедов запрос не сделать — надо гнать его ссаными тряпками из профессии и переделывать нормально, а не городить вот это вот.
     
     
  • 7.55, Аноним (6), 14:39, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Хех, но ведь это же вообще не имеет отношения к ORM, претензия должна быть к SQL (ну или к json).
     
  • 5.56, Аноним (56), 03:13, 06/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    На практике в нормализованных БД слишком много джойнов, даже если это база микросервиса с довольно узкой зоной ответственности. Поэтому денормализация до второй формы
    и требования к "академичности" пониже и код пожиже. Зато работает в рамках заданного SLA.
     
     
  • 6.67, Онаним (?), 00:03, 08/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Ха. Да. Дальше второй формы уходить - это уже для особых случаев. Когда не жалко.
     
  • 4.37, Онаним (?), 22:07, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    Глаза чутка подвытекли.
    Я уж за производительность этого добра даже не переживаю.
    Каждый вызов функции - это офигенные накладные расходы даже в компилируемом языке, чем и плохи все генераторы.
    В компилируемых такие вещи хотя бы заинлайнить можно, но в скриптовых всё очень плохо.
     
     
  • 5.44, Аноним (6), 22:52, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Кстати, с производительностью всё удивительно хорошо в итоге. Ну и оно же кэшируется. С диска долго, в памяти нормально, никаких претензий. Проблемы начинаются, когда генерирует что-то не то.
     
  • 4.38, Онаним (?), 22:07, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    Хотя конечно после cast(key as integer) там уже точно ничего не страшно
     
  • 4.61, Аноним (61), 19:50, 06/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > всё норм, ничего не бомбит

    особенно тут:
    > where(text("key and json_type(key) == 'integer' and type=='object'"))

     
     
  • 5.62, Аноним (6), 20:08, 06/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Разве что, чуточку. Но в SQL это выглядело не лучше и позволяло выполнить подстановку чего-нибудь нехорошего в 5 местах, а тут не получится.
     
     
  • 6.68, Аноним (68), 17:02, 09/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > позволяло выполнить подстановку чего-нибудь нехорошего

    Не понял, ты на код глазами смотришь или чем? Про возможность эскейпинга подставляемых значений безо всяких ормов что-нибудь слышал?

     
  • 4.66, Аноним (66), 18:38, 07/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    >Вы что-то делаете не так.

    Да, я, например, хочу строить запросы одинаково на любом рантайме.

     
  • 3.7, Аноним (7), 16:09, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +3 +/
    Опять все путают ORM и Query Builder ORM нужна для того, чтобы автоматически ор... большой текст свёрнут, показать
     
     
  • 4.18, kai3341 (ok), 19:29, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > Для аналитических же запросов - в которых и нужны более-менее сложные операции - ORM вообще не применимы (поскольку тут нет двухстороннего маппинга).
    > Но тут подойдет только мощный query builder, не уступающий написанию запроса вручную, типа SQLAlchemy

    Вот весь прикол в том, что аналитические запросы есть смысл класть на нормальную ORM типа алхимии (DjangoORM говно). Так как статью (ниже) читать дело не царское, скажу тут. ORM позволяет сделать SQL-запрос модульным, позволяет повторно переиспользовать код через наследование или композицию. Также адекватная ORM умеет в вывод типов. Алхимия позволяет также реализовывать макросы, что удачно сочетается с выводом типов.

    Ещё раз для тех, что не умеет читать: чмORM типа той же djangoORM дают преимущества только на простейших запросах (CRUD), но на аналитике дрыщут в лужу. Адекватные ORM типа алхимии дают как преимущества на банальном CRUD, так и на аналитике.

    Ну и таки шо по производительности? Если верхний мозг не использовать, то алхимия тормозит, джанга чуть меньше тормозит. Если верхний мозг в наличии, то небольшие тормоза получаем на этапе инициализации, но в рантайме производительность мало отличается от raw sql. Вопросу уделено особое внимание в статье

    А ещё про миграции никто не рассказал

     
     
  • 5.19, Аноним (19), 19:41, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Да, ORM-функциональность Алхимии можно использовать и с аналитикой, осуществляя автоматическую гидрацию read models. Но поскольку двустороннего маппинга тут по очевидным причинам нет, не совсем корректно называть это ORM.
     
     
  • 6.23, kai3341 (ok), 19:48, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > Да, ORM-функциональность Алхимии можно использовать и с аналитикой, осуществляя автоматическую
    > гидрацию read models. Но поскольку двустороннего маппинга тут по очевидным причинам
    > нет, не совсем корректно называть это ORM.

    Пользуясь случаем, о каком двустороннем маппинге речь? Об ActiveRecord? Если да, то где он вообще есть для такого типа запросов?

     
  • 5.41, Онаним (?), 22:16, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –1 +/
    Поскольку тормозит применительно к питону - это его естественное состояние, всё вами описанное таки верно.
     
  • 4.50, GG (ok), 10:56, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • –1 +/
    > нет двухстороннего маппинга

    Давно уже есть, просто довольно нетривиально

     
  • 3.9, kai3341 (ok), 16:16, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –3 +/
    > Меня, как опытному в SQL, люто бомбит от использования ORM, в котором надо трижды извратиться, сделав более сложночитаемый код, чтобы сделать более менее сложную операцию, сильно связывает руки. Но, конечно, за такие уязвимости и актисанитарию пороть надо.

    Меня тоже бомбило, но потом я попробовал алхимию: https://habr.com/ru/post/559738/
    Выходит плюс/минус тот же самый синтаксис

     
     
  • 4.12, Аноним (12), 17:30, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Если выходит плюс минус так же, зачем? Чтобы что? В базу оно все равно пойдет в виде запроса. Лишь бы не писать SQL?
    Из той же серии билдеры регулярных выражений (не путать с грамматиками). Иррациональный страх перед технологиями отцов-основателей? Своё лучше пахнет и делает волосы мягче и шелковистее?
     
     
  • 5.13, kai3341 (ok), 18:19, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –2 +/
    Как думаете, зачем я дал ссылку на статью? Ответ -- в ней ответы на заданные вами вопросы и некоторые ещё не заданные
     
  • 3.11, Аноним (11), 16:53, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +2 +/
    Расскажи пожалуйста подробно, зачем ты делаешь сложные операции? Ты не думал что твои геройствования связаны с плохой архитектурой?
     
     
  • 4.20, Без аргументов (?), 19:45, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Я работал разработчиком BI-отчетов и витрин данных. Там на первом месте оптимизация и скорость выполнения. Как выше написали, ORM/QB детский садик работает для примитивных CRUD. Насчет мапинга не скажу, что удобнее. Точнее мапинг можно и из сырого запроса сделать без всякого блоатваре.
     
     
  • 5.51, GG (ok), 10:57, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • –3 +/
    Вон из профессии
     
  • 4.27, Без аргументов (?), 19:57, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –1 +/
    Вся эта шелуха работает до тех пор, пока проект более менее не вырастет в большую кучу данных, а не тупо выбрать товары, чтобы в вебне показать витрину.
     
     
  • 5.32, Аноним (6), 20:51, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Но-но, у нас тут хайлоад, кек Орм в первую очередь, про удобство взаимодействия... большой текст свёрнут, показать
     
     
  • 6.35, Онаним (?), 21:59, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +2 +/
    Народ ныне очень любит ORM отрывать от логики объектов, превращая его в прослойку между объектами и БД, и запихивая всю логику обработки полностью в рассредоточенные контроллеры. Вот с этого подхода лично меня коробит, это лишняя абстракция ради абстракции

    Сам предпочитаю жирный ORM и контейнеры. Когда объекты из ORM рассованы по своим управляющим контейнерам, отвечающим за вгрузку-выгрузку, и когда они работают как полноценные объекты от логики - представляя собой не только тупой набор данных, но и всё, что нужно для их обработки. Из ORM у этих объектов "лишнего" только чтение-запись-удаление, в остальном они обычная часть логики обработки. Наличие контейнеров позволяет оптимизировать внутри контейнеров такие операции, как нестандартная выборка по условию, добавляя те части SQL, которые в самых дубовых 1-1 ORM'ах реализуются через одно место внешними запросами и впихиванием данных.

     
  • 5.52, GG (ok), 11:00, 05/07/2022 [^] [^^] [^^^] [ответить]  
  • –1 +/
    Если у тебя набор данных сложнее товаров для витрины превращается в непроходимую кучу — это исключительно твоя проблема.
    И заключается она не в ОРМ, а в том что ты себя возомнил архитектором, когда на самом деле тебе и бейджик кодера-юниора снимать рано ещё.
     
  • 4.36, Онаним (?), 22:03, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Пока у тебя да, полтора товара на витрине, как правильно указали - тебе пойдёт и один запрос на объект.
    Но когда у тебя хотя бы 100M записей в день начнёт капать, которые ещё надо с другими объектами связывать, тебе так или иначе их придётся пакетной обработкой проходить...
     
  • 3.17, Аноним (17), 18:56, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    А каким образом Вы боретесь с уязвимостями типа "sql-иньекция"?
     
     
  • 4.21, Без аргументов (?), 19:46, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Проверить входные параметры, чтобы без точек с запятой и т.п. А вообще изкоробки обычно есть постановка параметров (через ???), которая проверяет.
     
     
  • 5.28, kai3341 (ok), 20:05, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    > Проверить входные параметры, чтобы без точек с запятой и т.п. А вообще
    > изкоробки обычно есть постановка параметров (через ???), которая проверяет.

    Любой драйвер БД умеет в подстановку значений. Я рекомендую читать документацию

     
  • 3.26, Без аргументов (?), 19:56, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • –1 +/
    Нифига сколько неосиляторов в SQL минусонуло. Ну я тоже с трудом въезжал. Пока не научился делать пятэтажные запросы и такие, и сякие, с динамической группировкой и фильтрами на тысячу строк кода. А бывает, когда в СУБД надо передать хинты, т.к. оптимизатор не экстрасенс на все случаи жизни
     
     
  • 4.33, Аноним (6), 21:00, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Не в этом дело, мне кажется. Просто, комментатор слишком уж категоричен. Ну и потом, я слабо себе представляю, как можно использовать ORM, без чёткого понимания, что и зачем он генерирует.
     
  • 3.42, Онаним (?), 22:19, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    Ды не, ORM своё место. Тут стоит для начала вернуться к тому, что ORM от SQL-injection как правило не спасает, поскольку часть вещей на ORM всё равно нереализуема и сваливается к передаче в ORM кусков запроса. Самый простой пример - когда надо вгрузить объекты, подходящие по длинным цепочкам пропертей других объектов. Нет, можно всё повгружать "по порядочку", но когда у тебя на каждом шаге по миллиону записей - это становится немножко накладно.
     

  • 1.2, васёк (?), 15:36, 04/07/2022 Скрыто ботом-модератором [﹢﹢﹢] [ · · · ]     [к модератору]
  • –1 +/
     

     ....ответы скрыты (2)

  • 1.14, Аристарх (??), 18:46, 04/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • –2 +/
    > подстановку своего SQL-кода

    Тест на имбецила в 21 веке. Если такая ошибка находится, СРАЗУ же в отдел кадров на увольнение по собственному!

     
     
  • 2.22, Без аргументов (?), 19:47, 04/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    Соглы.
     

  • 1.15, Sergey (??), 18:46, 04/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    А как это можно использовать ? Не совсем понятно, куда нужно вбивать свой СКУЛь запрос ;(
     
     
  • 2.63, Аноним (63), 14:41, 07/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    В эксплоит. Эксплоит тебе придеться купить.
     

  • 1.16, Аноним (17), 18:49, 04/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +1 +/
    Нужно использовать "подготовленные выражения" и тогда sql-иньекции станут невозможны.
     
  • 1.29, Онаним (?), 20:10, 04/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • –1 +/
    затонеphp(tm)
    Суть та же, криворучки - они повсюду.
     
  • 1.34, Аноним (34), 21:51, 04/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +1 +/
    Sql injection в 2022? Достижение!
     
     
  • 2.64, Аноним (63), 14:43, 07/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Достижение, что наконец-то лидерство какого-то говнянеького фреймворка начинает шататься.
    Вопрос только, что где что-то можное современное молодежное с асинхроньщиной и поэтессами =)
     

  • 1.47, Аноним (47), 04:32, 05/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +1 +/
    Перечисленные методы в нормальных руках (при нормальных мозгах) никогда не будут принимать непроверенные параметры. Расходимся посоны...
     
  • 1.54, Аноним (61), 14:06, 05/07/2022 [ответить] [﹢﹢﹢] [ · · · ]  
  • +/
    > непроверенные внешние данные

    А раст может как-то помочь? Или в расте такие же дыры?

     
     
  • 2.57, Аноним (56), 03:25, 06/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    От ошибок в бизнес-логике раст тоже не защитит. Только от гонок и некоторых видов утечек в куче.
     
     
  • 3.58, Аноним (6), 03:47, 06/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    Пока вебрендера не было, синхронизация вкладок не отваливалась.
     
     
  • 4.60, Аноним (60), 17:45, 06/07/2022 [^] [^^] [^^^] [ответить]  
  • +1 +/
    Ратс не нужен забудь про него плз.  
     
     
  • 5.65, Аноним (63), 14:49, 07/07/2022 [^] [^^] [^^^] [ответить]  
  • +/
    Так его так и не доснимали же ...
     

     Добавить комментарий
    Имя:
    E-Mail:
    Текст:



    Партнёры:
    PostgresPro
    Inferno Solutions
    Hosting by Hoster.ru
    Хостинг:

    Закладки на сайте
    Проследить за страницей
    Created 1996-2024 by Maxim Chirkov
    Добавить, Поддержать, Вебмастеру