Сегодня мы в очередной раз рассмотрим ошибки в сложных программных вещах, но это будут уже не баги в коде. Мы поговорим об ошибках, заложенных еще на стадии проектирования, то есть об архитектурных багах. Ну и конечно же, тебя ждут 0day-уязвимости...

 

Ошибка в логике

Ошибка, как известно, может быть в коде ПО, а может в конфигурации. Кроме того, возможны ошибки при неправильном внедрении или сопровождении системы. Но бывают оплошности и в самой архитектуре. Такие ошибки прячутся в самой задумке, в самой логике системы. Последствия могут быть разными, в том числе и такие, которые приводят к нарушению безопасности. Примеров таких косяков — пруд пруди: DLL-Hijacking, ARP-POISONING, SMB RELAY... А также менее глобальные — например, ошибки в модулях libc, при обработке переменной окружения LD_AUDIT (автор Тавис Орманди). В общем, зоопарк достаточно богат и многообразен. В этой статье я расскажу о своем опыте по выявлению и эксплуатации таких вот багов.

 

Байки из склепа

В России есть крупные компании. Крупным компаниям надо как-то автоматизировать некоторые процессы. Для этого нужно ПО. Такое ПО пишут программисты. При этом программисты решают задачу, которую ставит им компания. И на стадии проектирования таких систем случаются казусы, которые приводят к достаточно прикольным последствиям. Удивительно, что только уже после написания ПО возникает вопрос: а что у нас с безопасностью? Печально, но в России это так.

Пример из жизни — ответ технического директора крупной компании, занимающейся разработкой софта, на сообщение об обнаруженной уязвимости. Уязвимость банальная, типа «Переполнение буфера в стеке», последствия — выполнение произвольного кода.

Так вот, ответ сотрудника компании, отвечающего за безопасность этого софта: «Это бред. Как может выполниться произвольный код? Откуда он возьмется в нашем ПО? Если и выполнится код, то только наш собственный, а он не произвольный! К тому же, у наших клиентов антивирус есть!» Пересказ вольный, но смысл передан точно. Так-то вот.

Но что-то я отвлекся — вернемся к ошибкам в логике. Собственно, любой анализ безопасности системы/проекта начинается крайне банально — запускается сниффер. Причем на клиентской тачке.

Это необходимо, чтобы понять, как система работает с сервером. В 90% случаев уязвимости в логике выявляются тупо при анализе логов этого самого сниффера. К примеру, всем очевидно, что при любой модели клиент-сервер желательно разграничивать работу СУБД с клиентом. Но многие наши разработчики ленятся писать — например, систему управления бизнесом, на основе трехзвенной архитектуры. Напоминаю, что при таком раскладе у пользователя стоит клиентское ПО, которое работает с сервером приложений, а сервер, в свою очередь, работает с данными в СУБД. В итоге надо писать два продукта — клиент и сервер приложений, а кроме того еще разрабатывать БД (хранимые процедуры, триггеры, да и вообще схему). Как итог — многим лениво писать сервер приложений, и они разрабатывают клиентское ПО, которое напрямую работает с СУБД. Такая двухзвенная модель накладывает ряд дополнительных требований к безопасности на уровне логики и разграничения доступа.

Однажды, разбирая логи сниффера, мы увидели такую реализацию. Клиент выполняет аутентификацию с СУБД на основе NTLM, то есть доступ в БД был организован по учетным записям домена.

Далее клиентское ПО выбирало роль, соответствующую своей учетной записи, в специальной таблице. А перед тем, как выбирать данные из любой боевой таблицы, проверяло, есть ли у данной роли права на эти данные. Очевидный косяк тут — проверка роли на стороне клиента, ведь что мешает пользователю выполнить соединение с СУБД с помощью клиента этой самой СУБД? Так как ролевая модель «поддельная», а не основана на возможностях СУБД, то такой расклад приводит к полному доступу к системе и всем данным. Самое забавное, что выполняя анализ безопасности другой отечественной системы, в другой компании, мы обнаружили точно такой же подход к архитектуре и, соответственно, ошибка была абсолютно такая же.

Естественно про это «и так знают» и выходят из положения, например, с помощью установки терминальных серверов, с которых пользователи и работают с системой. Но хочу заметить, что такой подход — не панацея, а просто «костыль». Другие же разработчики, зная о подобной проблеме, реализовали ограничение доступа, используя ролевую модель самой СУБД. Они отказались от доменных учеток, заводя учетные записи в базе данных. Внешне это выглядит так: пользователь запускает клиентское ПО, ждет секунду, получает список пользователей, ищет в списке свою фамилию, жмет кнопку <Войти>, после чего у него спрашивают пароль, который он вводит.

