Поиск уязвимостей в байткоде Java: что делать с результатами?


    Solar inCode умеет обнаруживать уязвимости в байткоде Java. Но показать инструкцию байткода, которая содержит уязвимость, мало. Как показать уязвимость в исходном коде, которого нет?

    На практике при использовании инструмента поиска уязвимостей к каждой найденной уязвимости нужно применить одно из трех действий:

    • устранить;
    • принять риски;
    • доказать, что это не уязвимость (false positive).

    Все три действия требуют некоторого анализа уязвимости (например, нужно понять, как ее устранить и какие риски она несет).

    Как проводить такой анализ, если мы находим уязвимости в байткоде Java, а исходного кода у нас нет?

    Основным методом поиска уязвимостей при создании inCode был выбран статический анализ — поиск уязвимостей без выполнения кода. При статическом анализе нужно:

    • построить модель кода (промежуточное представление);
    • дополнить модель информацией о данных с применением алгоритмов статического анализа (анализа потока данных, потока управления — dataflow analysis, taint analysis);
    • применить правила поиска уязвимостей (правила говорят, где в модели кода находятся уязвимости, в терминах этой модели и информации, которой она дополнена).

    Находя уязвимости в промежуточном представлении, необходимо их отображать в терминах исходного кода для дальнейшего анализа.

    Первыми приложениями, в которых Solar inCode искал уязвимости, были Android- и Java-приложения. Поиск уязвимостей в исполняемых файлах достаточно востребован:

    1. заказчику по условиям контракта могут не передавать исходный код;
    2. даже если исходный код передали, на боевой стенд (или в Google Play) может уйти исполняемый код, не соответствующий переданному исходному;
    3. разработчики используют сторонние компоненты без исходного кода, такой код также нужно контролировать.

    Поэтому для 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

      +3
      Не совсем понял о поиске какого класса уязвимостей в статье идет речь.
        0
        Речь идет о программных конструкциях, которые могут нарушить информационную безопасность приложений. Класс уязвимостей ограничен только движком анализа и правилами поиска.

        У нас обнаруживаются уязвимости типа «внедрение» (SQL Injection, XSS, Path traversal и так далее), вызовы небезопасных функций (например, хеширования и шифрования), другие шаблонные небезопасные конструкции (например, отключение проверки сертификатов с помощью X509TrustManager), участки, подозрительные на НДВ (или закладки) — всего более 150 различных уязвимостей для языка Java.
          0
          В чем небезопасность функций хэширования или шифрования?
            0
            Я не совсем точно выразился в предыдущем комментарии. Уязвимостью является использование небезопасных функций хеширования и шифрования (типа MD5 или DES) для работы с критичными данными (конфиденциальными данными, для генерации идентифицирующих значений).
              +3
              При этом, я уверен, у вас не отслеживается использование самописных кривых алгоритмов генерации ключей, хэширования или шифрования. И сразу виден сценарий: меняем относительно стойкий DES на XOR строкой из 2 байт и предупреждение уходит, значит код нормальный :-)
                +1
                Реализация самописных алгоритмов может отслеживаться по побочным эффектам — например, использованию результатов работы таких алгоритмов. На практике скорее используют небезопасные стандартные алгоритмы, чем самописные, поэтому большая часть таких уязвимостей обнаруживается.

                Безусловно, инструменты автоматического анализа кода — как статического, так и динамического — не могут выявить абсолютно всех проблем, особенно связанных с некоторыми логическими уязвимостями.
                Для такого глубоко анализа лучше применять ручной аудит кода :)

                  +2
                  Я, можно сказать, периодически наблюдаю как вместо стандартных небезопасных начинают городить самописное… Такие лапочки прямо! :-)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое