Cách viết quy tắc cho Checkmarx mà không bị điên

Này Habr!

Trong công việc của chúng tôi, công ty chúng tôi thường xuyên xử lý các công cụ phân tích mã tĩnh (SAST) khác nhau. Ra khỏi hộp tất cả đều hoạt động trung bình. Tất nhiên, tất cả phụ thuộc vào dự án và các công nghệ được sử dụng trong đó, cũng như mức độ áp dụng các quy tắc phân tích đối với những công nghệ này. Theo tôi, một trong những tiêu chí quan trọng nhất khi chọn công cụ SAST là khả năng tùy chỉnh nó theo đặc điểm cụ thể của ứng dụng của bạn, cụ thể là viết và thay đổi các quy tắc phân tích hoặc, vì chúng thường được gọi là Truy vấn tùy chỉnh.

Cách viết quy tắc cho Checkmarx mà không bị điên

Chúng tôi thường sử dụng Checkmarx nhất - một trình phân tích mã rất thú vị và mạnh mẽ. Trong bài viết này tôi sẽ nói về kinh nghiệm viết quy tắc phân tích cho nó.

Mục lục

Nhập

Để bắt đầu, tôi muốn giới thiệu một trong số ít bài viết bằng tiếng Nga về tính năng viết truy vấn cho Checkmarx. Nó được xuất bản trên Habré vào cuối năm 2019 với tiêu đề: "Xin chào, Checkmarx!" Cách viết truy vấn Checkmarx SAST và tìm các lỗ hổng thú vị.

Nó xem xét chi tiết cách viết các truy vấn đầu tiên bằng CxQL (Ngôn ngữ truy vấn Checkmarx) cho một số ứng dụng thử nghiệm và hiển thị các nguyên tắc cơ bản về cách hoạt động của các quy tắc phân tích.

Tôi sẽ không lặp lại những gì được mô tả trong đó, mặc dù vẫn sẽ có một số giao lộ. Trong bài viết của mình, tôi sẽ cố gắng biên soạn một loại “bộ sưu tập các công thức nấu ăn”, một danh sách các giải pháp cho các vấn đề cụ thể mà tôi gặp phải trong quá trình làm việc với Checkmarx. Tôi đã phải vắt óc suy nghĩ về nhiều vấn đề này. Đôi khi không có đủ thông tin trong tài liệu và đôi khi thậm chí còn khó hiểu cách thực hiện những gì được yêu cầu. Tôi hy vọng trải nghiệm và những đêm mất ngủ của tôi sẽ không vô ích, và “bộ sưu tập công thức Truy vấn Tùy chỉnh” này sẽ giúp bạn tiết kiệm được vài giờ hoặc một vài tế bào thần kinh. Vì vậy, hãy bắt đầu!

Thông tin chung về quy định

