Один баг получил CVE-2025-14177, второй представляет собой heap buffer overflow в iptcembed при копировании входного потока.

В PHP нашли две ошибки в обработке JPEG. Проблемы оказались не во фреймворках и сторонних библиотеках, а в стандартном расширении ext/standard, где реализованы многие встроенные функции. getimagesize и iptcembed обрабатывают пользовательские изображения в C-коде.
Ошибки описал эксперт по безопасности веб-приложений Никита Свешников. Первая проблема связана с раскрытием данных из памяти при чтении JPEG APP-сегментов через getimagesize, вторая с heap buffer overflow при работе iptcembed. Раскрытие данных из памяти получило CVE-2025-14177 и оценку 6,3 балла из 10, а переполнение буфера опубликовано как баг GH-20582 в php-src.
getimagesize определяет размер изображения, тип файла, MIME-тип и строку height="..." width="..." для HTML-тега <img>. Через параметр image_info функция может вернуть данные JPEG APP-сегментов, например APP1/APP13, с EXIF, XMP, IPTC и другими метаданными. Документация PHP отдельно предупреждает, что getimagesize не подходит для проверки подлинности изображения, а расширенные сведения через image_info поддерживаются только для JFIF.
CVE-2025-14177 затрагивает PHP 8.1 до 8.1.34, PHP 8.2 до 8.2.30, PHP 8.3 до 8.3.29, PHP 8.4 до 8.4.16 и PHP 8.5 до 8.5.1. Ошибка проявлялась при чтении изображения несколькими чанками (multi-chunk read), например через php://filter, но позднее стало ясно: фильтр не является обязательным условием для эксплуатации.
Проблема возникала не из-за самого размера APP-сегмента, а из-за сценария, когда PHP читал его несколькими чанками. Буфер под сегмент выделялся заранее, но каждый следующий чанк записывался с начала буфера, а не после уже прочитанных данных. Поэтому часть результата могла оказаться некорректной, а незаполненный хвост $info['APPn'] содержал старые данные из heap-памяти PHP-процесса.
Уязвимый сценарий возникает, когда серверное приложение вызывает getimagesize($filename, $info) для пользовательского файла и затем сохраняет, возвращает, логирует или иначе раскрывает данные из $info['APPn']. В этом случае в ответ приложения могут попасть не только данные APP-сегмента JPEG, но и неинициализированные байты из heap-памяти PHP-процесса.
APP-сегменты JPEG используются для хранения вспомогательных данных: например, APP1 часто содержит EXIF или XMP, а APP13 содержит IPTC-данные и ресурсы Photoshop. В статье для объяснения структуры JPEG используется схема Angel Albertini, после чего разбирается, как PHP читает APP-сегменты и почему ошибка проявилась именно в этой логике.
Проблему исправили в GH-20584. После каждого чтения нового фрагмента указатель внутри буфера теперь сдвигается на уже записанный объем данных, поэтому следующий чанк добавляется в правильную позицию, а не перезаписывает начало буфера. Для проверки добавили регрессионный тест ext/standard/tests/image/gh20584.phpt.
Вторая ошибка затронула iptcembed. PHP заранее выделял spoolbuf по размеру из fstat(), но при копировании входного JPEG-потока не проверял границы буфера, что могло привести к heap buffer overflow.
У FIFO, каналов, сокетов и некоторых устройств st_size может быть нулевым или не отражать реальный объем данных. Обычный файл тоже может вырасти после проверки размера. В результате PHP выделял слишком маленький буфер и продолжал писать за его пределы.
Исследователь воспроизвел переполнение через FIFO: один процесс запускал iptcembed, второй передавал специально собранный JPEG. После SOS-маркера парсер копировал остаток потока до EOF, из-за чего большой хвост данных выходил за границы spoolbuf.
Исправление GH-20591 добавило границу spoolbuf_end и проверку перед записью. Если буфер заканчивается, код больше не пишет за его пределы: функция уходит в ошибку, освобождает память и возвращает false.
Эти ошибки показывают, что встроенные функции PHP тоже могут быть поверхностью атаки, если приложение передает в них пользовательские файлы. Практический вывод: обновить PHP до исправленных версий, не использовать getimagesize как единственную проверку изображений и не раскрывать наружу $info['APPn'] без необходимости.