おい、ハブル!
私たちの仕事では、当社はさまざまな静的コード分析ツール (SAST) を頻繁に扱います。 箱から出してすぐに、それらはすべて平均的に機能します。 もちろん、それはすべて、プロジェクトとそこで使用されるテクノロジー、およびこれらのテクノロジーが分析ルールでどの程度カバーされているかによって異なります。 私の意見では、SAST ツールを選択する際の最も重要な基準の XNUMX つは、アプリケーションの仕様に合わせてツールをカスタマイズできることです。つまり、分析ルール (カスタム クエリと呼ばれることが多い) を作成および変更できることです。
私たちは非常に興味深く強力なコード アナライザーである Checkmarx を最も頻繁に使用します。 この記事では、その分析ルールを作成した私の経験について話します。
目次
エントリー
まず最初に、Checkmarx のクエリ作成機能に関するロシア語の数少ない記事の 2019 つをお勧めしたいと思います。 XNUMX年末に次のタイトルでHabréに掲載されました。
一部のテスト アプリケーションに対して CxQL (Checkmarx クエリ言語) で最初のクエリを作成する方法を詳細に検証し、分析ルールがどのように機能するかの基本原則を示します。
ここで説明されている内容は繰り返しませんが、いくつかの交差点がまだ存在します。 私の記事では、Checkmarx での作業中に遭遇した特定の問題に対する解決策のリストである、一種の「レシピ集」を編集しようと思います。 私はこれらの問題の多くについて頭を悩ませなければなりませんでした。 ドキュメントに十分な情報が含まれていない場合や、必要なことを行う方法を理解するのが難しい場合もありました。 私の経験と眠れぬ夜が無駄にならず、この「カスタム クエリ レシピのコレクション」が数時間または数個の神経細胞を節約することを願っています。 それでは、始めましょう!
ルールに関する一般的な情報
まず、次に何が起こるかをよりよく理解するために、いくつかの基本的な概念とルールを扱うプロセスを見てみましょう。 また、ドキュメントにはこれについて何も記載されていなかったり、構造が非常に広範囲に渡っていたりするため、あまり便利ではありません。
-
ルールは、開始時に選択されたプリセット (アクティブなルールのセット) に応じて、スキャン中に適用されます。 プリセットは無制限に作成できますが、それらをどのように構築するかはプロセスの詳細によって異なります。 言語ごとにグループ化することも、プロジェクトごとにプリセットを選択することもできます。 アクティブなルールの数は、スキャンの速度と精度に影響します。
Checkmarx インターフェースでのプリセットの設定
-
ルールは、CxAuditor と呼ばれる特別なツールで編集されます。 これは、Checkmarx を実行しているサーバーに接続するデスクトップ アプリケーションです。 このツールには、ルールの編集と、すでに実行されたスキャンの結果の分析という XNUMX つの操作モードがあります。
CxAuditインターフェース
-
Checkmarx のルールは言語ごとに分割されています。つまり、各言語には独自のクエリ セットがあります。 言語に関係なく適用される一般的なルールもいくつかあり、これらはいわゆる基本クエリです。 ほとんどの場合、基本的なクエリには、他のルールが使用する情報の検索が含まれます。
言語ごとにルールを分ける
-
ルールには「実行可能」と「非実行可能」(実行されるか実行されない)があります。 私の意見では、完全に正しい名前ではありませんが、それがそれです。 肝心なのは、「実行可能」ルールの実行結果は UI のスキャン結果に表示され、「非実行可能」ルールは他のリクエスト (本質的には単なる関数) で結果を使用する場合にのみ必要であるということです。
作成時のルールの種類の決定
-
新しいルールを作成したり、既存のルールを補足/書き換えたりすることができます。 ルールを書き換えるには、ツリー内でルールを見つけて右クリックし、ドロップダウン メニューから「上書き」を選択する必要があります。 ここで、新しいルールは最初はプリセットに含まれておらず、アクティブではないことに留意することが重要です。 それらの使用を開始するには、機器の「Preset Manager」メニューでそれらをアクティブにする必要があります。 書き換えられたルールはその設定を保持します。つまり、ルールがアクティブだった場合はそのまま残り、すぐに適用されます。
Preset Manager インターフェイスの新しいルールの例
-
実行中に、内容に応じてリクエストの「ツリー」が構築されます。 情報を収集するルールが最初に実行され、情報を使用するルールが XNUMX 番目に実行されます。 実行結果はキャッシュされるため、既存のルールの結果を使用できる場合は、そうすることをお勧めします。これにより、スキャン時間が短縮されます。
-
ルールはさまざまなレベルで適用できます。
-
システム全体 - あらゆるプロジェクトのスキャンに使用されます
-
チーム レベル (チーム) - 選択したチームのプロジェクトをスキャンするためにのみ使用されます。
-
プロジェクト レベル - 特定のプロジェクトに適用されます
ルールが適用されるレベルの決定
初心者向けの「辞書」
私が疑問を抱いたいくつかのことから始めて、作業を大幅に簡素化するいくつかのテクニックも紹介します。
リストを使った操作
- вычитание одного из другого (list2 - list1)
* пересечение списков (list1 * list2)
+ сложение списков (list1 + list2)
& (логическое И) - объединяет списки по совпадению (list1 & list2), аналогично пересечению (list1 * list2)
| (логическое ИЛИ) - объединяет списки по широкому поиску (list1 | list2)
Со списками не работает: ^ && || % /
見つかったすべてのアイテム
スキャンされた言語内で、Checkmarx が識別したすべての要素 (文字列、関数、クラス、メソッドなど) のリストを取得できます。 これは、次の方法でアクセスできるオブジェクトの空間です。 All
。 つまり、特定の名前を持つオブジェクトを検索します。 searchMe
、たとえば、見つかったすべてのオブジェクトを名前で検索できます。
// Такой запрос выдаст все элементы
result = All;
// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");
ただし、何らかの理由でスキャンに含まれなかった別の言語 (たとえば、Android プロジェクトの groovy) で検索する必要がある場合は、変数を使用してオブジェクト空間を拡張できます。
result = AllMembers.All.FindByName("searchMe");
流動解析のための機能
これらの関数は多くのルールで使用されており、これらの関数の意味を簡単に説明します。
// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на МЕНЯ (first).
result = first.DataInfluencedBy(second);
// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);
ファイル名/パスの取得
クエリの結果から取得できる属性がいくつかあります (エントリが見つかったファイルの名前、文字列など) が、ドキュメントにはそれらの取得方法と使用方法が記載されていません。 したがって、これを行うには、LinePragma プロパティにアクセスする必要があります。必要なオブジェクトはその中にあります。
// Для примера найдем все методы
CxList methods = Find_Methods();
// В методах найдем по имени метод scope
CxList scope = methods.FindByName("scope");
// Таким образом можо получить путь к файлу
string current_filename = scope.GetFirstGraph().LinePragma.FileName;
// А вот таким - строку, где нашлось срабатывание
int current_line = scope.GetFirstGraph().LinePragma.Line;
// Эти параметры можно использовать по разному
// Например получить все объекты в файле
CxList inFile = All.FindByFileName(current_filename);
// Или найти что происходит в конкретной строке
CxList inLine = inFile.FindByPosition(current_line);
覚えておく価値があるのは、 FileName
メソッドを使用したため、実際にはファイルへのパスが含まれています GetFirstGraph
.
実行結果
CxQL 内に特別な変数があります result
、作成したルールを実行した結果を返します。 これはすぐに初期化され、中間結果を書き込むことができ、作業中に変更したり調整したりできます。 ただし、ルール内にこの変数または関数への割り当てがない場合は、 return
— 実行結果は常にゼロになります。
次のクエリは実行結果として何も返さず、常に空になります。
// Находим элементы foo
CxList libraries = All.FindByName("foo");
ただし、実行結果をマジック変数 result に代入すると、この呼び出しが何を返すかがわかります。
// Находим элементы foo
CxList libraries = All.FindByName("foo");
// Выводим, как результат выполнения правила
result = libraries
// Или еще короче
result = All.FindByName("foo");
他のルールの結果を使用する
Checkmarx のルールは、通常のプログラミング言語の関数と同様に呼び出すことができます。 ルールを作成するときは、他のクエリの結果を使用することもできます。 たとえば、コード内のすべてのメソッド呼び出しを毎回検索する必要はなく、必要なルールを呼び出すだけです。
// Получаем результат выполнения другого правила
CxList methods = Find_Methods();
// Ищем внутри метод foo.
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);
このアプローチにより、コードを短縮し、ルールの実行時間を大幅に短縮できます。
トラブルシューティング
ロギング
ツールを使用する場合、必要なクエリをすぐに作成できない場合があり、さまざまなオプションを試して実験する必要があります。 このような場合に備えて、ツールは次のように呼び出されるログを提供します。
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);
ただし、このメソッドは入力としてのみ受け入れることを覚えておく価値があります。 ストリング, したがって、最初の操作の結果として、見つかった要素の完全なリストを表示することはできません。 XNUMX 番目のオプションはデバッグに使用され、時々マジック変数に代入します。 result
クエリの結果を確認して、何が起こるかを確認してください。 このアプローチはあまり便利ではありません。後のコード内でこれをオーバーライドしたり操作したりしないようにする必要があります。 result
または、以下のコードをコメントするだけです。 あるいは、私のように、既製のルールからそのような呼び出しをいくつか削除するのを忘れて、なぜ何も機能しないのかと疑問に思うこともあります。
より便利な方法は、メソッドを呼び出すことです。 return
必須パラメータを使用して。 この場合、ルールの実行が終了し、次のように記述した結果として何が起こったかを確認できるようになります。
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)
ログインの問題
CxAudit ツール (ルールの作成に使用されます) にアクセスできない場合があります。 これには、クラッシュ、突然の Windows アップデート、BSOD、その他の制御不能な予期せぬ状況など、さまざまな理由が考えられます。 この場合、データベース内に未完了のセッションが存在し、再ログインできない場合があります。 これを修正するには、いくつかのクエリを実行する必要があります。
Checkmarx 8.6 より前の場合:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
Checkmarx 8.6 以降の場合:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');
書き方のルール
ここからは最も興味深い部分に移ります。 CxQL でルールを書き始めるときに、不足しているのは多くのドキュメントではなく、特定の問題を解決し、クエリが一般的にどのように機能するかのプロセスを説明する生きた例です。
これからクエリ言語を始めようとしている人が少しでも理解しやすくなるように、カスタム クエリを使用して特定の問題を解決する例をいくつか紹介します。 それらの中には、非常に一般的で実質的に変更せずに社内で使用できるものもあります。また、より具体的なものもありますが、アプリケーションの仕様に合わせてコードを変更することで使用することもできます。
したがって、最も頻繁に遭遇する問題は次のとおりです。
タスク: ルールの実行結果には複数のフローがあり、そのうちの XNUMX つは別のフローのネストになっているため、そのうちの XNUMX つを残す必要があります。
解決策: 実際、Checkmarx は、重複している可能性のある複数のデータ フローを表示したり、他のデータ フローの短縮バージョンを表示したりすることがあります。 このような場合には特別な方法があります フローを減らす。 パラメーターに応じて、最短または最長のフローが選択されます。
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);
タスク: ツールが反応する機密データのリストを展開します
解決策: Checkmarx には基本的なルールがあり、その結果は他の多くのクエリで使用されます。 これらのルールの一部をアプリケーション固有のデータで補足することで、スキャン結果をすぐに改善できます。 以下は、開始するためのルールの例です。
一般プライバシー違反リスト
機密情報を保存するためにアプリケーションで使用されるいくつかの変数を追加しましょう。
// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();
// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
"*securityToken*", "*sessionId*"}, false);
// Добавляем к конечному результату
result.Add(personalList);
タスク: パスワードを含む変数のリストを展開する
解決策: コード内でパスワードを定義するための基本的なルールにすぐに注意を払い、社内で一般的に使用されている変数名のリストをそれに追加することをお勧めします。
パスワードプライバシー違反リスト
CxList allStrings = All.FindByType("String");
allStrings.Add(All.FindByType(typeof(StringLiteral)));
allStrings.Add(Find_UnknownReference());
allStrings.Add(All.FindByType(typeof (Declarator)));
allStrings.Add(All.FindByType(typeof (MemberAccess)));
allStrings.Add(All.FindByType(typeof(EnumMemberDecl)));
allStrings.Add(Find_Methods().FindByShortName("get*"));
// Дополняем дефолтный список переменных
List < string > pswdIncludeList = new List<string>{"*password*", "*psw", "psw*", "pwd*", "*pwd", "*authKey*", "pass*", "cipher*", "*cipher", "pass", "adgangskode", "benutzerkennwort", "chiffre", "clave", "codewort", "contrasena", "contrasenya", "geheimcode", "geslo", "heslo", "jelszo", "kennwort", "losenord", "losung", "losungswort", "lozinka", "modpas", "motdepasse", "parol", "parola", "parole", "pasahitza", "pasfhocal", "passe", "passord", "passwort", "pasvorto", "paswoord", "salasana", "schluessel", "schluesselwort", "senha", "sifre", "wachtwoord", "wagwoord", "watchword", "zugangswort", "PAROLACHIAVE", "PAROLA CHIAVE", "PAROLECHIAVI", "PAROLE CHIAVI", "paroladordine", "verschluesselt", "sisma",
"pincode",
"pin"};
List < string > pswdExcludeList = new List<string>{"*pass", "*passable*", "*passage*", "*passenger*", "*passer*", "*passing*", "*passion*", "*passive*", "*passover*", "*passport*", "*passed*", "*compass*", "*bypass*", "pass-through", "passthru", "passthrough", "passbytes", "passcount", "passratio"};
CxList tempResult = allStrings.FindByShortNames(pswdIncludeList, false);
CxList toRemove = tempResult.FindByShortNames(pswdExcludeList, false);
tempResult -= toRemove;
tempResult.Add(allStrings.FindByShortName("pass", false));
foreach (CxList r in tempResult)
{
CSharpGraph g = r.data.GetByIndex(0) as CSharpGraph;
if(g != null && g.ShortName != null && g.ShortName.Length < 50)
{
result.Add(r);
}
}
タスク: Checkmarx でサポートされていない使用されているフレームワークを追加する
解決策: Checkmarx のすべてのクエリは言語ごとに分割されているため、言語ごとにルールを追加する必要があります。 以下にそのようなルールの例をいくつか示します。
標準機能を補完または置き換えるライブラリを使用する場合、それらを基本ルールに簡単に追加できます。 そうすれば、それを使用する誰もが新しい導入についてすぐに知ることができます。 例として、Android にログインするためのライブラリは Timber と Loggi です。 基本パッケージでは、システムコール以外を識別するためのルールがないため、パスワードやセッション識別子がログに記録されても、それを知ることはできません。 このようなメソッドの定義を Checkmarx ルールに追加してみましょう。
ログ記録に Timber ライブラリを使用するテスト コードの例:
package com.death.timberdemo;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import timber.log.Timber;
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Timber.e("Error Message");
Timber.d("Debug Message");
Timber.tag("Some Different tag").e("And error message");
}
}
次に、Checkmarx のリクエストの例を示します。これにより、アプリケーションからのデータの出口ポイントとして Timber メソッドを呼び出す定義を追加できます。
Android出力の検索
// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget();
// Добавляем к конечному результату
result.Add(timber);
また、隣接するルールに追加することもできますが、これは Android へのログインに直接関係します。
FindAndroidLog_Outputs
// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();
// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
All.FindByExactMemberAccess("Timber.*") +
All.FindByShortName("Timber").GetMembersOfTarget()
);
また、Android アプリケーションが使用する場合は、 getInputData
:
探すAndroid読む
// Получаем результат выполнения базового правила
result = base.Find_Android_Read();
// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");
// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());
タスク: iOS プロジェクトの plist で機密データを検索する
解決策: iOS では、さまざまな変数や値を保存するために、.plist 拡張子の付いた特別なファイルがよく使用されます。 パスワード、トークン、キー、その他の機密データはデバイスから問題なく抽出できるため、これらのファイルに保存することはお勧めできません。
Plist ファイルには、肉眼では明らかではありませんが、Checkmarx にとって重要な機能があります。 必要なデータを検索し、パスワードまたはトークンがどこかに記載されているかどうかを通知するルールを作成しましょう。
バックエンド サービスと通信するためのトークンを含むファイルの例:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>DeviceDictionary</key>
<dict>
<key>phone</key>
<string>iPhone 6s</string>
</dict>
<key>privatekey</key>
<string>MIICXAIBAAKBgQCqGKukO1De7zhZj6+</string>
</dict>
</plist>
また、Checkmarx のルールには、作成時に考慮すべきいくつかのニュアンスがあります。
// Используем результат выполнения правила по поиску файлов plist, чтобы уменьшить время работы правила и
CxList plist = Find_Plist_Elements();
// Инициализируем новую переменную
CxList dictionarySettings = All.NewCxList();
// Теперь добавим поиск всех интересующих нас значений. В дальнейшем можно расширять этот список.
// Для поиска значений, как ни странно, используется FindByMemberAccess - поиск обращений к методам. Второй параметр внутри функции, false, означает, что поиск нечувствителен к регистру
dictionarySettings.Add(plist.FindByMemberAccess("privatekey", false));
dictionarySettings.Add(plist.FindByMemberAccess("privatetoken", false));
// Для корректного поиска из-за особенностей структуры plist - нужно искать по типу "If statement"
CxList ifStatements = plist.FindByType(typeof(IfStmt));
// Добавляем в результат, перед этим получив родительский узел - для правильного отображения
result = dictionarySettings.FindByFathers(ifStatements);
タスク: XML 内の情報の検索
解決策: Checkmarx には、XML を操作し、値、タグ、属性などを検索するための非常に便利な機能があります。 しかし、残念ながら、ドキュメントにエラーがあり、動作する例が XNUMX つもありませんでした。 この欠陥は最新バージョンのドキュメントでは解消されていますが、以前のバージョンのドキュメントを使用する場合は注意してください。
以下はドキュメントの間違った例です。
// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);
実行を試みた結果、次のようなエラーが表示されます。 All
そのような方法はありません...そして、XML を操作するための関数を使用するための特別な別個のオブジェクト空間があるため、これは真実です - cxXPath
。 Android で HTTP トラフィックの使用を許可する設定を見つけるための正しいクエリは次のようになります。
// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);
もう少し詳しく見てみましょう。すべての関数の構文は似ているため、関数を理解したら、必要な関数を選択するだけです。 したがって、パラメータに従って順番に次のようにします。
-
"*.xml"
— 検索するファイルのマスク -
8
— ルールが適用される言語の ID -
"cleartextTrafficPermitted"
— XML の属性名 -
"true"
— この属性の値 -
false
— 検索時の正規表現の使用 -
true
— 大文字と小文字を区別せずに検索が実行されることを意味します。
例として、HTTP プロトコルを介したサーバーとの通信を許可する Android のネットワーク接続設定がセキュリティの観点から間違っていることを識別するルールを使用しました。 属性を含む設定例 cleartextTrafficPermitted
意味のある true
:
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<trust-anchors>
<certificates src="@raw/my_ca"/>
</trust-anchors>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">secure.example.com</domain>
</domain-config>
</domain-config>
</network-security-config>
タスク: ファイル名/パスで結果を制限する
解決策: Android 用モバイル アプリケーションの開発に関連する大規模プロジェクトの XNUMX つで、難読化設定を決定するルールの誤検知に遭遇しました。 実際のところ、ルールはそのままの状態でファイル内を検索します。 build.gradle
アプリケーションのリリースバージョンに難読化ルールを適用する設定。
ただし、大規模なプロジェクトでは子ファイルが存在する場合があります。 build.gradle
、プロジェクトに含まれるライブラリを参照します。 特徴は、これらのファイルが難読化の必要性を示していない場合でも、親アセンブリ ファイルの設定がコンパイル中に適用されることです。
したがって、タスクは、ライブラリに属する子ファイル内のトリガーを遮断することです。 線の有無で識別できる apply 'com.android.library'
.
ファイルからのコード例 build.gradle
、難読化の必要性を決定します。
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.2"
defaultConfig {
...
}
buildTypes {
release {
minifyEnabled true
...
}
}
}
dependencies {
...
}
ファイル例 build.gradle
この設定を持たないプロジェクトに含まれるライブラリの場合:
apply plugin: 'android-library'
dependencies {
compile 'com.android.support:support-v4:18.0.+'
}
android {
compileSdkVersion 14
buildToolsVersion '17.0.0'
...
}
そして、Checkmarx のルールは次のとおりです。
ProGuard難読化未使用
// Поиск метода release среди всех методов в Gradle файлах
CxList releaseMethod = Find_Gradle_Method("release");
// Все объекты из файлов build.gradle
CxList gradleBuildObjects = Find_Gradle_Build_Objects();
// Поиск того, что находится внутри метода "release" среди всех объектов из файлов build.gradle
CxList methodInvokesUnderRelease = gradleBuildObjects.FindByType(typeof(MethodInvokeExpr)).GetByAncs(releaseMethod);
// Ищем внутри gradle-файлов строку "com.android.library" - это значит, что данный файл относится к библиотеке и его необходимо исключить из правила
CxList android_library = gradleBuildObjects.FindByName("com.android.library");
// Инициализация пустого массива
List<string> libraries_path = new List<string> {};
// Проходим через все найденные "дочерние" файлы
foreach(CxList library in android_library)
{
// Получаем путь к каждому файлу
string file_name_library = library.GetFirstGraph().LinePragma.FileName;
// Добавляем его в наш массив
libraries_path.Add(file_name_library);
}
// Ищем все вызовы включения обфускации в релизных настройках
CxList minifyEnabled = methodInvokesUnderRelease.FindByShortName("minifyEnabled");
// Получаем параметры этих вызовов
CxList minifyValue = gradleBuildObjects.GetParameters(minifyEnabled, 0);
// Ищем среди них включенные
CxList minifyValueTrue = minifyValue.FindByShortName("true");
// Немного магии, если не нашли стандартным способом :D
if (minifyValueTrue.Count == 0) {
minifyValue = minifyValue.FindByAbstractValue(abstractValue => abstractValue is TrueAbstractValue);
} else {
// А если всё-таки нашли, то предыдущий результат и оставляем
minifyValue = minifyValueTrue;
}
// Если не нашлось таких методов
if (minifyValue.Count == 0)
{
// Для более корректного отображения места срабатывания в файле ищем или buildTypes или android
CxList tempResult = All.NewCxList();
CxList buildTypes = Find_Gradle_Method("buildTypes");
if (buildTypes.Count > 0) {
tempResult = buildTypes;
} else {
tempResult = Find_Gradle_Method("android");
}
// Для каждого из найденных мест срабатывания проходим и определяем, дочерний или основной файлы сборки
foreach(CxList res in tempResult)
{
// Определяем, в каком файле был найден buildType или android методы
string file_name_result = res.GetFirstGraph().LinePragma.FileName;
// Если такого файла нет в нашем списке "дочерних" файлов - значит это основной файл и его можно добавить в результат
if (libraries_path.Contains(file_name_result) == false){
result.Add(res);
}
}
}
このアプローチは非常に汎用的であり、Android アプリケーションだけでなく、結果が特定のファイルに属するかどうかを判断する必要がある他の場合にも役立ちます。
タスク: 構文が完全にサポートされていない場合は、サードパーティのライブラリのサポートを追加します。
解決策: コードを記述するプロセスで使用されるさまざまなフレームワークの数は、数えきれないほどです。 もちろん、Checkmarx は常にその存在を知っているわけではありません。私たちの仕事は、特定のメソッドが特にこのフレームワークに属していることを理解できるように教育することです。 フレームワークでは非常に一般的な関数名が使用されており、特定のライブラリに対する特定の呼び出しの関係を明確に判断することが不可能であるため、状況が複雑になる場合があります。
難しいのは、このようなライブラリの構文が常に正しく認識されるとは限らず、大量の誤検知が発生しないように実験する必要があることです。 スキャンの精度を向上させ、問題を解決するには、いくつかのオプションがあります。
-
最初のオプションでは、ライブラリが特定のプロジェクトで使用されていることが確実にわかっており、チーム レベルでルールを適用できます。 しかし、チームが別のアプローチを取ることを決定したり、関数名が重複する複数のライブラリを使用したりすると、多数の誤検知が発生するというあまり好ましくない状況が得られる可能性があります。
-
XNUMX 番目のオプションは、ライブラリが明確にインポートされているファイルを検索することです。 このアプローチを使用すると、必要なライブラリがこのファイル内で正確に使用されていることを確認できます。
-
XNUMX 番目のオプションは、上記の XNUMX つのアプローチを一緒に使用することです。
例として、狭い範囲でよく知られている図書館を見てみましょう $
、データを事前に作成された SQL クエリに置き換えます。 つまり、実際には、これは Java の Prepared Statement に直接似ています。 ただし、SQL クエリを動的に構築する必要がある場合、たとえばテーブル名を渡す必要がある場合は、演算子を使用できます。 #$
、これはデータをクエリに直接置き換えます (文字列の連結とほぼ同じです)。
サンプルコード:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOption
Checkmarx は、スプライシング リテラル値の使用を検出する方法をまだ知らず、演算子をスキップします #$
そこで、潜在的な SQL インジェクションを識別し、コード内の適切な場所を強調表示するように教えてみましょう。
// Находим все импорты
CxList imports = All.FindByType(typeof(Import));
// Ищем по имени, есть ли в импортах slick
CxList slick = imports.FindByShortName("slick");
// Некоторый флаг, определяющий, что импорт библиотеки в коде присутствует
// Для более точного определения - можно применить подход с именем файла
bool not_empty_list = false;
foreach (CxList r in slick)
{
// Если встретили импорт, считаем, что slick используется
not_empty_list = true;
}
if (not_empty_list) {
// Ищем вызовы, в которые передается SQL-строка
CxList sql = All.FindByShortName("sql");
sql.Add(All.FindByShortName("sqlu"));
// Определяем данные, которые попадают в эти вызовы
CxList data_sql = All.DataInfluencingOn(sql);
// Так как синтакис не поддерживается, можно применить подход с регулярными выражениями
// RegExp стоит использовать крайне осторожно и не применять его на большом количестве данных, так как это может сильно повлиять на производительность
CxList find_possible_inj = data_sql.FindByRegex(@"#$", true, true, true);
// Избавляемся от лишних срабатываний, если они есть и выводим в результат
result = find_possible_inj.FindByType(typeof(BinaryExpr));
}
タスク: オープンソース ライブラリで使用されている脆弱な関数を検索する
解決策: 多くの企業は、オープンソース監視ツール (OSA プラクティス) を使用して、開発されたアプリケーションでのライブラリの脆弱なバージョンの使用を検出しています。 このようなライブラリを安全なバージョンに更新できない場合があります。 機能制限がある場合もあれば、安全なバージョンがまったく存在しない場合もあります。 この場合、SAST と OSA の実践を組み合わせると、脆弱性の悪用につながる機能がコード内で使用されていないことを判断するのに役立ちます。
しかし、特に JavaScript を考慮する場合、これは完全に簡単な作業ではない場合があります。 以下は、コンポーネントの脆弱性の例を使用した、理想的ではないかもしれませんが、それでも機能する解決策です。 lodash
メソッド内 template
и *set
.
JS ファイル内の潜在的に脆弱なコードをテストする例:
/**
* Template example
*/
'use strict';
var _ = require("./node_modules/lodash.js");
// Use the "interpolate" delimiter to create a compiled template.
var compiled = _.template('hello <%= js %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'
// Use the internal `print` function in "evaluate" delimiters.
var compiled = _.template('<% print("hello " + js); %>!');
console.log(compiled({ 'js': 'lodash' }));
// => 'hello lodash!'
HTML で直接接続する場合:
<!DOCTYPE html>
<html>
<head>
<title>Lodash Tutorial</title>
<script src="./node_modules/lodash.js"></script>
<script type="text/javascript">
// Lodash chunking array
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
let c1 = _.template('<% print("hello " + js); %>!');
console.log(c1);
let c2 = _.template('<% print("hello " + js); %>!');
console.log(c2);
</script>
</head>
<body></body>
</html>
私たちは、「脆弱性」にリストされているすべての脆弱なメソッドを探しています。
// Ищем все строки: в которых встречается строка lodash (предполагаем, что это объявление импорта библиотеки
CxList lodash_strings = Find_String_Literal().FindByShortName("*lodash*");
// Ищем все данные: которые взаимодействуют с этими строками
CxList data_on_lodash = All.InfluencedBy(lodash_strings);
// Задаем список уязвимых методов
List<string> vulnerable_methods = new List<string> {"template", "*set"};
// Ищем все наши уязвимые методы, которые перечисленны в уязвимостях и отфильтровываем их только там, где они вызывались
CxList vulnerableMethods = All.FindByShortNames(vulnerable_methods).FindByType(typeof(MethodInvokeExpr));
//Находим все данные: которые взаимодействуют с данными методами
CxList vulnFlow = All.InfluencedBy(vulnerableMethods);
// Если есть пересечение по этим данным - кладем в результат
result = vulnFlow * data_on_lodash;
// Формируем список путей по которым мы уже прошли, чтобы фильтровать в дальнейшем дубли
List<string> lodash_result_path = new List<string> {};
foreach(CxList lodash_result in result)
{
// Очередной раз получаем пути к файлам
string file_name = lodash_result.GetFirstGraph().LinePragma.FileName;
lodash_result_path.Add(file_name);
}
// Дальше идет часть относящаяся к html файлам, так как в них мы не можем проследить откуда именно идет вызов
// Формируем массив путей файлов, чтобы быть уверенными, что срабатывания уязвимых методов были именно в тех файлах, в которых объявлен lodash
List<string> lodash_path = new List<string> {};
foreach(CxList string_lodash in lodash_strings)
{
string file_name = string_lodash.GetFirstGraph().LinePragma.FileName;
lodash_path.Add(file_name);
}
// Перебираем все уязвимые методы и убеждаемся, что они вызваны в тех же файлах, что и объявление/включение lodash
foreach(CxList method in vulnerableMethods)
{
string file_name_method = method.GetFirstGraph().LinePragma.FileName;
if (lodash_path.Contains(file_name_method) == true && lodash_result_path.Contains(file_name_method) == false){
result.Add(method);
}
}
// Убираем все UknownReferences и оставляем самый "длинный" из путей, если такие встречаются
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow) - result.FindByType(typeof(UnknownReference));
タスク: アプリケーションに埋め込まれた証明書の検索
解決策: アプリケーション、特にモバイルアプリケーションが証明書やキーを使用してさまざまなサーバーにアクセスしたり、SSL ピニングを検証したりすることは珍しいことではありません。 セキュリティの観点から、そのようなものをコードに保存することはベスト プラクティスではありません。 リポジトリ内で類似のファイルを検索するルールを作成してみましょう。
// Найдем все сертификаты по маске файла
CxList find_certs = All.FindByShortNames(new List<string> {"*.der", "*.cer", "*.pem", "*.key"}, false);
// Проверим, где в приложении они используются
CxList data_used_certs = All.DataInfluencedBy(find_certs);
// И для мобильных приложений - можем поискать методы, где вызывается чтение сертификатов
// Для других платформ и приложений могут быть различные методы
CxList methods = All.FindByMemberAccess("*.getAssets");
// Пересечение множеств даст нам результат по использованию локальных сертификатов в приложении
result = methods * data_used_certs;
タスク: アプリケーション内で侵害されたトークンを見つける
解決策: 多くの場合、侵害されたトークンやコード内に存在するその他の重要な情報を取り消すことが必要になります。 もちろん、それらをソース コード内に保存するのは良い考えではありませんが、状況は異なります。 CxQL クエリのおかげで、次のようなものを見つけるのは非常に簡単です。
// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();
// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");
まとめ
この記事が、Checkmarx ツールを使い始めた人にとって役立つことを願っています。 おそらく、長い間独自のルールを作成してきた人も、このガイドに役立つものを見つけることができるでしょう。
残念ながら、現在、Checkmarx のルールの開発中に新しいアイデアを収集できるリソースが不足しています。 だからこそ私たちは、
ありがとうございました!
出所: habr.com