Trước tiên, chúng ta hãy xem xét một số khái niệm cơ bản và quy trình làm việc với các quy tắc để hiểu rõ hơn về những gì sẽ xảy ra tiếp theo. Và cũng bởi vì tài liệu không nói gì về điều này hoặc cấu trúc rất dàn trải, không thuận tiện cho lắm.

  1. Các quy tắc được áp dụng trong quá trình quét tùy thuộc vào cài đặt trước được chọn khi bắt đầu (một bộ quy tắc hiện hoạt). Bạn có thể tạo số lượng cài đặt trước không giới hạn và cách cấu trúc chính xác chúng tùy thuộc vào chi tiết cụ thể trong quy trình của bạn. Bạn có thể nhóm chúng theo ngôn ngữ hoặc chọn cài đặt trước cho từng dự án. Số lượng quy tắc hoạt động ảnh hưởng đến tốc độ và độ chính xác của quá trình quét.

    Cách viết quy tắc cho Checkmarx mà không bị điênThiết lập Preset trong giao diện Checkmarx

  2. Các quy tắc được chỉnh sửa trong một công cụ đặc biệt có tên CxAuditor. Đây là một ứng dụng dành cho máy tính để bàn kết nối với máy chủ chạy Checkmarx. Công cụ này có hai chế độ hoạt động: chỉnh sửa quy tắc và phân tích kết quả của quá trình quét đã thực hiện.

    Cách viết quy tắc cho Checkmarx mà không bị điênGiao diện CxAudit

  3. Các quy tắc trong Checkmarx được chia theo ngôn ngữ, tức là mỗi ngôn ngữ có một bộ truy vấn riêng. Ngoài ra còn có một số quy tắc chung được áp dụng bất kể ngôn ngữ nào, đây được gọi là các truy vấn cơ bản. Phần lớn, các truy vấn cơ bản liên quan đến việc tìm kiếm thông tin mà các quy tắc khác sử dụng.

    Cách viết quy tắc cho Checkmarx mà không bị điênQuy tắc phân chia theo ngôn ngữ

  4. Các quy tắc là “Có thể thực thi” và “Không thể thực thi” (Đã thực thi và không được thực thi). Theo ý kiến ​​của tôi, cái tên này không hoàn toàn chính xác, nhưng nó là như vậy. Điểm mấu chốt là kết quả của việc thực thi các quy tắc “Có thể thực thi” sẽ được hiển thị trong kết quả quét trong giao diện người dùng và các quy tắc “Không thể thực thi” chỉ cần thiết để sử dụng kết quả của chúng trong các yêu cầu khác (về bản chất, chỉ là một chức năng).

    Cách viết quy tắc cho Checkmarx mà không bị điênXác định loại quy tắc khi tạo

  5. Bạn có thể tạo các quy tắc mới hoặc bổ sung/viết lại các quy tắc hiện có. Để viết lại quy tắc, bạn cần tìm quy tắc đó trong cây, nhấp chuột phải và chọn “Ghi đè” từ menu thả xuống. Điều quan trọng cần nhớ ở đây là các quy tắc mới ban đầu không được đưa vào cài đặt trước và không hoạt động. Để bắt đầu sử dụng chúng, bạn cần kích hoạt chúng trong menu “Preset Manager” trong nhạc cụ. Các quy tắc được viết lại sẽ giữ nguyên cài đặt của chúng, nghĩa là nếu quy tắc đó đang hoạt động thì quy tắc đó sẽ vẫn như vậy và sẽ được áp dụng ngay lập tức.

    Cách viết quy tắc cho Checkmarx mà không bị điênVí dụ về quy tắc mới trong giao diện Preset Manager

  6. Trong quá trình thực thi, một “cây” yêu cầu sẽ được xây dựng, điều này phụ thuộc vào điều gì. Các quy tắc thu thập thông tin được thực hiện trước tiên và những người sử dụng thông tin đó sẽ được thực hiện sau. Kết quả thực thi được lưu vào bộ nhớ đệm, vì vậy nếu có thể sử dụng kết quả của quy tắc hiện có thì tốt hơn nên làm như vậy, điều này sẽ giảm thời gian quét.

  7. Các quy tắc có thể được áp dụng ở các cấp độ khác nhau:

  • Đối với toàn bộ hệ thống - sẽ được sử dụng để quét bất kỳ dự án nào

  • Ở cấp độ nhóm (Nhóm) - sẽ chỉ được sử dụng để quét các dự án trong nhóm đã chọn.

  • Ở cấp độ dự án - Sẽ được áp dụng trong một dự án cụ thể

    Cách viết quy tắc cho Checkmarx mà không bị điênXác định mức độ áp dụng quy tắc

“Từ điển” cho người mới bắt đầu

Và tôi sẽ bắt đầu với một vài điều khiến tôi thắc mắc, đồng thời tôi cũng sẽ chỉ ra một số kỹ thuật giúp đơn giản hóa cuộc sống một cách đáng kể.

Các thao tác với danh sách

- вычитание одного из другого (list2 - list1)
* пересечение списков (list1 * list2)
+ сложение списков (list1 + list2)

& (логическое И) - объединяет списки по совпадению (list1 & list2), аналогично пересечению (list1 * list2)
| (логическое ИЛИ) - объединяет списки по широкому поиску (list1 | list2)

Со списками не работает:  ^  &&  ||  %  / 

Tất cả các mục tìm thấy