Сниф трафика показал удивительную вещь: действительно, пароль проверяется на уровне СУБД специально разработанной процедурой, ролевая модель прописана там же, клиент ничего не решает, но шаг с отображением списка пользователей был провален полностью. Клиентское ПО коннектится к БД под учетной записью по умолчанию, которая прошита в памяти (легко можно достать дебаггером). Далее идет селект списка пользователей, но тут есть нюанс: по идее это должно было быть реальзовано так:

select logins, FIO from db;

Но, по неизвестным причинам, было сделано так:

select * from db;

Что позволяло получить пароли всех пользователей при отображении их списка. Естественно, пароли в клиентском ПО не отображались, но в логах сниффера они были видны вполне четко. И даже если бы этого селекта не было, то таблица db была доступна пользователю по умолчанию, так что пароли можно было получить, используя прошитую учетку. Зачем программисты используют свои навороты, вместо того, чтобы пользоваться уже проверенными механизмами? Им виднее...

 

OpenEdge

Ладно, хватит историй, перейдем к делу. Сейчас я расскажу об идиотской архитектурной ошибке в известном в узких кругах продукте RDBMS Progress OpenEdge. Название кажется тебе незнакомым?

Вот лишь малый список компаний, которые используют эту СУБД:

  • PepsiCo
  • Coca-Cola
  • Johnson & Johnson
  • Lockheed Martin
  • McDonnell-Douglas
  • Sony
  • Danon
  • Mercedes-Benz
  • Ford Motor
  • Mazda Motor Corporation
  • Heineken
  • ...

Так что, как видишь, эта штуковина очень мощная и дорогая, используется в крутых компаниях :). Там мы ее и нашли. Вернее, нашел ее sh2kerr и наш коллега (теперь уже работающий в Yandex) Алексей Трошичев. С помощью одного лишь сниффера на клиентской части они заметили одну деталь: от клиента к серверу не передается ничего, что напоминало бы хэш пароля или сам пароль во время аутентификации. Сначала sh2kerr заметил, что вроде вот он — хэш. Но... он передавался от сервера к клиенту! Это озадачило наших героев. Понять, что происходит, было трудно, но штукер заподозрил, что тут-то и зарыт ключ к разгадке. Однако времени на дебаг не было, так как он находился на объекте и должен был делать работу дальше. Зато автор статьи на объекте не был и располагал временем.

От штукера поступил телефонный звонок с описанием проблемы: «Неужели хэш от пароля посылается клиенту? И если да, то что же клиент делает с этим хэшем?». Я подумал, что он бредит. Какой разработчик напишет такую ерунду? Тем не менее, раз есть задача, то надо ее решать. Я скачал триал СУБД OpenEdge, установил, настроил и начал копать. Первым делом я запустил сниффер и провел аутентификацию. Слова Саши подтвердились — от клиента ничего дельного не идет, зато от сервера идет какаято ботва, похожая на код, пароль, шифр или хэш.

Проверить этот факт не составило труда. Сделав дамп таблицы _Users, я увидел, что напротив логина Admin, созданного мной, стоит точно такая же строка как и в TCP-пакете от сервера к клиенту. Опять интуиция штукера не подвела — это был хэш. Но какого... почему от сервера? Что делает клиент? На эти вопросы помог ответить OllyDbg.

 

What the heck?

Тут я расскажу подробнее, так как это может быть любопытно. Берем OllyDbg или ImmunityDebugger и аттачимся к клиентскому процессу OpenEdge — prowin32.exe. Так как хэш приходит от сервера, то требуется найти место разбора входящего трафика. Для начала надо поставить брейкпоинт на функцию recv(), так как, видимо, с ее помощью ПО получает данные. Известно, что эта функция прячется в библиотеке WS2_32.dll, но на всякий случай сдела ем поиск всех функций. Оля делает это так: правой кнопкой по полю с дизасм-кодом, далее «Search for -> Name in all modules».

В открывшемся окне несмотря на отсутствие поля ввода пишем «recv» и видим, что еще функция «recv» берется из WSOCK32.dll.
Ставим брейкпоинты на оба вызова. Жмем <F9> (Run) и пытаемся войти в СУБД. При этом у нас работает брейкпоинт на функции recv.

Ок, смотрим, откуда пришел вызов функции (пришел он из модуля prow32.dll) и ставим там брейкпоинт. Тогда в следующий раз перед вызовом recv у нас сработает брейкпоинт в prow32.dll, и мы сможем отследить входящие данные по <F8>, так сказать, на глаз, перепрыгивая работу recv (ESP+4 будет указывать на буфер, куда помещаются входные данные от сервера). Жмякаем кнопочки <F8> и <F9>, пока не увидим хэш в данном буфере. Далее нам интересно, что будет с этой строкой: выделяем ее и ставим брейкпоинт на чтение.

