30 декабря 2024 года Министерство финансов США объявило, что спонсируемые государством китайские хакеры, по-видимому, взломали системы департамента. Злоумышленники проникли через инструмент под названием BeyondTrust, программное обеспечение для удаленной поддержки, и в их базе данных была ошибка, которая годами ждала своего обнаружения.
5 декабря 2024 года BeyondTrust, компания, предоставляющая программное обеспечение для удаленной поддержки Министерству финансов США, обнаружила что-то необычное в своих системах. 3 дня спустя, 8 декабря, BeyondTrust пришлось сделать трудный звонок в Министерство финансов США, чтобы сообщить им о взломе их системы.
Когда новость распространилась, люди начали копать немного глубже. Когда исследователи безопасности из Rapid7 начали анализировать эти патчи, они обнаружили нечто неожиданное: даже при правильном экранировании, даже при соблюдении всех правил безопасности, все равно был путь внутрь.
На протяжении десятилетий разработчики следовали одному простому правилу для предотвращения всех видов атак путем внедрения SQL-кода: всегда экранируйте свои входные данные. Каждый раз, когда вы принимаете пользовательский ввод, который должен попасть в запрос к базе данных, вы экранируете все специальные символы, такие как кавычки, чтобы сделать их безопасными.
Рассмотрим этот простой пример запроса плагина. Здесь я пытаюсь запросить пользователя в базе данных с таким именем пользователя и паролем.
SELECT * FROM users WHERE username = 'pwn' AND password = 'I'm Batman';
Но если я введу этот пароль вместо "I'm Batman", наш запрос будет выглядеть примерно так:
SELECT * FROM users WHERE username = 'pwn' AND password = '' OR 1=1';
Теперь эта одинарная кавычка вырывается из проверки пароля, и поскольку 1 всегда равно 1, и этот запрос не может быть ложным, вы теперь вошли в систему как пользователь pwn без использования настоящего пароля.
Именно поэтому нельзя просто копировать пользовательский ввод в запросы к базе данных. Нам всегда нужно экранировать специальные символы, такие как кавычки, просто чтобы сделать их безопасными. И в случае с BeyondTrust они действительно удаляли специальные символы, чтобы сделать свой запрос более безопасным. Но как, черт возьми, злоумышленникам все же удалось проникнуть внутрь?
Ответ заключается всего в двух байтах: 0xc0 0x27
. Довольно просто, не так ли? Как эти, казалось бы, безобидные байты несут ответственность за все это? Чтобы понять это лучше, нам нужно копнуть немного глубже в исходный код базы данных PostgreSQL.
BeyondTrust использовала функцию под названием PG_Escape_string
, чтобы безопасно экранировать любые специальные символы, которые есть в пользовательском вводе. Вы передаете свои входные данные через эту функцию, и она обрабатывает все экранирование за вас.
Но взгляд под поверхность раскрывает тревожную реальность: когда вы вызываете PG_Escape_string
, она не просто выполняет экранирование сама, вместо этого она делает вызов к родной функции PostgreSQL.
Теперь давайте посмотрим на основную функцию, которая определяет, сколько байтов составляет символ. Это делается с помощью функции pg_utf_mblen
. Практически любой текст, требующий экранирования, проходит через эту функцию.
Например, буква "a" - это всего один байт, а для представления эмодзи может потребоваться несколько байтов. Базе данных PostgreSQL необходимо правильно обрабатывать это при экранировании строк.
Ключевой момент: если первый байт равен 0xc0
, она решит, что это двухбайтовый символ, и установит длину в два. Эта переменная длины используется в следующем коде.
len = pg_utf_mblen(source);
memcpy(target, source, len);
Этот код копирует все байты из источника в целевой буфер, полагаясь на то, что длина, полученная от функции pg_utf_mblen
, верна. Он никогда не проверяет, являются ли эти байты действительно допустимыми символами UTF-8.
Что, если мы установим второй байт в одинарную кавычку 0x27
? Первый байт 0xc0
сообщает: "Я начало двухбайтовой последовательности символов", и база данных PostgreSQL посмотрит на него и скажет: "Хорошо, я просто скопирую следующий байт как есть". Она копирует оба байта: 0xc0
и нашу хитроумную одинарную кавычку, не экранируя ее.
Как злоумышленники использовали эту ошибку? Программное обеспечение для удаленной поддержки BeyondTrust имело компонент, который принимал параметр в качестве пользовательского ввода и отправлял его в PHP-скрипт. Скрипт пытался правильно экранировать все опасные символы с помощью функции PG_Escape_string
, но мы только что узнали, как обмануть эту функцию, заставив ее выдавать небезопасные SQL-запросы с нашими двумя магическими байтами.
Эта небезопасная строка передается в программу под названием psql
, которая является интерфейсом командной строки для базы данных PostgreSQL. Но у нее есть функция, которая может выполнять системные команды.
К 14 декабря BeyondTrust исправила свои облачные среды, а через 2 дня они объявили об уязвимости с исправлениями для всех клиентов. Истинный масштаб этой уязвимости стал намного яснее в январе 2025 года, когда анализ Rapid7 выявил базовую ошибку PostgreSQL, которая потенциально затронула миллионы других систем по всему миру.
Исправление для продуктов BeyondTrust было довольно простым: BeyondTrust добавила простую проверку, которая разрешала только буквы и цифры в качестве пользовательского ввода, чтобы не было специальных символов или сложных байтов.
Со стороны PostgreSQL также были выпущены исправления для всех поддерживаемых версий.