Trong ngôn ngữ được quét, bạn có thể nhận được danh sách tất cả các thành phần mà Checkmarx đã xác định (chuỗi, hàm, lớp, phương thức, v.v.). Đây là một số không gian của các đối tượng có thể được truy cập thông qua All. Tức là tìm kiếm một đối tượng có tên cụ thể searchMe, bạn có thể tìm kiếm, ví dụ: theo tên trên tất cả các đối tượng được tìm thấy:

// Такой запрос выдаст все элементы
result = All;

// Такой запрос выдаст все элементы, в имени которых присутствует “searchMe“
result = All.FindByName("searchMe");

Tuy nhiên, nếu bạn cần tìm kiếm bằng một ngôn ngữ khác mà vì lý do nào đó không được đưa vào quá trình quét (ví dụ: groovy trong một dự án Android), bạn có thể mở rộng không gian đối tượng của chúng tôi thông qua một biến:

result = AllMembers.All.FindByName("searchMe");

Chức năng phân tích dòng chảy

Các hàm này được sử dụng trong nhiều quy tắc và đây là một bản tóm tắt nhỏ về ý nghĩa của chúng:

// Какие данные second влияют на first.
// Другими словами - ТО (second) что влияет на  МЕНЯ (first).
result = first.DataInfluencedBy(second);

// Какие данные first влияют на second.
// Другими словами - Я (first) влияю на ТО (second).
result = first.DataInfluencingOn(second);

Lấy tên/đường dẫn tệp

Có một số thuộc tính có thể thu được từ kết quả của một truy vấn (tên của tệp chứa mục nhập được tìm thấy, chuỗi, v.v.), nhưng tài liệu không cho biết cách lấy và sử dụng chúng. Vì vậy, để thực hiện việc này, bạn cần truy cập vào thuộc tính LinePragma và các đối tượng chúng ta cần sẽ nằm bên trong nó:

// Для примера найдем все методы
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);

Điều đáng ghi nhớ là FileName thực sự chứa đường dẫn đến tệp, vì chúng tôi đã sử dụng phương thức GetFirstGraph.

Kết quả thực hiện

Có một biến đặc biệt bên trong CxQL result, trả về kết quả thực thi quy tắc đã viết của bạn. Nó được khởi tạo ngay lập tức và bạn có thể ghi các kết quả trung gian vào đó, thay đổi và tinh chỉnh chúng khi bạn làm việc. Tuy nhiên, nếu không có sự gán cho biến hoặc hàm này trong quy tắc return- kết quả thực hiện sẽ luôn bằng XNUMX.

Truy vấn sau đây sẽ không trả về bất cứ điều gì cho chúng tôi do thực thi và sẽ luôn trống:

// Находим элементы foo
CxList libraries = All.FindByName("foo");

Tuy nhiên, sau khi gán kết quả thực thi cho biến magic result, chúng ta sẽ thấy lệnh gọi này trả về kết quả gì:

// Находим элементы foo
CxList libraries = All.FindByName("foo");

// Выводим, как результат выполнения правила
result = libraries

// Или еще короче
result = All.FindByName("foo");

Sử dụng kết quả của các quy tắc khác

Các quy tắc trong Checkmarx có thể được gọi là tương tự như các hàm trong ngôn ngữ lập trình thông thường. Khi viết một quy tắc, bạn có thể sử dụng kết quả của các truy vấn khác. Ví dụ: không cần phải tìm kiếm tất cả các lệnh gọi phương thức trong mã mỗi lần, chỉ cần gọi quy tắc mong muốn:

// Получаем результат выполнения другого правила
CxList methods = Find_Methods();

// Ищем внутри метод foo. 
// Второй параметр false означает, что ищем без чувствительности к регистру
result = methods.FindByShortName("foo", false);

Cách tiếp cận này cho phép bạn rút ngắn mã và giảm đáng kể thời gian thực hiện quy tắc.

Giải quyết vấn đề

ghi nhật ký

Khi làm việc với công cụ, đôi khi không thể viết ngay truy vấn mong muốn và bạn phải thử nghiệm, thử các tùy chọn khác nhau. Đối với trường hợp như vậy, công cụ này cung cấp tính năng ghi nhật ký, được gọi như sau:

// Находим что-то
CxList toLog = All.FindByShortName("log");

// Формируем строку и отправляем в лог
cxLog.WriteDebugMessage (“number of DOM elements =” + All.Count);