Жмем <F9> и видим, что срабатывает брейкпоинт на память в процессе memmove, то есть хэш меняет место, так что снимаем старый брейкпоинт и ставим его в другом месте, выделяя область с хэшем после перемещения. Так повторяем еще пару раз и в итоге видим, что на третий раз брейкпоинт у нас не на копировании, а на сравнении первого байта нашей строки:

CMP AL,BYTE PTR DS:[ECX]

В ECX у нас строка хэша из сети, в AL байт, который сравнивается с первым байтом хэша. Отмотав пару строчек в дизасме, видим:

MOV AL,BYTE PTR DS:[EDX]

То есть в AL первый байт строки по адресу EDX, а там у нас какойто другой хэш (можно предположить, а потом и убедиться в дебаггере, что этот второй хэш считается локально и берется из поля ввода пароля). Далее, если сравнение успешно, то ECX и EDX увеличиваются на единицу, и процедура сравнения повторяется.

Так реализована местная функция из prow32.dll — dbut_stcomp(). Данный кусок кода сравнивает две строки, в нашем конкретном случае — два хэша. И если все совпало, то возвращается 0. Если нет — то номер байта, где произошел косяк. Идем по коду далее, следя за EAX после выхода из функции dbut_stcomp. И тут мы видим, что EAX делают отрицательным, а затем после RETN происходит сравнение EAX с нулем и, в зависимости от результата, разная обработка.

TEST EAX,EAX
JE SHORT prow32.1024653F
MOV ECX,DWORD PTR DS:[106D1FF4]
MOV EDX,DWORD PTR DS:[ECX+B0]
PUSH EDX
PUSH 2C6
CALL prow32.10026CA0 ; ошибка аутентификации

Если ноль — то прыжок, если нет — то по <F9> видим ошибку аутентификации. Поставив на сравнение брейкпоинт и убрав остальные, еще раз пытаемся выполнить аутентификацию, только после сравнения EAX с нулем меняем JE на JNE. Таким образом, неважно, чем закончилось сравнение, мы идем сюда: prow32.1024653F. После нажатия <F9> ничего не произошло: никаких сообщений об ошибке, только открылся интерфейс клиента СУБД. При этом я мог менять и читать данные в БД с помощью этого интерфейса. Сравнение проделанных шагов с данными из сниффера показали, что сервер шлет хэш выбранного юзера клиенту, клиент считывает и сравнивает хэши, после чего шлет результат серверу...

Другими словами, это как если бы был такой диалог:

Клиент: Привет, сервак, как жизнь? Как дети? Форкаешься еще?
Сервер: Ну привет, клиентик. Ты сам-то чьих будешь?
Клиент: Ну как же это? Админ я, Админ!
Сервер: Ох... ну если ты Админ, то хэш твоего пароля XXX! Ты уверен, что у твоего пароля такой хэш?
Клиент: Ну ты скажешь! Конечно! У меня такой же хэш и получился! Админ я или кто, по-твоему!
Сервер: Ну раз Админ, то проходи!

Вот и все. Самая бредовая бага, которую я когда-либо видел. Патчим клиентскую DLL'ку и пароль нам больше не нужен :). Как разработчик до такого додумался — пес его знает. Кстати, если пользователя не существует, то получится такой расклад:

Клиент: Привет, сервак, как жизнь? Как дети? Форкаешься еще?
Сервер: Ну привет, клиентик. Ты сам-то чьих будешь?
Клиент: Ну как же это? 0ткТе4нзН3рх я, 0ткТе4нзН3рх!
Сервер: Эээ... Чувак, слышь, не знаю я никакого 0ткТе4нзН3рха...
Клиент: Ну ты скажешь! У меня вон и хэш совпал! 0ткТе4нзН3рх я...
Сервер: Ну раз 0ткТе4нзН3рх, то проходи...!

Мы связались с разработчиком и очень долго ждали, что он решит. Решил он просто: да, это баг, но только для тех, кто использует СУБД-аутентификацию. Все крутые чуваки ее не должны использовать. А еще лучше используйте n-звенную архитектуру. Такой ответ понятен: баг невозможно исправить хотя бы по причине обратной совместимости. Но факт есть факт.

 

Kaspersky AV 1000day

Говоря об архитектурных багах, надо понимать, что иногда они зависят от множества факторов. Так эта глава расскажет об ошибке во всеми любимом антивирусе имени Касперского.

