嘿哈布爾!
在我們的工作中,我們公司經常使用各種靜態程式碼分析工具(SAST)。 開箱即用,它們都工作正常。 當然,這一切都取決於專案和其中使用的技術,以及分析規則對這些技術的覆蓋程度。 在我看來,選擇 SAST 工具時最重要的標準之一是能夠根據應用程式的具體情況對其進行自訂,即編寫和更改分析規則,或更常稱為自訂查詢。
我們最常使用 Checkmarx - 一個非常有趣且強大的程式碼分析器。 在這篇文章中我將談談我為其編寫分析規則的經驗。
目錄
條目
首先,我想推薦一篇關於為 Checkmarx 編寫查詢功能的俄語文章。 它於 2019 年底在 Habré 上發表,標題為:
它詳細研究如何使用 CxQL(Checkmarx 查詢語言)為某些測試應用程式編寫第一個查詢,並展示了分析規則如何運作的基本原理。
我不會重複其中描述的內容,儘管仍然存在一些交叉點。 在我的文章中,我將嘗試編寫一種“食譜集”,這是我在使用 Checkmarx 期間遇到的特定問題的解決方案清單。 很多這樣的問題我都得絞盡腦汁。 有時文件中沒有足夠的信息,有時甚至很難理解如何執行所需的操作。 我希望我的經驗和不眠之夜不會白費,而這個「自訂查詢食譜集」將為您節省幾個小時或幾個神經細胞。 那麼,就讓我們開始吧!
有關規則的一般信息
首先,讓我們來看看一些基本概念以及使用規則的過程,以便更好地理解接下來會發生什麼。 而且因為文件沒有對此進行任何說明,或者結構非常分散,這不是很方便。
-
掃描期間應用規則取決於開始時選擇的預設(一組活動規則)。 您可以建立無限數量的預設,具體如何建立它們取決於您的流程的具體情況。 您可以按語言將它們分組,或為每個項目選擇預設。 活動規則的數量會影響掃描的速度和準確性。
在 Checkmarx 介面中設定預設
-
這些規則是在名為 CxAuditor 的特殊工具中編輯的。 這是一個連接到運行 Checkmarx 的伺服器的桌面應用程式。 該工具有兩種操作模式:編輯規則和分析已執行掃描的結果。
CxAudit介面
-
Checkmarx 中的規則以語言劃分,即每種語言都有自己的一組查詢。 還有一些無論語言如何都適用的通用規則,這些是所謂的基本查詢。 在大多數情況下,基本查詢涉及搜尋其他規則使用的資訊。
按語言劃分規則
-
規則分為「可執行」和「不可執行」(已執行和未執行)。 在我看來,這不是一個正確的名字,但事實就是如此。 最重要的是,執行「可執行」規則的結果將顯示在 UI 中的掃描結果中,而「不可執行」規則只需要在其他請求中使用其結果(本質上,只是一個函數)。
建立時確定規則類型
-
您可以建立新規則或補充/重寫現有規則。 為了重寫規則,您需要在樹中找到它,右鍵單擊並從下拉式選單中選擇“覆蓋”。 請務必記住,新規則最初並未包含在預設中,且未處於活動狀態。 要開始使用它們,您需要在儀器的「預設管理器」功能表中啟動它們。 重寫的規則保留其設置,也就是說,如果規則處於活動狀態,它將保持如此狀態並立即應用。
預設管理器介面中的新規則範例
-
在執行過程中,會建立一棵請求“樹”,這取決於什麼。 首先執行收集資訊的規則,然後執行使用資訊的規則。 執行結果被緩存,因此如果可以使用現有規則的結果,那麼最好這樣做,這將減少掃描時間。
-
規則可以套用於不同層級:
-
對於整個系統 - 將用於任何項目的任何掃描
-
在團隊層級(Team) - 將僅用於掃描所選團隊中的項目。
-
在項目層級 - 將應用於特定項目
確定規則的應用級別
適合初學者的“字典”
我將從一些引起我疑問的事情開始,我還將展示一些可以顯著簡化生活的技術。
列表操作
- вычитание одного из другого (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);
但值得記住的是,該方法僅接受輸入 細繩,因此無法顯示第一個操作的結果的找到元素的完整清單。 第二個選項用於調試,時常分配給魔術變量 result
查詢結果,看看會發生什麼。 這種方法不是很方便;您需要確保之後的程式碼中沒有對此進行覆寫或操作 result
或直接註解下面的程式碼。 或者,您可能像我一樣,忘記從現成的規則中刪除幾個此類調用,並想知道為什麼沒有任何效果。
更方便的方法是呼叫該方法 return
與所需的參數。 在這種情況下,規則的執行將結束,我們將能夠看到我們編寫的結果發生了什麼:
// Находим что-то
CxList toLog = All.FindByShortName("log");
// Выводим результат выполнения
return toLog
//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)
登入問題
在某些情況下,您無法存取 CxAudit 工具(用於編寫規則)。 造成這種情況的原因可能有很多,包括崩潰、Windows 突然更新、BSOD 和其他我們無法控制的不可預見的情況。 在這種情況下,有時資料庫中存在未完成的會話,這會阻止您再次登入。 要修復它,您需要執行多個查詢:
對於 8.6 之前的 Checkmarx:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
對於 8.6 之後的 Checkmarx:
// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');
書寫規則
現在我們到了最有趣的部分。 當您開始在 CxQL 中編寫規則時,您通常缺少的不是文檔,而是一些解決某些問題的活生生的示例,以及描述查詢一般如何工作的過程。
我將盡力讓那些開始深入研究查詢語言的人的生活變得更輕鬆,並給出幾個使用自訂查詢來解決某些問題的範例。 其中一些非常通用,幾乎無需更改即可在您的公司中使用,另一些則更具體,但也可以透過更改程式碼來使用它們以適應您的應用程式的具體情況。
那麼,以下是我們最常遇到的問題:
任務: 規則執行結果中有多個Flow,其中一個Flow是另一個Flow的巢狀,必須保留其中一個。
解決方案: 事實上,有時 Checkmarx 會顯示多個可能重疊的資料流,並且是其他資料流的縮短版本。 對於這種情況有一個特殊的方法 減少流量。 根據參數,它將選擇最短或最長的流:
// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);
// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);
任務: 展開工具反應的敏感資料列表
解決方案: Checkmarx 有基本規則,其結果被許多其他查詢使用。 透過使用特定於您的應用程式的資料補充其中一些規則,您可以立即改進掃描結果。 以下是幫助您入門的範例規則:
一般_privacy_violation_list
讓我們添加幾個在應用程式中用於儲存敏感資訊的變數:
// Получаем результат выполнения базового правила
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 登入直接相關:
尋找AndroidLog_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 以及搜尋值、標籤、屬性等。 但不幸的是,文檔中存在一個錯誤,導致沒有一個範例有效。 儘管該缺陷已在最新版本的文檔中消除,但如果您使用早期版本的文檔,請務必小心。
這是文檔中的一個不正確的範例:
// Код работать не будет
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
— 表示搜尋將忽略大小寫,即不區分大小寫
例如,我們使用了一條規則,從安全性角度來看,Android 中允許透過 HTTP 協定與伺服器進行通訊的網路連線設定是錯誤的。 包含屬性的設定範例 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 行動應用程式開發相關的大型專案之一中,我們遇到了確定混淆設定的規則的誤報。 事實上,開箱即用的規則在文件中搜索 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 並不總是知道它們的存在,我們的任務是教它理解某些方法專門屬於這個框架。 有時,由於框架使用非常常見的函數名稱,並且不可能明確確定特定呼叫與特定庫的關係,因此情況會變得複雜。
困難在於此類程式庫的語法並不總是能夠正確識別,您必須進行試驗以避免大量誤報。 有多種選擇可以提高掃描精度並解決問題:
-
第一個選項,我們確定該庫用於特定項目,並且可以在團隊層級應用該規則。 但是,如果團隊決定採取不同的方法或使用多個函數名稱重疊的庫,我們可能會得到大量誤報的不太令人愉快的圖片
-
第二個選項是搜尋明確匯入了庫的檔案。 透過這種方法,我們可以確保我們需要的庫完全在這個文件中使用。
-
第三種選擇是結合上述兩種方法。
舉個例子,讓我們來看看一個小圈子裡眾所皆知的圖書館 $
,它將資料替換為預先執行的 SQL 查詢。 也就是說,實際上它是Java中Prepared Statement的直接類似物。 但是,如果需要動態建構 SQL 查詢,例如需要傳遞表名,則可以使用運算符 #$
,它將直接將資料替換到查詢中(幾乎像字串連接)。
示例:
// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOption
Checkmarx 尚不知道如何偵測 Splicing Literal Values 的使用並跳過運算符 #$
,所以讓我們嘗試教它識別潛在的 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-Pinning 的情況並不罕見。 從安全角度來看,將此類內容儲存在程式碼中並不是最佳實踐。 讓我們嘗試編寫一個規則來搜尋儲存庫中的類似文件:
// Найдем все сертификаты по маске файла
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 規則開發過程中收集新想法的資源。 這就是我們創建的原因
謝謝你的關注!
來源: www.habr.com