Nhưng điều đáng ghi nhớ là phương pháp này chỉ chấp nhận làm đầu vào dây, do đó sẽ không thể hiển thị danh sách đầy đủ các phần tử được tìm thấy do thao tác đầu tiên. Tùy chọn thứ hai, được sử dụng để gỡ lỗi, là thỉnh thoảng gán cho một biến ma thuật result kết quả của truy vấn và xem điều gì sẽ xảy ra. Cách tiếp cận này không thuận tiện lắm; bạn cần chắc chắn rằng không có ghi đè hoặc thao tác nào với điều này trong mã sau result hoặc đơn giản là nhận xét mã bên dưới. Hoặc bạn có thể, giống như tôi, quên xóa một số cuộc gọi như vậy khỏi quy tắc có sẵn và tự hỏi tại sao không có gì hiệu quả.

Một cách thuận tiện hơn là gọi phương thức return với tham số cần thiết. Trong trường hợp này, việc thực thi quy tắc sẽ kết thúc và chúng ta có thể xem điều gì đã xảy ra do những gì chúng ta đã viết:

// Находим что-то
CxList toLog = All.FindByShortName("log");

// Выводим результат выполнения
return toLog

//Все, что написано дальше не будет выполнено
result = All.DataInfluencedBy(toLog)

Vấn đề đăng nhập

Có những tình huống bạn không thể truy cập vào công cụ CxAudit (dùng để viết quy tắc). Có thể có nhiều lý do dẫn đến điều này, bao gồm sự cố, cập nhật Windows đột ngột, BSOD và các tình huống không lường trước khác nằm ngoài tầm kiểm soát của chúng tôi. Trong trường hợp này, đôi khi có một phiên chưa hoàn thành trong cơ sở dữ liệu khiến bạn không thể đăng nhập lại. Để khắc phục, bạn cần chạy một số truy vấn:

Đối với Checkmarx trước 8.6:

// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;
 
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE [ClientType] = 6;

Đối với Checkmarx sau 8.6:

// Проверяем, что есть залогиненые пользователи, выполнив запрос в БД
SELECT COUNT(*) FROM LoggedinUser WHERE (ClientType = 'Audit');
 
// Если что-то есть, а на самом деле даже если и нет, попробовать выполнить запрос
DELETE FROM [CxDB].[dbo].LoggedinUser WHERE (ClientType = 'Audit');

Quy tắc viết

Bây giờ chúng ta đến phần thú vị nhất. Khi bạn bắt đầu viết các quy tắc trong CxQL, điều bạn thường thiếu không phải là nhiều tài liệu mà là một số ví dụ sống động về cách giải quyết một số vấn đề nhất định và mô tả quy trình hoạt động của các truy vấn nói chung.

Tôi sẽ cố gắng làm cho cuộc sống dễ dàng hơn một chút đối với những người bắt đầu tìm hiểu ngôn ngữ truy vấn và đưa ra một số ví dụ về cách sử dụng Truy vấn tùy chỉnh để giải quyết một số vấn đề nhất định. Một số trong số chúng khá chung chung và có thể được sử dụng trong thực tế trong công ty của bạn mà không cần thay đổi, một số khác thì cụ thể hơn nhưng chúng cũng có thể được sử dụng bằng cách thay đổi mã cho phù hợp với đặc thù ứng dụng của bạn.

Vì vậy, đây là những vấn đề chúng tôi gặp phải thường xuyên nhất:

Thử thách: Có một số Luồng trong kết quả thực thi quy tắc và một trong số chúng là sự lồng ghép của một quy tắc khác, bạn phải để lại một trong số chúng.

giải pháp: Thật vậy, đôi khi Checkmarx hiển thị một số luồng dữ liệu có thể chồng chéo và là phiên bản rút gọn của các luồng dữ liệu khác. Có một phương pháp đặc biệt cho những trường hợp như vậy Giảm dòng chảy. Tùy theo thông số sẽ chọn Flow ngắn nhất hoặc dài nhất:

// Оставить только длинные Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceSmallFlow);

// Оставить только короткие Flow
result = result.ReduceFlow(CxList.ReduceFlowType.ReduceBigFlow);