Ошибка приводит к удаленному исполнению произвольного кода, причем никаких эксплойтов писать не надо, да и вообще ничего делать не надо — все уже написано и сделано. Значит, делал я с Алексеем Тюриным aka GreenDog (ну и ники у людей...) внутренний пентест одной компании, и все бы ничего, но никак их не сломать: пароли сменены, ОС пропатчены — что же делать? У меня случайно был включен сниффер в Cain. Пока Леха ломал Oracle TNS (что, кстати, тоже закончилось успехом), я открыл Кайн — в закладке «Passwords -> SMB» кроме моего прочего трэша была одна строчка, в которой говорилось, что некий сервер X пытался выполнить NTLM-аутентификацию с моей рабочкой, используя доменную учетку какого-то пользователя.

В имени учетки явно прослеживалось слово «Kasper». Сниффер работал четыре часа, и лишь один раз проскочила такая штука (мне еще повезло, что протокол был SMB, а не SMB2, так как Cain пока не умеет парсить новый протокол). Так что же это за ерунда? С творением Евгения Касперского я не очень знаком, однако Google помог разобраться. Итак, очевидно, что есть некий центральный сервер Каспера — это знают все.

На остальных тачках стоит «клиентская часть», которая управляется центральной. Раз что-то пыталось пролезть ко мне с сервака, где стоит Сервер Касперского, то очевидно, что там есть некий функционал опроса сети. Погуглив чутка, было выявлено, что такой функционал и правда есть. В Kaspersky Administration Kit 6/8 есть фича «Опрос IP-подсети». Написано, что эта фигня ищет новые компы в сети с помощью ICMP-пинга. После этого Каспер пытается зайти на него по SMB, чтобы найти там своего агента и, не найдя его, комп добавляется в список нераспределенных хостов.

Так как разрабатывать свой протокол очень лень, то логично использовать NTLM-аутентификацию. Поняв все это, я предположил, что раз речь идет о Каспере, то учетка должна обладать неслабыми правами. На следующее утро я включил smb_relay-модуль из состава Metasploit, натравил его на сервер Y из сети компании и стал ждать. Через четыре часа у меня был шелл на сервере Y с учетной записью NT AUTHORITY/SYSTEM.

 

Итак, что же произошло?

Исходные данные:

  1. Сеть на основе Microsoft Active Directory.
  2. Сервер Касперского с Administration Kit.
  3. В Administration Kit установлена опция «Сканирование IP-сети».

По умолчанию сканирование происходит раз в семь часов. Так как серваку надо управлять всеми тачками с антивирусами, то там везде должна быть учетка, причем с правами локального админа. Каспер пытается залезть этой учеткой куда попало, даже на неизвестные компы в сети, например на наш хост с BackTrack, где запущен smb_relay. В этот момент мы перехватываем NTLM-запрос и перенаправляем на любой сервер или рабочку с Касперским (кроме сервера X, откуда Каспер сканит).

Другими словами, мы пытаемся выполнить аутентификацию на серваке Y от имени Каспера. Y ответит нам NTLM-Response пакетом, который мы передадим серваку Каспера — X. ОС на X подумает, что все ОК и продолжит аутентификацию согласно ответу. Модуль smb_relay также будет пересылать пакеты Y от имени Каспера, что в итоге закончится тем, что сервак Y проаутентифицирует нас под сервисной доменной учеткой, которая имеет права локального админа. Данных прав достаточно, чтобы проинсталлить и запустить, например, meterpreter. Таким образом выполнено проникновение и сервер Y скомпрометирован (вспоминая предыдущую статью, можно прикинуть, что одного такого проникновения вполне бывает достаточно, чтобы захватить весь домен).

Эксплойт работает для любой версии Windows, так что это достаточно простой и универсальный метод пробивания серваков (учитывая популярность в корпоративных сетях Каспера). Архитектор просто забыл про особенность протокола NTLM и его недостатки. Дал слишком большие права пользователю и везде пытается проаутентифицироваться под его именем. Простая архитектура опроса IP-подсети + недостаточно надежный метод аутентификации (NTLM) сделали свое дело.

 

И что?

В данной статье я хотел обратить внимание на то, как важно уделять внимание мелочам и деталям на этапе построения идеи проекта и его архитектуры. Ошибки в логике очень просто эксплуатировать и сложно исправлять. Кроме того, если ты обратил внимание, то все перечисленные тут ошибки были обнаружены ручным анализом с помощью простейшего сетевого сниффера.

Если поиск ошибок в коде хоть как-то можно автоматизировать, то поиск логических ошибок — нельзя, это ручная работа. При этом, как было сказано, найти их несложно — внимательность + знание о том, как это работает, всегда поможет понять угрозу. Будь внимателен и may the force be with you...

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии