1. Куча / Говнокод #23590

    −3

    1. 01
    2. 02
    3. 03
    4. 04
    5. 05
    6. 06
    7. 07
    8. 08
    9. 09
    10. 10
    11. 11
    12. 12
    13. 13
    14. 14
    15. 15
    16. 16
    https://habrahabr.ru/company/mailru/blog/344696/
    
    Долбоёбы из "Mail.Ru", приобрётшие авторитет только благодаря тому, что их сайтик случайно стал одним из самых популярных, учат жизни:
    
    >>> НЕ ДЕЛАЙТЕ ТАК:
    >>> 
    >>> /* Небезопасный код: */
    >>> $query = $pdo->query("SELECT * FROM users WHERE username = '" . $_GET['username'] . "'");
    >>> 
    >>> Делайте так:
    >>> 
    >>> /* Защищено от SQL-внедрений: */
    >>> $results = $easydb->row("SELECT * FROM users WHERE username = ?", $_GET['username']);
    
    Будто переменную в первом варианте запроса нельзя пропустить через "mysql_real_escape_string" или иную экранирующую функцию.
    Зато в итоге запрос остаётся очевидным, без даунских высчитываний вида "каким по счёту является значение поля и какой по счёту вопросительный знак ему соответствует". Тьфу, блядь...

    Запостил: COWuTEJIbTBOEuMAMKu, 15 Декабря 2017

    Комментарии (45) RSS

    • подготовленные запросы - это не экранирование, и всё правильно там говорят
      Ответить
      • чем подготовленные запросы лучше экранирования?
        Ответить
        • тем, что параметры улетают отдельно от запроса и не подвержены инъекциям вообще в принципе, не говоря уж про переиспользование
          Ответить
          • > переиспользование
            Тогда уж орм. Всё проще, чем говно из кусков клеить, да ещё и с параметрами отдельно от запроса...
            Ответить
            • я про то, что сам запрос может уйти на сервак и быть прооптимизирован единожды (даже если вызван сотню раз с разными параметрами).
              Ответить
          • Я же просил преимущества перед экранированием. Экранирование тоже не подвержено инъекциям.

            > переиспользование
            Типа перфоманс? Это не всегда актуально, но при этом усложняет логику (подготавливать запросы, держать открытую сессию с базой и переиспользовать ее).

            В общем, не абсолютное превосходство конечно.
            Ответить
            • учитывая, что речь идет про php, то для стандартной пыхомакаки нет другой субд, как под стать этому дерьму mysql

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

              из-за мудаков, которые не научились в параметры запроса, оракл начиная с какой-то версии ВСЕ константы выносит в бинд параметры уже на своей стороне (даже where foo = 0 and bar is null) и твой неподготовленный запрос все равно будет подготовленным (т.е. для него также поищут в кеше уже план и т.д.)

              > держать открытую сессию с базой
              пыхобляди даже в 2017 году несут какой-то адовый пиздец, что пул сессий с базой - это сложно
              Ответить
              • > 2017 год

                Пыхоскрипты выполняются ≤ 30 секунд и умирают, на этом вся пыхоиндустрия держится. Какой толк от пула, если контекст потеряется, когда страничка отрендерится?
                Ответить
                • подключение к бд - дорогая для бд операция, обычно существенно, на несколько порядков, дороже select 1

                  для пыхоиндустрии так-то куча мидлвари есть типа pgpool

                  когда страничка отрендерится для одного хттп клиента, для другого параллельно потребуется ровно такой же дб юзер, с тем же окружением (правами и т.д.) и теми же запросами (ага, привет кеш подготовленных запросов!)
                  Ответить
              • И почему же ты несешь адовый пиздец, пыхоблядь? Может тебе хуйнуть еще одну глобальную переменную и не сложно, а я предпочитаю не таскать за собой "сессии" по коду, когда в этом нет необходимости.
                Ответить
              • > но в нормальной субд подготовленный запрос - это кеш, это уже экономия на этапе парсинга, это посчитанный план один раз

                кэш там в ж не надо.

                я бенчи с оракакла видел. кэш там только мешает и является ярым боттлнеком: он один на тучи клиентов, и его надо лочить для апдейта. поэтому и подготовленые запросы там это must. и лок контеншн они уменьшают, и размер кэша не раздувают.

                с другой стороны, парсинг сукля это мелочи - меньше процента в общем случае - по сравнению с хождением на диск или даже передаче данных по сети.

                с еще более другой стороны, кэш он только и помогает что бы избежать пересчёта плана оптимизации запроса. на больших базах это грабли потому что кэш надо сбрасывать в ручную, иначе запросы будут делать с устаревшим планом. на мелкой веб питушне - там ни запросов ни размеров нет, когда планы сложно считать.

                из того что слышал, тот же некро-сукль этими проблемами не страдает. и ему просто по барабану - подготовленые или неподготовленые запросы - разница в производительности маргинальная. и кэш там тоже как лучше чем в оракакле мэнеджится, и проблем с ним нет.
                Ответить
                • > на больших базах это грабли, запросы будут делать с устаревшим планом
                  wrong
                  Если база большая, то ситуация минуту назад или даже час назад ничем не отличается от текущей, с чего бы план поменялся?

                  План вообще строится такой хуйней, как "оптимизатор", задача оптимизатора собирать статистику по таблицам, индексам и даже по коэффициенту разнообразия хранящихся в колонках данных, это в любой вменяемой субд так.

                  Если ты индекс добавишь, партиций набьешь или матвьюху вместо одноименной вьюхи присунешь, то оракл (я вижу у тебя отдельная любовь к нему, ну пусть) и так старый план запроса инвалидирует сам, будь он хоть 100 раз закеширован + там тоже не для дураков сделано, есть соответствующие трешолды если таблица была на 1К записей, а стала на 10М - оптимизатор об этом вспомнит и без тебя

                  > кэш надо сбрасывать в ручную
                  Расскажи поподробнее, как часто ты это делаешь? Я вот кучу лет жил без этого, пришлось поискать, нагуглил только alter system flush shared_pool (который сбрасывает не только кеш запросов), нагуглил, что он нихуя не нужен даже в отладочных целях (когда разработчик бд отлаживает основные сценарии продакшен обращений к бд). Тем более непонятно, как это должен делать ынтерпрайзный бек через хибернейт, и, главное, когда.

                  > на мелкой веб питушне - там ни запросов ни размеров нет, когда планы сложно считать
                  В любой, даже самой мелкой питушне, как только она становится порталом (с личным кабинетом, срезами по подмножеству контента, зависящими от текущего user-id, в т.ч. права на доступ и зависимости от менструального цикла текущего юзера) начинаются многоэтажные запросы, которые для получения 5 строк должны сходить в 10 таблиц. Если для пыхомакаки "с большими проектами с кучей бизнеслогики" даже нет разницы биндить значения или получать O\\\\\\\\\\'Hara, то чего уж тут про кеш рассусоливать
                  Ответить
                  • > Если база большая, то ситуация минуту назад или даже час назад ничем не отличается от текущей, с чего бы план поменялся?
                    > > кэш надо сбрасывать в ручную
                    > Расскажи поподробнее, как часто ты это делаешь?

                    лично не делаю.

                    на больших базах, для бэкапов некоторые оссобенно безразмерно растущие таблицы периодически пересоздаются (например под новым именем, с таймстемпом в имени таблицы, приложение автоматом льёт данные в "актуальную" таблицу).

                    иначе их просто не забэкапить, ни удалить надёжно в будущем не удастся. (да, и простые бэкапы тут тоже не катят: народ редиректит часто это в выделенную ДБ на выделенном серваке. тормозит - но выбора мало.)

                    т.е. у тебя таблица часто каждую день/неделю/месяц новая.

                    > ... есть соответствующие трешолды если таблица была на 1К записей, а стала на 10М - оптимизатор об этом вспомнит и без тебя

                    очевидно что - по крайней мере оракаклы 10 и раннее - этого не делают.

                    сам видел, сам по этим граблям ходил. после сброса кэша запрос резко делался в минуты, а не в часы. (сколько там данных было - не спрашивай, потому что "select 1 from xxx" там тормозило прилично, почему очень сильно не рекомендовалось, и я даже не пробовал. и работало на тех данных много людей - поэтому когда твой запрос тормозит час, значит что и всего остального тима будет тоже подтормаживать.)

                    к ораклу у меня нет любви, просто на телекомах на нем все сидят, и опыт с настоящими базами у меня почти исплючительно оракакловый.
                    Ответить
                    • Лично видел как в батчах prepared statementы давали профит. Работало осязаемо быстрее.

                      Есть еще такой забавный способ, который почти не используют. Делаем один prepared statement и туда в цикле суём сотни разных параметров.

                      Типа
                      //make I/O only one time
                      PreparedStatement ps= con.prepareStatement("select huy from pizda where dzhigurda=?");
                      for (A a:b){
                         ps.setString(..)
                         ps.executeQuery(..)
                      ..
                      }
                      Ответить
                      • > Лично видел как в батчах prepared statementы давали профит. Работало осязаемо быстрее.

                        не спорю. но по крайней мере на оракакале, этот профит по большей части из-за того что не надо апдейтить кэш.

                        потому что если не делать препареды, то у тебя мнгновенно появляется боттлнек на кэше стэйтментов.

                        > Есть еще такой забавный способ, который почти не используют.

                        я такое как раз для батчей на оракле и делал.

                        так же читал про фанатов которые делали еще один шаг глубже: создавали временную строред процедуру для выборки. это даёт 100% производительность на оракле, без боттлнеков/этц.
                        Ответить
                • >а больших базах это грабли потому что кэш надо сбрасывать в ручную, иначе запросы будут делать с устаревшим планом.

                  Статистику надо апдейтить, если оптимизатор планы негодные суёт.
                  Еще есть всякие with recompile.
                  Ответить
              • >но в нормальной субд подготовленный запрос - это кеш, это уже экономия на этапе парсинга, это посчитанный план один раз

                Люто плюсую.
                Ответить
            • > не подвержено инъекциям

              хуета, без знания текущей кодировки, режима экранирования сервера и всех нюансов можно наглотаться хуев по самое небалуйся

              https://stackoverflow.com/questions/5741187/sql-injection-that-gets-around-mysql-real-escape-string

              В то время как можно слать параметры отдельно от запроса и тупо не волноваться. Но это, блядь, не наш путь, потому что видите ли лишком сложно писать.

              > но при этом усложняет логику

              вы там в четвертом классе что ли, что вам всё сложнааааа?
              Ответить
              • Просто я делаю большие проекты с кучей бизнеслогики, а не хомпаги как ты, поэтому знаю цену дополнительному коду и глобальным состояниям.
                Ответить
                • о, пошли аргументы к авторитету, дискуссия приближалась к завершению
                  Ответить
                  • где ты их увидел, умняш?
                    Ответить
                    • > Просто я делаю большие проекты с кучей бизнеслогики
                      Ответить
                • Какие ты там проекты делаешь, если для тебя prepared statement это усложнение? Сложно считать какой там параметр где по счету, так что твоей нев**безности мешает использовать named parametrs, или в твоей школе этого не преподавали?
                  Все крупные ORM запиленные для php под капотом билдят запросы именно на основе PDO.
                  Ответить
                  • ORM - это бесползное переусложнение проекта, особенно когда вся бизнес-логика написана на языке шаблонизатора.
                    Ровные пацаны пишут в каждом скрипте: $BD = mysql_connect() or die('Osheebka bazy dannyh :(');
                    Ответить
              • Мне понравился пример с SO.

                — Ко-ко-ко, используйте PDO! При использовании PDO ваши проекты будут безопасными, куд-кудах!
                — Ба-бах! Пример кода с PDO, подверженного инъекции.
                Ответить
                • > Пример кода с PDO, подверженного инъекции.
                  Весь запрос засунули в запрос?
                  Ответить
                  • Оказывается, по умолчанию PDO для MySQL эмулирует подготовленный запрос с помощью sprintf и аналога mysql_real_escape_string. Этот аналог глючит, если подсунуть некорректно закодированную строку в мультибайтовой кодировке.

                    В той же статье указаны способы решения проблемы:

                    0. Не полагаться на mysql_real_escape_string. Есть куча примеров, когда эта функция ничего не делает со строкой, оставляя инъекцию.

                    1. Не использовать «плохие» кодировки. utf8 и utf8mb4 для большинства случаев хорошие.

                    2. Явно указывать серверу кодировку (после подключения к БД), не полагаясь на то, что она уже инициализирована.

                    3. Выключать эмуляцию подготовленных запросов:
                    $pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);


                    Как вариант, для MySQL автор предлагает использовать mysqli::prepare вместо PDO, потому что mysqli никогда не эмулирует подготовленные запросы. Неожиданно, да?

                    Ещё можно отказаться от MySQL в пользу другой СУБД, но что-то мне подсказывает, что изобретательные программисты и туда протащат инъекцию.
                    Ответить
                    • А PDO не умеет использовать mysqli?
                      Ответить
                      • PDO — это альтернатива mysqli и прочим «прямым» драйверам СУБД. У PDO есть свои драйвера: pdo_mysql, pdo_mssql, pdo_pgsql, pdo_sqlite, pdo_firebird и даже pdo_odbc (чтобы коннектиться к базам через ещё один слой).

                        http://php.net/manual/ru/pdo.drivers.php

                        Вот Zend Framework и прочие фреймворки со своими собственными уровнями абстракции могут использовать хоть mysqli, хоть PDO, хоть ещё какой-нибудь драйвер.
                        Ответить
    • Высчитывать не обязательно, если поддерживаются именованные параметры:
      $results = $easydb->row("SELECT * FROM users WHERE username = :fooname", 
          array(':fooname' => $_GET['username']));


      *****

      В каких языках программирования, кроме "PHP", сложилась традиция собирать запрос тупой конкатенацией вместо использования готовых функций для подстановки параметров?
      Ответить
      • Ну в чём-то он прав... Строки с интерполяцией выглядят нагляднее, чем передача параметров через отдельные аргументы.

        В теории, можно было бы сделать безопасный и удобный сахарок:
        // Если бы
        "SELECT * FROM users WHERE username = $username"
        // раскрывалось во что-то похожее на
        new InterpolatedString("SELECT * FROM users WHERE username = ", $username)
        // где на чётных местах стоят литералы, а на нечётных - значения параметров.
        // И функция row(), получив InterpolatedString, могла бы корректно всё заэкранировать или передать через подготовленный запрос и т.п.
        Ответить
        • В современном JS питушня для этого есть:
          function sql(query, ...ps) {
              return query[0] + ps.map((p,i) => sqlEscape(p) + query[i+1]).join('');
              // TODO: define sqlEscape
          }
          
          sql`select * from table where x = ${2} and y = ${3};`
          Ответить
          • О, и вариадик аргументы завезли.
            Ответить
            • > и вариадик аргументы завезли

              всю жизнь был волшебный объект arguments
              Ответить
          • >sql`select * from table where x = ${2} and y = ${3};`
            https://www.youtube.com/watch?v=MGxAtO5n9SY

            Что за новомодная дрочь? Это разве js? [вставил в консоль] OMFG
            https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
            Ответить

    Добавить комментарий