Thử thách: Mở rộng danh sách dữ liệu nhạy cảm mà công cụ phản ứng

giải pháp: Checkmarx có các quy tắc cơ bản, kết quả của chúng được sử dụng bởi nhiều truy vấn khác. Bằng cách bổ sung một số quy tắc này bằng dữ liệu cụ thể cho ứng dụng của bạn, bạn có thể cải thiện ngay kết quả quét của mình. Dưới đây là một quy tắc ví dụ để giúp bạn bắt đầu:

Chung_privacy_violation_list

Hãy thêm một số biến được sử dụng trong ứng dụng của chúng tôi để lưu trữ thông tin nhạy cảm:

// Получаем результат выполнения базового правила
result = base.General_privacy_violation_list();

// Ищем элементы, которые попадают под простые регулярные выражения. Можно дополнить характерными для вас паттернами.
CxList personalList = All.FindByShortNames(new List<string> {
	"*securityToken*", "*sessionId*"}, false);

// Добавляем к конечному результату
result.Add(personalList);

Thử thách: Mở rộng danh sách các biến bằng mật khẩu

giải pháp: Tôi khuyên bạn nên chú ý ngay đến quy tắc cơ bản để xác định mật khẩu trong mã và thêm vào đó danh sách các tên biến thường được sử dụng trong công ty của bạn.

Mật khẩu_privacy_violation_list

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);
	}
}

Thử thách: Thêm các khung đã sử dụng không được Checkmarx hỗ trợ

giải pháp: Mọi truy vấn trong Checkmarx đều được chia theo ngôn ngữ nên bạn cần thêm quy tắc cho từng ngôn ngữ. Dưới đây là một số ví dụ về các quy tắc như vậy.

Nếu các thư viện được sử dụng để bổ sung hoặc thay thế chức năng tiêu chuẩn thì chúng có thể dễ dàng được thêm vào quy tắc cơ bản. Sau đó tất cả những ai sử dụng nó sẽ biết ngay về những lời giới thiệu mới. Ví dụ: thư viện để đăng nhập Android là Timber và Loggi. Trong gói cơ bản, không có quy tắc nào để xác định các cuộc gọi phi hệ thống, vì vậy nếu mật khẩu hoặc số nhận dạng phiên được đưa vào nhật ký, chúng tôi sẽ không biết về nó. Hãy thử thêm định nghĩa về các phương thức đó vào quy tắc Checkmarx.

Ví dụ về mã kiểm tra sử dụng thư viện Timber để ghi nhật ký:

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");
    }
}

Và đây là một ví dụ về yêu cầu Checkmarx, nó sẽ cho phép bạn thêm định nghĩa gọi các phương thức Timber làm điểm thoát cho dữ liệu từ ứng dụng:

Tìm đầu ra của Android

// Получаем результат выполнения базового правила
result = base.Find_Android_Outputs();

// Дополняем вызовами, которые приходят из библиотеки Timber
CxList timber = All.FindByExactMemberAccess("Timber.*") +
    All.FindByShortName("Timber").GetMembersOfTarget();

// Добавляем к конечному результату
result.Add(timber);

Và bạn cũng có thể thêm vào quy tắc lân cận, nhưng quy tắc này liên quan trực tiếp đến việc đăng nhập vào Android:

TìmAndroidLog_Outputs

// Получаем результат выполнения базового правила
result = base.Find_Android_Log_Outputs();

// Дополняем вызовами, которые приходят из библиотеки Timber
result.Add(
  All.FindByExactMemberAccess("Timber.*") +
  All.FindByShortName("Timber").GetMembersOfTarget()
);

Ngoài ra, nếu ứng dụng Android sử dụng quản lý công việc đối với công việc không đồng bộ, bạn nên thông báo thêm cho Checkmarx về điều này bằng cách thêm phương thức lấy dữ liệu từ tác vụ getInputData:

TìmAndroidĐọc

// Получаем результат выполнения базового правила
result = base.Find_Android_Read();

// Дополняем вызовом функции getInputData, которая используется в WorkManager
CxList getInputData = All.FindByShortName("getInputData");

// Добавляем к конечному результату
result.Add(getInputData.GetMembersOfTarget());

