К содержанию
Независимый разработчик Обсудим проект

Самые критичные уязвимости php за время его жизни

Самые критичные уязвимости php за время его жизни

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

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

Почему PHP так часто оказывался в центре внимания

PHP исторически был языком, на котором писали быстро и много. Огромная популярность среди новичков, обилие готовых скриптов, низкий порог входа — всё это привело к тому, что миллионы сайтов работали на коде, который никто не проверял на безопасность. Сам язык тоже не стоял на месте: многие функции появлялись в спешке и позже были признаны опасными. В результате уязвимости в PHP и типовых приложениях на нём стали обычным делом.

Важно понимать: проблема не только в ядре языка, но и в том, как его использовали. Многие катастрофы случались из-за небрежности разработчиков, но были и уязвимости, которые напрямую касались стандартной библиотеки или конфигурации по умолчанию. Ниже я собрал самые заметные и разрушительные случаи за последние двадцать лет.

Регистрация глобальных переменных (register_globals)

В начале 2000-х годов настройка register_globals была включена по умолчанию. Она автоматически превращала параметры запроса в глобальные переменные. Это означало, что любой, кто передавал в URL строку вида ?is_admin=1, мог переопределить переменную $is_admin в скрипте. Многие приложения того времени проверяли права доступа именно через такие флаги, и защита рушилась одним запросом.

Уязвимость была настолько распространённой, что стала символом эпохи. В 2002 году настройку признали опасной и постепенно начали отключать по умолчанию, а в PHP 5.4.0 (2012 год) она была удалена полностью. Тем не менее наследие register_globals до сих пор встречается в старых проектах, которые годами не обновлялись.

Magic Quotes и попытка автоматического экранирования

Ещё одна попытка решить проблему безопасности на уровне языка — magic_quotes_gpc. Эта директива автоматически экранировала кавычки в данных, приходящих из запросов. Задумка была благая: защитить разработчиков от SQL-инъекций. Но на практике она создавала иллюзию защиты и порождала ошибки при работе с базами данных, где требовалось двойное экранирование.

Magic Quotes была удалена в PHP 5.4.0 вместе с register_globals. Многие разработчики пострадали от того, что полагались на это автоматическое экранирование и не использовали подготовленные выражения. Когда настройку убрали, старый код оказался уязвим. Этот случай хорошо иллюстрирует, почему безопасность нельзя перекладывать только на язык.

Удалённое включение файлов (RFI) и LFI

Функции include и require — основа модульности в PHP. Но если имя файла бралось из пользовательского ввода без проверки, злоумышленник мог подключить удалённый скрипт. Это называлось Remote File Inclusion (RFI). В начале 2000-х огромное количество сайтов с параметром ?page= в URL стали жертвами массовых атак. Атакующие подставляли URL своего вредоносного скрипта, и сервер выполнял его.

Local File Inclusion (LFI) позволял читать локальные файлы, например /etc/passwd, или выполнять код через загрузку файлов. Многие известные CMS и форумы (phpBB, WordPress, Joomla) страдали от подобных уязвимостей в своих плагинах и темах. Даже сегодня LFI остаётся распространённой проблемой, хотя современные фреймворки стараются минимизировать риск.

Год

Уязвимость

Тип

Влияние

2000

register_globals

Небезопасная конфигурация

Обход аутентификации, выполнение кода

2002

magic_quotes_gpc

Небезопасная конфигурация

Ложное чувство защиты, SQL-инъекции

2004

RFI в phpBB

Удалённое включение файлов

Массовое заражение сайтов

2007

LFI в WordPress

Локальное включение файлов

Чтение файлов, выполнение кода

2012

Удаление register_globals

Изменение языка

Поломка старых приложений

2019

CVE-2019-11043

Переполнение буфера в FPM

Удалённое выполнение кода

временная шкала уязвимостей PHP с 2000 по 2020 год

SQL-инъекции: вечная классика

SQL-инъекции не являются уязвимостью самого PHP, но именно на PHP-проектах они получили колоссальное распространение. Причина проста: долгое время не было удобного способа безопасно передавать параметры в запрос. Функция mysql_query не поддерживала подготовленные выражения, а экранирование через mysql_real_escape_string применялось далеко не всегда.

Массовые взломы сайтов через SQL-инъекции привели к появлению целых ботнетов, которые сканировали интернет в поисках уязвимых страниц. Вспомнить хотя бы эпидемию Asprox или массовые атаки на Joomla в 2015 году. Только с приходом PDO и mysqli с поддержкой prepared statements ситуация начала меняться, но и сегодня инъекции остаются в топе OWASP.

Сериализация и десериализация: скрытая угроза

Функции serialize и unserialize в PHP — мощный инструмент, но их использование с пользовательскими данными может привести к уязвимостям типа PHP Object Injection. Если приложение десериализует данные из cookie или параметра запроса, злоумышленник может подставить специально сформированную строку, которая создаст объект с неожиданными свойствами и вызовет цепочку магических методов, приводящих к выполнению кода или чтению файлов.

Один из самых громких случаев — уязвимость в WordPress 2016 года (CVE-2016-10033), связанная с PHPMailer, где десериализация не была основной причиной, но цепочки POP-объектов в популярных библиотеках встречались регулярно. В 2020-х годах исследователи находили цепочки в Laravel, Symfony и других фреймворках. Разработчикам рекомендуется избегать unserialize для непроверенных данных и использовать json_decode.

Уязвимость

Год

Компонент

Последствия

CVE-2016-10033

2016

PHPMailer

Удалённое выполнение кода через инъекцию в mail()

CVE-2018-5712

2018

PHP GD

Чтение памяти, возможное выполнение кода

CVE-2019-11043

2019

PHP-FPM

