Язык шаблонов для универсального сигнатурного анализатора кода

    Процесс сигнатурного анализа кода в нашем проекте PT Application Inspector разбит на следующие этапы:


    1. парсинг в зависимое от языка представление (abstract syntax tree, AST);
    2. преобразование AST в независимый от языка унифицированный формат;
    3. непосредственное сопоставление с шаблонами, описанными на DSL.

    О первых двух этапах было рассказано в предыдущих статьях «Теория и практика парсинга исходников с помощью ANTLR и Roslyn» и «Обработка древовидных структур и унифицированное AST». Данная статья посвящена третьему этапу, а именно: различным способам описания шаблонов, разработке специализированного языка (DSL) для их описания, а также примерам шаблонов на этом языке.



    Содержание




    Способы описания шаблонов


    • Заданные в коде шаблоны (hardcoded);
    • JSON, XML или другой язык разметки;
    • DSL, предметно-ориентированный язык .


    Hardcoded


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



    JSON, XML или другой язык разметки


    Части сопоставляемого AST можно непосредственно сохранять и загружать из JSON или других форматов. При таком подходе шаблоны можно будет загружать извне, однако синтаксис будет громоздким и не подойдет для редактирования пользователем. Однако этот способ можно использовать для сериализации древовидных структур. (О способах сериализации древовидных структур в .NET и их обходе будет рассказано в следующей статье.)



    Собственный язык описания шаблонов, DSL


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



    Целесообразность


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



    Синтаксис


    Во второй статье цикла мы говорили, что основными конструкциями в императивных языках программирования являются примитивные типы (literals), выражения (expressions) и инструкции (statements). При разработке DSL мы сделали все так же. Примеры выражений:


    • expr(args); вызов метода;
    • Id expr = expr; инициализация переменной;
    • expr + expr; конкатенация;
    • new Id(args); создание объекта;
    • expr[expr]; обращение по индексу или ключу.

    Инструкции создаются путем добавления точки с запятой в конец выражения.


    Литералами же являются примитивные типы, такие как:


    • Id; идентификатор;
    • String; строка, обособляется двойными кавычками;
    • Int; целое число;
    • Bool; булево значение.

    Эти литералы позволяют описывать простые конструкции, однако с помощью них нельзя, например, описывать диапазоны чисел, регулярные выражения. Для поддержки таких более сложных случаев были введены расширенные конструкции (PatternStatement, PatternExpression, PatternLiteral). Такие конструкции обособляются специальными скобками <[ и ]>. Подобный синтаксис был заимствован из Nemerle (в нем такие скобки используются для квазицитирования, т. е. для преобразования кода внутри них в AST Nemerle).


    Примеры поддерживаемых расширенных конструкций представлены в списке ниже. Для некоторых также конструкций предусмотрен синтаксический сахар, позволяющий сократить запись:


    • <[]>; оператор расширенного выражения (например, <[md5|sha1]> или <[0..2048]>);
    • # или <[expr]>`; любое Expression;
    • ... или <[args]>; произвольное количество любых аргументов;
    • (expr.)?expr; эквивалентно expr.expr или просто expr;
    • <[~]>expr — отрицание условия;
    • expr (<[||]> expr)* — объединение нескольких условий (ИЛИ);
    • Comment: "regex" — поиск по комментариям.


    Примеры шаблонов



    Жестко заданный пароль (все языки)


    (#.)?<[(?i)password(?-i)]> = <["\w*"]>


    • #; любое выражение, может отсутствовать;
    • <[(?i)password(?-i)]>; регулярное выражение типа Id, может быть записано в любом регистре;
    • <["\w*"]>; регулярное выражение типа String;



    Слабый генератор случайных чисел (C#, Java)


    new Random(...)


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



    Утечка отладочной информации (PHP)


    Configure.<[(?i)^write$]>("debug", <[1..9]>)


    • <[(?i)^write$]>; регулярное выражение типа ID, не зависит от регистра и определяет только точные вхождения;
    • ("debug", <[1..9]>); аргументы функции;
    • <[1..9]>; диапазон целых чисел от 1 до 9.


    Небезопасное SSL-соединение (Java)


    new AllowAllHostnameVerifier(...) <[||]> SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER.


    Использование "логического ИЛИ" для целых синтаксических конструкций.



    Пароль в комментарии (все языки)


    Comment: <[ "(?i)password(?-i)\s*\=" ]>


    Поиск комментариев в исходном коде. Причем в C#, Java, PHP, как известно, однострочные комментарии начинаются с двойного слэша //, а в SQL-подобных языках — с двойного дефиса --.



    SQL-инъекция (C#, Java, PHP)


    <["(?i)select\s\w*"]> + <[~"\w*"]>


    Простая SQL инъекция: конкатенация любой строки, начинающейся с select и не строковым выражением в правой части.



    Куки без атрибута безопасности (PHP)


    session_set_cookie_params(#,#,#)


    Установка куки без флага защищенности, который задается в четвертом аргументе.



    Пустой блок обработки исключения (все языки)


    try {...} catch { }


    Пустой блок обработки исключений. В C# модуль будет находить такой код:


    try
    {
    }
    catch
    {
    }

    В T-SQL такой:


    BEGIN TRY
        SELECT 1/0 AS DivideByZero
    END TRY
    BEGIN CATCH
    END CATCH

    А в PL/SQL такой:


    PROCEDURE empty_default_exception_handler IS
    BEGIN
        INSERT INTO table1 VALUES(1, 2, 3, 4);
        COMMIT;
      EXCEPTION
        WHEN OTHERS THEN NULL;
    END;


    Небезопасный куки (Java)


    Cookie <[@cookie]> = new Cookie(...);
    ...
    ~<[@cookie]>.setSecure(true);
    ...
    response.addCookie(<[@cookie]>);


    Добавление куки без установленного флага защищенности. Несмотря на то что данный шаблон правильнее реализовывать в taint-анализе, его удалось реализовать и с помощью более примитивного алгоритма сопоставления. В нем используется прикрепленная переменная @cookie, отрицание выражения и произвольное количество утверждений.



    Перехват незакрытого курсора (PL/SQL, T-SQL)


    PL/SQL

    <[@cursor]> = DBMS_SQL.OPEN_CURSOR;
    ...
    <[~]>DBMS_SQL.CLOSE_CURSOR(<[@cursor]>);

    T-SQL

    declare_cursor(<[@cursor]>);
    ...
    <[~]>deallocate(<[@cursor]>);

    Незакрытый курсор потенциально может эксплуатироваться менее привилегированным пользователем.


    В T-SQL будет находиться такой код:


    DECLARE Employee_Cursor CURSOR FOR
    SELECT EmployeeID, Title FROM AdventureWorks2012.HumanResources.Employee;
    OPEN Employee_Cursor;
    FETCH NEXT FROM Employee_Cursor;
    WHILE @@FETCH_STATUS = 0
       BEGIN
          FETCH NEXT FROM Employee_Cursor;
       END;
    --DEALLOCATE Employee_Cursor; is missing
    GO


    Чрезмерно расширенные полномочия (PL/SQL, T-SQL)


    grant_all(...)


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


    Будет находиться такой код:
    GRANT ALL ON employees TO john_doe;



    Заключение


    Для демонстрации работы нашего модуля мы подготовили видео, в котором показан процесс поиска определенных шаблонов в коде на различных языках программирования (C#, Java, PHP) в нашем продукте PT Application Inspector. Демонстрируется также корректная обработка синтаксических ошибок, которая была затронута в первой статье нашей серии.




    В следующих статьях мы расскажем:


    • о сравнении, сериализации и обходе древовидных структур в .NET;
    • построении CFG, DFG и taint-анализе.
    Positive Technologies
    Company

    Comments 6

      0
      спасибо за статью! На TypeScript не поглядываете? Очень хотелось бы увидеть его в списке.
        0

        У нас в планах есть поддержка JavaScript.

        0
        Разве использование регулярных выражений не повлияет существено на производительность?
          0

          Нет, а почему они должны существенно на нее повлиять? Регулярные выражения сопоставляются не со всеми токенами, а с теми, до которых дошли в результате сопоставления шаблона в виде дерева с основным AST. К тому они же компилируются при создании (на платформе .NET используется параметр RegexOptions.Compiled).


          В целом же модуль Pattern Matching работает очень быстро, но и возможности у него достаточно сильно ограничены.

          +1
          Спасибо за пост!

          А эту задачу пробовали решать?: Surreptitiously Weakening Cryptographic Systems
          Мне кажется, или подход может быть тот же?
            0

            А можно вкратце для всех объяснить как DSL можно там использовать?

          Only users with full accounts can post comments. Log in, please.