Thử thách: Tìm kiếm dữ liệu nhạy cảm trong plist cho các dự án iOS

giải pháp: iOS thường sử dụng các tệp đặc biệt có phần mở rộng .plist để lưu trữ các biến và giá trị khác nhau. Không nên lưu trữ mật khẩu, mã thông báo, khóa và dữ liệu nhạy cảm khác trong các tệp này vì chúng có thể được trích xuất khỏi thiết bị mà không gặp bất kỳ sự cố nào.

Tệp Plist có các tính năng không thể thấy rõ bằng mắt thường nhưng lại quan trọng đối với Checkmarx. Hãy viết một quy tắc sẽ tìm kiếm dữ liệu chúng ta cần và cho chúng ta biết liệu mật khẩu hoặc mã thông báo có được đề cập ở đâu đó hay không.

Một ví dụ về tệp như vậy chứa mã thông báo để liên lạc với dịch vụ phụ trợ:

<?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>

Và một quy tắc cho Checkmarx, có một số sắc thái cần được tính đến khi viết:

// Используем результат выполнения правила по поиску файлов 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);

Thử thách: Tìm thông tin trong XML

giải pháp: Checkmarx có các chức năng rất thuận tiện để làm việc với XML và tìm kiếm các giá trị, thẻ, thuộc tính, v.v. Nhưng thật không may, đã xảy ra lỗi trong tài liệu do không có một ví dụ nào hoạt động được. Mặc dù thực tế là lỗi này đã được loại bỏ trong phiên bản tài liệu mới nhất nhưng hãy cẩn thận nếu bạn sử dụng các phiên bản tài liệu cũ hơn.

Đây là một ví dụ không chính xác từ tài liệu:

// Код работать не будет
result = All.FindXmlAttributesByNameAndValue("*.app", 8, “id”, "error- section", false, true);

Do nỗ lực thực thi, chúng tôi sẽ nhận được một lỗi All không có phương pháp nào như vậy... Và điều này đúng, vì có một không gian đối tượng đặc biệt, riêng biệt để sử dụng các hàm làm việc với XML - cxXPath. Đây là giao diện của truy vấn chính xác để tìm một cài đặt trong Android cho phép sử dụng lưu lượng HTTP:

// Правильный вариант с использованием cxXPath
result = cxXPath.FindXmlAttributesByNameAndValue("*.xml", 8, "cleartextTrafficPermitted", "true", false, true);

Chúng ta hãy xem xét nó chi tiết hơn một chút, vì cú pháp của tất cả các hàm là tương tự nhau, sau khi bạn đã tìm ra một hàm, bạn chỉ cần chọn hàm bạn cần. Vì vậy, tuần tự theo các thông số:

  • "*.xml"— mặt nạ của các tập tin cần tìm kiếm

  • 8 - id của ngôn ngữ mà quy tắc được áp dụng

  • "cleartextTrafficPermitted"— tên thuộc tính trong xml

  • "true" - giá trị của thuộc tính này

  • false — sử dụng biểu thức chính quy khi tìm kiếm

  • true — có nghĩa là việc tìm kiếm sẽ được thực hiện bỏ qua chữ hoa chữ thường, nghĩa là không phân biệt chữ hoa chữ thường

Ví dụ: chúng tôi đã sử dụng một quy tắc xác định cài đặt kết nối mạng không chính xác, từ quan điểm bảo mật, trong Android cho phép giao tiếp với máy chủ qua giao thức HTTP. Ví dụ về cài đặt có chứa thuộc tính cleartextTrafficPermitted với ý nghĩa 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>

Thử thách: Giới hạn kết quả theo tên/đường dẫn tệp

giải pháp: Trong một trong những dự án lớn liên quan đến việc phát triển ứng dụng di động dành cho Android, chúng tôi đã gặp phải những kết quả dương tính giả về quy tắc xác định cài đặt làm rối mã nguồn. Thực tế là quy tắc tìm kiếm vượt trội trong tệp build.gradle cài đặt chịu trách nhiệm áp dụng các quy tắc làm rối mã nguồn cho phiên bản phát hành của ứng dụng.

