flawopen.com/Incidents/equifax-struts-ognl-rce-july-2017
Equifax: данные почти 150 млн человек раскрыты
CVE-2017-5638 позволяла внедрять OGNL через заголовок Content-Type на не обновлённом портале Equifax на Struts. Атака длилась около 76 дней и раскрыла данные почти 150 миллионов человек.[1], [4]
Представьте систему доставки, которая копирует отклонённую почтовую этикетку в сообщение об ошибке. Если сообщение воспринимает этикетку как исполняемый шаблон инструкций, отправитель может заставить систему выполнить указания вместо простого сообщения о неверном адресе. Для Struts уже был патч, но уведомление Equifax отправили по устаревшему списку администраторов, а сканирование сети не выявило уязвимость на портале.[1], [4]
Ключевые понятия и термины
OGNL- Object-Graph Navigation Language — язык выражений, которым Struts читает и задаёт свойства Java-объектов. Вычислить текст злоумышленника как OGNL значит выполнить его код.[1], [6]
Парсер multipart Jakarta- Компонент Struts, обрабатывающий загрузку файлов. Отклоняя некорректный Content-Type, он формировал сообщение об ошибке, которое затем вычислялось.[1], [3], [2]
Внедрение выражений (CWE-917)- Недоверенный ввод попадает в выражение, которое вычисляет интерпретатор, и данные становятся инструкциями.[6]
Реестр активов- Учёт того, какое ПО и какой версии работает на каких системах. Без него уведомление о патче невозможно сопоставить с машинами, которым он нужен.[4]
Связи со стандартами
- Ссылка CWE для слабого места, описанного в этом руководстве: CWE-20 CWE-917
- CISA KEV — добавлено 2021-11-03 — CVE-2017-5638
Хронология инцидента
Apache публикует бюллетень безопасности S2-045 и исправленные версии Struts 2.3.32 и 2.5.10.1.[1]
Позднее Equifax сообщила GAO, что отдельное вторжение началось 13 мая и тогда злоумышленники приступили к извлечению персональных данных; ранее уже был несанкционированный доступ, но данные тогда не похищали.[4]
Equifax замечает подозрительный трафик после продления истёкшего сертификата на устройстве проверки трафика.[4]
Equifax публично объявляет об утечке. Позднее GAO сообщает, что были затронуты данные как минимум 145,5 миллиона человек в США и почти миллиона за пределами страны.[4]
Пошаговый сценарий атаки
Подделанный заголовок Content-Type
Злоумышленник отправляет запрос на загрузку, в заголовке Content-Type которого вместо типа содержимого стоит выражение OGNL.[1]
Тихий и долгий вынос данных
Злоумышленники использовали найденные в хранилище учётные данные, чтобы перейти от трёх связанных с порталом баз к ещё 48 не связанных с порталом базам. По данным GAO, они выполнили около 9 000 запросов; атака длилась примерно 76 дней, а просроченный сертификат мешал проверять зашифрованный трафик.[4]
Исходный код: Уязвимый vs Защищённый вариант
Иллюстративный Java-код, а не исходники Struts; настоящее исправление в 2.3.32 и 2.5.10.1 изменило способ формирования ошибки multipart. Защищённая версия использует фиксированный ключ сообщения и передаёт заголовок только как позиционный аргумент, поэтому он форматируется как текст и никогда не становится частью выражения. Кроме того, всё, что не похоже на тип содержимого, заменяется заглушкой, так что даже будущее изменение сообщения не вернёт заголовок в вычисляемую строку.[3], [2], [6]
import com.opensymphony.xwork2.TextProvider;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private final TextProvider text; // resolves messages and evaluates expressions in them
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
// Необработанный заголовок становится частью вычисляемого сообщения.
return text.getText("Invalid content type: " + contentType);
}
}import com.opensymphony.xwork2.TextProvider;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
public final class UploadErrors {
private static final Pattern MEDIA_TYPE = Pattern.compile(
"^[a-z]+/[a-z0-9.+-]+(;\\s*[a-z0-9-]+=[a-z0-9._-]+)*$");
private final TextProvider text;
public UploadErrors(TextProvider text) {
this.text = text;
}
public String describe(HttpServletRequest request) {
String contentType = request.getHeader("Content-Type");
boolean wellFormed = contentType != null && contentType.length() <= 256
&& MEDIA_TYPE.matcher(contentType.toLowerCase(Locale.ROOT)).matches();
// Используйте фиксированный ключ сообщения; заголовок передаётся только как позиционный аргумент,
// и только если он имеет формат типа содержимого.
return text.getText("upload.invalid.content.type",
new String[] { wellFormed ? contentType : "(malformed)" });
}
}Чек-лист по защите системы для инженеров
- ✓Ведите SBOM для каждого развёрнутого приложения и умейте за несколько часов после бюллетеня вроде S2-045 ответить на вопрос «где у нас работает Struts 2.3.x?».[4]
- ✓Сверяйте цели сканера уязвимостей с реестром активов: чистый результат сканирования хоста, который не входил в область проверки, не доказывает, что патч установлен.[4]
- ✓Поднимайте тревогу по истечению сертификатов на устройствах TLS-инспекции и мониторинга не менее чем за 30 дней; здесь истёкший сертификат скрыл месяцы утечки.[4]
- ✓На ревью кода отклоняйте любой вызов, передающий склеенный недоверенный текст в резолвер сообщений, шаблонизатор или вычислитель выражений; передавайте его аргументом.[6]
- ✓Отделяйте базы данных от приложений, доступных из интернета, убирайте учётные данные из файлов приложений и поднимайте тревогу при необычном объёме запросов от учётных записей приложений.[4]
Источники
- Apache Struts — бюллетень безопасности S2-045 (CVE-2017-5638)
- Apache Struts — multipart parser error-message fix (commit 3523064)
- Apache Struts — upstream multipart error-handling fix (commit 6b8272c)
- GAO США — Защита данных: меры Equifax и федеральных ведомств после утечки 2017 года (GAO-18-559)
- CVE-2017-5638 — запись CVE
- MITRE CWE-917 — внедрение в язык выражений
- Log4Shell: как запись в журнал стала удалённым выполнением кода
- Внедрение команд — полный справочник по классу