Выполнение кода при определённой конфигурации nginx

CVE-2024-4577

2024

PHP на Windows

Удалённое выполнение кода через инъекцию аргументов

схема цепочки POP-объектов при десериализации

Уязвимости в самом ядре PHP

Хотя большинство проблем связано с приложениями, в самом PHP тоже находили серьёзные дыры. Например, CVE-2019-11043 в PHP-FPM позволяла выполнить код на сервере при использовании nginx с определённой конфигурацией. Эта уязвимость получила название PHP-FPM underflow и затронула множество высоконагруженных сайтов. Исправление вышло быстро, но новость наделала шума.

Другой пример — CVE-2015-0235, известная как GHOST, затронула функцию gethostbyname в glibc, но PHP-приложения, использующие эту функцию, также оказались под ударом. В 2024 году обнаружили CVE-2024-4577 — уязвимость в PHP на Windows, связанная с передачей аргументов командной строки. Она напомнила, что даже зрелый язык может преподносить сюрпризы.

Проблема с обработкой загрузки файлов

Загрузка файлов — ещё одна область, где PHP-приложения часто ошибались. Проверка MIME-типа через $_FILES['type'] ненадёжна, потому что этот параметр приходит от клиента. Если приложение доверяет ему и сохраняет файл с расширением .php, злоумышленник может загрузить веб-шелл и получить контроль над сервером. Такие случаи массово происходили в 2010-х годах с популярными CMS и форумами.

Даже сегодня многие забывают отключать выполнение PHP в директориях для загрузок. В конфигурации Apache это делается через php_admin_flag engine off или через .htaccess. В nginx — через location с обработкой только статических файлов. Казалось бы, простое правило, но оно спасло бы тысячи сайтов.

Type juggling и магические хеши

PHP — язык с динамической типизацией, и это порождает неочевидные уязвимости. Сравнение строк с помощью оператора == может привести к неожиданным результатам. Например, выражение '0e12345' == '0e67890' возвращает true, потому что обе строки интерпретируются как числа 0. Это свойство использовали для обхода проверок хешей паролей, если хеш начинался с '0e' и содержал только цифры.

В 2014 году эта проблема стала известна как magic hashes. Многие приложения хранили хеши MD5 и сравнивали их через ==. Злоумышленник мог подобрать пароль, MD5-хеш которого начинался с '0e' и далее шли только цифры, и войти в систему без знания реального пароля. Решение — всегда использовать === или, что правильнее, password_verify().

Инъекции в mail() и заголовки

Функция mail() в PHP позволяет передавать дополнительные заголовки. Если в них попадают пользовательские данные без фильтрации, злоумышленник может добавить собственные заголовки, например Bcc или Content-Type, и использовать сервер для рассылки спама. Эта уязвимость была особенно популярна в контактных формах. В 2016 году похожая проблема в PHPMailer (CVE-2016-10033) привела к удалённому выполнению кода, потому что аргументы передавались в системную команду без должной очистки.

Сегодня рекомендуется использовать проверенные библиотеки для отправки почты, такие как PHPMailer или Symfony Mailer, и всегда обновлять их. Также стоит отключить передачу пользовательских данных в пятый аргумент mail().

Влияние на индустрию и уроки

Все эти уязвимости научили нас нескольким важным вещам. Во-первых, безопасность не может быть «функцией по умолчанию» — её нужно проектировать. Во-вторых, сообщество PHP сделало огромный шаг вперёд: появились PSR-стандарты, фреймворки с встроенной защитой, инструменты статического анализа. В-третьих, обновление PHP — это не прихоть, а необходимость. Многие из описанных проблем исчезли в новых версиях, но старый код продолжает работать на устаревших серверах.

Я часто вижу проекты, которые годами сидят на PHP 5.6 или даже 5.3. Их владельцы считают, что «работает — не трогай». Но именно такие сайты становятся лёгкой добычей. Хайп вокруг новых уязвимостей — хороший повод провести аудит и обновить зависимости.

Современные методы защиты

Сегодня у разработчиков на PHP есть множество инструментов для предотвращения уязвимостей. Подготовленные выражения в PDO и mysqli, экранирование вывода через htmlspecialchars, использование современных фреймворков (Laravel, Symfony), которые берут на себя рутинные проверки. Также важно следить за обновлениями и использовать инструменты вроде PHPStan, Psalm, Composer audit.

  • Всегда используйте подготовленные выражения для запросов к базе данных.

  • Проверяйте и фильтруйте весь пользовательский ввод, но не полагайтесь только на фильтрацию.

  • Экранируйте вывод в HTML через htmlspecialchars с флагами ENT_QUOTES и UTF-8.

  • Отключайте выполнение PHP в директориях для загрузок.

  • Обновляйте PHP и библиотеки, следите за CVE.

  • Используйте строгое сравнение === там, где это важно.

  • Не используйте unserialize для данных из ненадёжных источников.

Эти правила кажутся простыми, но их нарушение до сих пор приводит к инцидентам. Я сам видел, как проект с миллионной аудиторией использовал mysql_query с конкатенацией строк — и это было в 2020 году.

Обратная связь

Запросить аудит безопасности

Нажимая кнопку «Отправить», я даю согласие на обработку моих персональных данных: имени, телефона и email — для обработки заявки и обратной связи.

Заключение

За двадцать лет PHP прошёл путь от опасного новичка до зрелого языка с развитой экосистемой. Уязвимости были и будут, но их количество и влияние снижаются благодаря усилиям сообщества. Хайп вокруг очередной дыры — это не повод отказываться от языка, а возможность проверить свои проекты и сделать их надёжнее. Безопасность — это процесс, а не состояние. И я рад, что за пятнадцать лет моей практики подход к ней изменился к лучшему.

Все статьи