Nhưng trong các dự án lớn đôi khi có những file con build.gradle, đề cập đến các thư viện có trong dự án. Điều đặc biệt là ngay cả khi các tệp này không cho thấy nhu cầu làm xáo trộn, các cài đặt của tệp hợp ngữ gốc sẽ được áp dụng trong quá trình biên dịch.

Vì vậy, nhiệm vụ là cắt bỏ các kích hoạt trong các tệp con thuộc thư viện. Chúng có thể được xác định bởi sự hiện diện của dòng apply 'com.android.library'.

Mã ví dụ từ tập tin build.gradle, điều này xác định nhu cầu che giấu:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.2"
    defaultConfig {
        ...
    }

    buildTypes {
        release {
            minifyEnabled true
            ...
        }
    }
}

dependencies {
  ...
}

Ví dụ về tệp build.gradle đối với thư viện có trong dự án không có cài đặt này:

apply plugin: 'android-library'

dependencies {
  compile 'com.android.support:support-v4:18.0.+'
}

android {
  compileSdkVersion 14
  buildToolsVersion '17.0.0'
  ...
}

Và quy tắc cho Checkmarx:

ProGuardObfuscationNotInUse

// Поиск метода 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);
		}
	}
}

Cách tiếp cận này có thể khá phổ biến và hữu ích không chỉ cho các ứng dụng Android mà còn cho các trường hợp khác khi bạn cần xác định xem kết quả có thuộc về một tệp cụ thể hay không.

Thử thách: Thêm hỗ trợ cho thư viện của bên thứ ba nếu cú ​​pháp không được hỗ trợ đầy đủ

giải pháp: Số lượng các khung công tác khác nhau được sử dụng trong quá trình viết mã đơn giản là vượt trội. Tất nhiên, Checkmarx không phải lúc nào cũng biết về sự tồn tại của chúng và nhiệm vụ của chúng tôi là dạy nó hiểu rằng một số phương pháp nhất định đặc biệt thuộc về khuôn khổ này. Đôi khi điều này phức tạp bởi thực tế là các khung sử dụng tên hàm rất phổ biến và không thể xác định rõ ràng mối quan hệ của một lệnh gọi cụ thể với một thư viện cụ thể.

Khó khăn là cú pháp của các thư viện như vậy không phải lúc nào cũng được nhận dạng chính xác và bạn phải thử nghiệm để tránh nhận được một số lượng lớn kết quả dương tính giả. Có một số tùy chọn để cải thiện độ chính xác của quá trình quét và giải quyết vấn đề:

  • Tùy chọn đầu tiên, chúng tôi biết chắc chắn rằng thư viện được sử dụng trong một dự án cụ thể và có thể áp dụng quy tắc ở cấp độ nhóm. Nhưng nếu nhóm quyết định thực hiện một cách tiếp cận khác hoặc sử dụng một số thư viện có tên hàm trùng nhau, chúng ta có thể có được một bức tranh không mấy dễ chịu về vô số kết quả dương tính giả.

  • Tùy chọn thứ hai là tìm kiếm các tệp trong đó thư viện được nhập rõ ràng. Với phương pháp này, chúng tôi có thể chắc chắn rằng thư viện chúng tôi cần được sử dụng chính xác trong tệp này.

  • Và lựa chọn thứ ba là sử dụng đồng thời hai phương pháp trên.

Ví dụ: hãy xem một thư viện nổi tiếng trong vòng tròn hẹp bóng mượt đối với ngôn ngữ lập trình Scala, cụ thể là chức năng Nối các giá trị theo nghĩa đen. Nói chung, để truyền tham số cho truy vấn SQL, bạn phải sử dụng toán tử $, thay thế dữ liệu vào một truy vấn SQL được tạo sẵn. Trên thực tế, nó tương tự trực tiếp với Câu lệnh đã chuẩn bị sẵn trong Java. Tuy nhiên, nếu bạn cần xây dựng một truy vấn SQL một cách linh hoạt, chẳng hạn như nếu bạn cần truyền tên bảng, bạn có thể sử dụng toán tử #$, sẽ thay thế trực tiếp dữ liệu vào truy vấn (gần giống như nối chuỗi).

Mã mẫu:

// В общем случае - значения, контролируемые пользователем
val table = "coffees"
sql"select * from #$table where name = $name".as[Coffee].headOption

