Solar inCode умеет обнаруживать уязвимости в байткоде Java. Но показать инструкцию байткода, которая содержит уязвимость, мало. Как показать уязвимость в исходном коде, которого нет?
На практике при использовании инструмента поиска уязвимостей к каждой найденной уязвимости нужно применить одно из трех действий:
- устранить;
- принять риски;
- доказать, что это не уязвимость (false positive).
Все три действия требуют некоторого анализа уязвимости (например, нужно понять, как ее устранить и какие риски она несет).
Как проводить такой анализ, если мы находим уязвимости в байткоде Java, а исходного кода у нас нет?
Основным методом поиска уязвимостей при создании inCode был выбран статический анализ — поиск уязвимостей без выполнения кода. При статическом анализе нужно:
- построить модель кода (промежуточное представление);
- дополнить модель информацией о данных с применением алгоритмов статического анализа (анализа потока данных, потока управления — dataflow analysis, taint analysis);
- применить правила поиска уязвимостей (правила говорят, где в модели кода находятся уязвимости, в терминах этой модели и информации, которой она дополнена).
Находя уязвимости в промежуточном представлении, необходимо их отображать в терминах исходного кода для дальнейшего анализа.
Первыми приложениями, в которых Solar inCode искал уязвимости, были Android- и Java-приложения. Поиск уязвимостей в исполняемых файлах достаточно востребован:
- заказчику по условиям контракта могут не передавать исходный код;
- даже если исходный код передали, на боевой стенд (или в Google Play) может уйти исполняемый код, не соответствующий переданному исходному;
- разработчики используют сторонние компоненты без исходного кода, такой код также нужно контролировать.
Поэтому для Android- и Java-приложений в качестве промежуточного представления для статического анализа мы выбрали байткод Java. После компиляции исходного кода мобильного приложения в байткод Java компилятор Dalvik объединяет class-файлы и перекомпилирует код в байткод для Dalvik, получая исполняемый dex-файл. Исполняемый файл вместе с ресурсами и файлом конфигурации упаковывается в пакет apk, который распространяется через Google Play. Существуют средства, реализующие обработку и преобразования пакетов apk: распаковку, расшифровку ресурсов и файлов конфигурации, трансляцию кода Dalvik в байткод Java (apktool, dex2jar).
Байткод также можно получить из исходного кода путем компиляции (так мы делаем при анализе исходного кода Java и Scala). Таким образом, байткод Java хорошо подходит в качестве единого внутреннего представления при анализе исходного и исполняемого кода Java и Android-приложений (на самом деле, также можно проводить анализ всех языков, компилирующихся в байткод Java).
Байткод Java можно декомпилировать, при этом получить код достаточно высокого качества. Есть множество декомпиляторов для Java (JD, fernflower, Procyon). Мы не стали использовать восстановленный Java-код в качестве промежуточного представления, поскольку любые средства декомпиляции допускают ошибки, что может сказаться на качестве поиска уязвимостей.
Итак, мы нашли уязвимости в байткоде Java (о том, как это делается, мы будем писать в следующих статьях). Что делать с результатами?
Мы должны показать их в терминах «исходного» кода, точнее, восстановленного кода высокого уровня. Под уязвимостью здесь мы понимаем набор позиций инструкций в байткоде, которые определяют уязвимость (небезопасный вызов метода, набор инструкций, через который проходит поток небезопасных данных). Таким образом, каждой инструкции в байткоде мы должны сопоставить номер строки в восстановленном коде. В class-файле (файл байткода, соответствующий классу в исходном коде) существует атрибут LineNumberTable, в котором хранится отображение позиций в байткоде на номера строк в исходном коде. Таким образом, для отображения уязвимостей в терминах языка Java нужно, чтобы в байткоде был атрибут LineNumberTable.
При анализе байткода (в том числе полученного из apk-файла) атрибута LineNumberTable может не оказаться. Он может удаляться при компиляции или при обратной трансляции из apk. Хотя это и не так важно — удаленный из байткода LineNumberTable соответствовал исходному коду, который написал разработчик, а не восстановленному «исходному» коду. Это означает, что необходимо восстановить атрибут LineNumberTable в анализируемом байткоде, который будет указывать на восстановленный код.
Базовый алгоритм основывается на построении абстрактного синтаксического дерева декомпилируемого кода (AST) по байткоду Java и выводе исходного кода в процессе обхода AST. В процессе обхода AST (обход в глубину) также происходит сохранение информации о соответствии номеров строк восстанавливаемого кода и позиций инструкций в методах байткода.
В каждом узле дерева мы знаем позицию инструкции в байткоде относительно начала текущего метода и номер строки в файле восстанавливаемого кода. Поэтому при обходе также сохраняются границы методов в восстанавливаемом коде для последующей фильтрации пар «позиция в байткоде метода»-«номер строки в файле».
Отдельно обрабатываются анонимные классы, поскольку они порождают вложенные друг в друга методы. Для этого по окончании обхода происходит анализ вложенности интервалов позиций методов в исходном коде.
На практике мы часто получаем проекты, содержащие и исходный код (тогда мы можем получить байткод с таблицей номеров строк путем компиляции), и байткод (различные сторонние компоненты, библиотеки и так далее). Для анализа таких проектов в inCode реализована комбинированная предобработка проекта на Java. Она состоит из следующих шагов:
- в проекте обнаруживаются все class-файлы, файлы с исходным кодом на java и scala, jar/war файлы с байткодом;
- в зависимости от настройки сканирования проекта, которую задает пользователь, в список class-файлов включаются class-файлы из jar/war-файлов (чаще всего это означает, что проект анализируется вместе с библиотеками) ;
- из class-файлов и файлов исходного кода мы получаем полные имена классов, с помощью имен классов происходит сопоставление файлов исходного кода и файлов с байткодом;
- файлы байткода, для которых не нашлись файлы исходного кода, подвергаются декомпиляции с описанной выше процедурой восстановления информации о номерах строк.
При такой предобработке учитываются анонимные и вложенные классы — один файл исходного кода может соответствовать нескольким файлам байткода.
В результате у нас для каждого файла байткода есть файл с Java-кодом (или восстановленным, или исходным) и таблица номеров строк, связывающая его с этим файлом.
С помощью описанных в статье процедур и алгоритмов любую уязвимость, найденную в Java- или Android-приложении, inCode отображает на исходный код, вне зависимости от того, был ли он передан на анализ.
Похожий подход применяется при анализе бинарных файлов iOS-приложений, однако там все гораздо сложнее: задача декомпиляции бинарного кода архитектуры ARM является гораздо менее исследованной. Этой теме будут посвящены дальнейшие публикации.
Комментарии (7)
yaleksar
01.11.2016 12:36Речь идет о программных конструкциях, которые могут нарушить информационную безопасность приложений. Класс уязвимостей ограничен только движком анализа и правилами поиска.
У нас обнаруживаются уязвимости типа «внедрение» (SQL Injection, XSS, Path traversal и так далее), вызовы небезопасных функций (например, хеширования и шифрования), другие шаблонные небезопасные конструкции (например, отключение проверки сертификатов с помощью X509TrustManager), участки, подозрительные на НДВ (или закладки) — всего более 150 различных уязвимостей для языка Java.vilgeforce
01.11.2016 12:40В чем небезопасность функций хэширования или шифрования?
yaleksar
01.11.2016 12:44Я не совсем точно выразился в предыдущем комментарии. Уязвимостью является использование небезопасных функций хеширования и шифрования (типа MD5 или DES) для работы с критичными данными (конфиденциальными данными, для генерации идентифицирующих значений).
vilgeforce
01.11.2016 12:46+3При этом, я уверен, у вас не отслеживается использование самописных кривых алгоритмов генерации ключей, хэширования или шифрования. И сразу виден сценарий: меняем относительно стойкий DES на XOR строкой из 2 байт и предупреждение уходит, значит код нормальный :-)
yaleksar
01.11.2016 13:02+1Реализация самописных алгоритмов может отслеживаться по побочным эффектам — например, использованию результатов работы таких алгоритмов. На практике скорее используют небезопасные стандартные алгоритмы, чем самописные, поэтому большая часть таких уязвимостей обнаруживается.
Безусловно, инструменты автоматического анализа кода — как статического, так и динамического — не могут выявить абсолютно всех проблем, особенно связанных с некоторыми логическими уязвимостями.
Для такого глубоко анализа лучше применять ручной аудит кода :)
vilgeforce
01.11.2016 13:03+2Я, можно сказать, периодически наблюдаю как вместо стандартных небезопасных начинают городить самописное… Такие лапочки прямо! :-)
Regis
Не совсем понял о поиске какого класса уязвимостей в статье идет речь.