Checkmarx chưa biết cách phát hiện việc sử dụng Splicing Literal Values ​​và bỏ qua toán tử #$, vì vậy hãy thử hướng dẫn nó cách xác định các thao tác chèn SQL tiềm năng và đánh dấu đúng vị trí trong mã:

// Находим все импорты
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));
}

Thử thách: Tìm kiếm các hàm dễ bị tấn công đã sử dụng trong thư viện Nguồn mở

giải pháp: Nhiều công ty sử dụng các công cụ giám sát Nguồn mở (thực hành OSA) để phát hiện việc sử dụng các phiên bản thư viện dễ bị tấn công trong các ứng dụng đã phát triển. Đôi khi không thể cập nhật thư viện đó lên phiên bản an toàn. Trong một số trường hợp có những hạn chế về chức năng, trong những trường hợp khác thì không có phiên bản an toàn nào cả. Trong trường hợp này, sự kết hợp giữa các phương pháp SAST và OSA sẽ giúp xác định rằng các chức năng dẫn đến việc khai thác lỗ hổng không được sử dụng trong mã.

Nhưng đôi khi, đặc biệt là khi xem xét JavaScript, đây có thể không phải là một nhiệm vụ hoàn toàn tầm thường. Dưới đây là một giải pháp, có thể không lý tưởng nhưng vẫn hoạt động, sử dụng ví dụ về các lỗ hổng trong thành phần lodash trong các phương pháp template и *set.

Ví dụ về kiểm tra mã dễ bị tấn công trong tệp 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!'

Và khi kết nối trực tiếp bằng 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>

Chúng tôi đang tìm kiếm tất cả các phương pháp dễ bị tấn công, được liệt kê trong các lỗ hổng:

// Ищем все строки: в которых встречается строка 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));

Thử thách: Tìm kiếm chứng chỉ được nhúng trong ứng dụng

giải pháp: Không có gì lạ khi các ứng dụng, đặc biệt là ứng dụng dành cho thiết bị di động, sử dụng chứng chỉ hoặc khóa để truy cập vào nhiều máy chủ khác nhau hoặc xác minh Ghim SSL. Từ góc độ bảo mật, việc lưu trữ những thứ như vậy trong mã không phải là cách tốt nhất. Hãy thử viết một quy tắc sẽ tìm kiếm các tệp tương tự trong kho lưu trữ:

// Найдем все сертификаты по маске файла
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;

Thử thách: Tìm mã thông báo bị xâm phạm trong ứng dụng

giải pháp: Thông thường, cần phải thu hồi mã thông báo bị xâm phạm hoặc thông tin quan trọng khác có trong mã. Tất nhiên, việc lưu trữ chúng bên trong mã nguồn không phải là một ý tưởng hay, nhưng các tình huống sẽ khác nhau. Nhờ các truy vấn CxQL, việc tìm kiếm những thứ như thế này khá dễ dàng:

// Получаем все строки, которые содержатся в коде
CxList strings = base.Find_Strings();

// Ищем среди всех строк нужное нам значение. В примере токен в виде строки "qwerty12345"
result = strings.FindByShortName("qwerty12345");

Kết luận

Tôi hy vọng bài viết này sẽ hữu ích cho những ai đang bắt đầu làm quen với công cụ Checkmarx. Có lẽ những người đã viết ra các quy tắc của riêng mình trong một thời gian dài cũng sẽ tìm thấy điều gì đó hữu ích trong hướng dẫn này.

Thật không may, hiện tại thiếu nguồn tài nguyên để có thể thu thập những ý tưởng mới trong quá trình phát triển các quy tắc cho Checkmarx. Đó là lý do tại sao chúng tôi tạo ra kho lưu trữ trên Github, nơi chúng tôi sẽ đăng tác phẩm của mình để mọi người sử dụng CxQL có thể tìm thấy điều gì đó hữu ích trong đó và cũng có cơ hội chia sẻ tác phẩm của họ với cộng đồng. Kho lưu trữ đang trong quá trình điền và cấu trúc nội dung, vì vậy rất hoan nghênh những người đóng góp!

Cảm ơn bạn!

Nguồn: www.habr.com

Thêm một lời nhận xét