당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법

헤이 하브르!

우리 회사는 업무를 수행하면서 다양한 정적 코드 분석 도구(SAST)를 다루는 경우가 많습니다. 기본적으로 그들은 모두 평균적으로 작동합니다. 물론 이는 프로젝트와 프로젝트에 사용된 기술, 그리고 이러한 기술이 분석 규칙에 얼마나 잘 적용되는지에 따라 달라집니다. 제 생각에는 SAST 도구를 선택할 때 가장 중요한 기준 중 하나는 응용 프로그램의 세부 사항에 맞게 도구를 사용자 정의할 수 있는 능력, 즉 분석 규칙을 작성하고 변경하거나 사용자 정의 쿼리라고 부르는 것입니다.

당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법

우리는 매우 흥미롭고 강력한 코드 분석기인 Checkmarx를 가장 자주 사용합니다. 이 기사에서는 이에 대한 분석 규칙을 작성한 경험에 대해 이야기하겠습니다.

차례

기입

우선, Checkmarx의 쿼리 작성 기능에 관한 몇 안 되는 러시아어 기사 중 하나를 추천하고 싶습니다. 2019년 말 Habré에 다음과 같은 제목으로 출판되었습니다. "안녕하세요, 체크막스!" Checkmarx SAST 쿼리를 작성하고 멋진 취약점을 찾는 방법.

일부 테스트 애플리케이션에 대해 CxQL(Checkmarx 쿼리 언어)로 첫 번째 쿼리를 작성하는 방법을 자세히 조사하고 분석 규칙 작동 방식에 대한 기본 원칙을 보여줍니다.

일부 교차로가 여전히 존재할지라도 여기에 설명된 내용을 반복하지 않겠습니다. 내 기사에서 나는 Checkmarx에서 작업하는 동안 직면한 특정 문제에 대한 해결책 목록인 일종의 "레시피 모음"을 작성하려고 노력할 것입니다. 나는 이러한 많은 문제들에 대해 머리를 써야 했습니다. 문서에 정보가 충분하지 않은 경우도 있었고, 필요한 작업을 수행하는 방법을 이해하기 어려울 때도 있었습니다. 내 경험과 잠 못 이루는 밤이 헛되지 않기를 바랍니다. 이 "맞춤 쿼리 레시피 모음"을 통해 몇 시간 또는 몇 개의 신경 세포를 절약할 수 있습니다. 자, 시작해 봅시다!

규칙에 대한 일반 정보

먼저, 다음에 일어날 일을 더 잘 이해하기 위해 몇 가지 기본 개념과 규칙 작업 프로세스를 살펴보겠습니다. 또한 문서에는 이에 대해 아무 것도 언급되어 있지 않거나 구조가 너무 분산되어 있어 그다지 편리하지 않습니다.

  1. 시작 시 선택한 사전 설정(활성 규칙 집합)에 따라 검색 중에 규칙이 적용됩니다. 사전 설정을 무제한으로 생성할 수 있으며 정확한 구성 방법은 프로세스의 세부 사항에 따라 다릅니다. 언어별로 그룹화하거나 각 프로젝트에 대한 사전 설정을 선택할 수 있습니다. 활성 규칙의 수는 검색 속도와 정확성에 영향을 미칩니다.

    당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법Checkmarx 인터페이스에서 사전 설정 설정

  2. 규칙은 CxAuditor라는 특수 도구에서 편집됩니다. 이것은 Checkmarx를 실행하는 서버에 연결하는 데스크톱 애플리케이션입니다. 이 도구에는 규칙 편집과 이미 수행된 스캔 결과 분석이라는 두 가지 작동 모드가 있습니다.

    당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법CxAudit 인터페이스

  3. Checkmarx의 규칙은 언어별로 구분됩니다. 즉, 각 언어에는 고유한 쿼리 세트가 있습니다. 언어에 관계없이 적용되는 몇 가지 일반 규칙도 있는데, 이것이 소위 기본 쿼리입니다. 대부분의 경우 기본 쿼리에는 다른 규칙이 사용하는 정보 검색이 포함됩니다.

    당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법언어별로 규칙 나누기

  4. 규칙은 "실행 가능" 및 "실행 불가능"(실행됨 및 실행되지 않음)입니다. 제 생각에는 정확한 이름은 아니지만 그게 전부입니다. 요점은 "실행 가능" 규칙을 실행한 결과가 UI의 스캔 결과에 표시되고 "비실행 가능" 규칙은 해당 결과를 다른 요청(본질적으로는 함수)에 사용하는 데에만 필요하다는 것입니다.

    당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법생성 시 규칙 유형 결정

  5. 새 규칙을 만들거나 기존 규칙을 보완/다시 작성할 수 있습니다. 규칙을 다시 작성하려면 트리에서 규칙을 찾아 마우스 오른쪽 버튼을 클릭하고 드롭다운 메뉴에서 "재정의"를 선택해야 합니다. 여기서 새 규칙은 처음에는 사전 설정에 포함되지 않으며 활성화되지 않는다는 점을 기억하는 것이 중요합니다. 사용을 시작하려면 장비의 "Preset Manager" 메뉴에서 활성화해야 합니다. 다시 작성된 규칙은 설정을 유지합니다. 즉, 규칙이 활성화된 경우 그대로 유지되고 즉시 적용됩니다.

    당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법사전 설정 관리자 인터페이스의 새 규칙 예

  6. 실행하는 동안 요청의 "트리"가 구축되며 이는 무엇에 따라 달라집니다. 정보를 수집하는 규칙이 먼저 실행되고, 정보를 사용하는 규칙이 두 번째로 실행됩니다. 실행 결과는 캐시되므로 기존 규칙의 결과를 그대로 사용할 수 있다면 그렇게 하는 것이 검사 시간을 단축하는 데 도움이 됩니다.

  7. 규칙은 다양한 수준에서 적용될 수 있습니다.

  • 전체 시스템용 - 모든 프로젝트의 모든 스캔에 사용됩니다.

  • 팀 수준(팀) - 선택한 팀의 프로젝트를 스캔하는 데만 사용됩니다.

  • 프로젝트 수준에서 - 특정 프로젝트에 적용됩니다.

    당황하지 않고 Checkmarx에 대한 규칙을 작성하는 방법규칙이 적용될 수준 결정

초보자를 위한 '사전'

그리고 나에게 의문을 불러일으키는 몇 가지 사항부터 시작하고, 삶을 크게 단순화할 여러 가지 기술도 보여 드리겠습니다.

목록 작업

- вычитание одного из другого (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— 실행 결과는 항상 XNUMX입니다.

다음 쿼리는 실행 결과로 아무것도 반환하지 않으며 항상 비어 있습니다.

// Находим элементы 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에서 규칙 작성을 시작할 때 종종 부족한 것은 문서가 아니라 특정 문제를 해결하고 쿼리가 일반적으로 작동하는 프로세스를 설명하는 실제 예제입니다.

나는 쿼리 언어를 시작하는 사람들의 삶을 좀 더 쉽게 만들고 특정 문제를 해결하기 위해 사용자 정의 쿼리를 사용하는 몇 가지 예를 제공하려고 노력할 것입니다. 그 중 일부는 매우 일반적이어서 실질적으로 변경 없이 회사에서 사용할 수 있고, 일부는 더 구체적이지만 응용 프로그램의 세부 사항에 맞게 코드를 변경하여 사용할 수도 있습니다.

따라서 가장 자주 발생하는 문제는 다음과 같습니다.

작업 : 규칙을 실행한 결과에는 여러 개의 흐름이 있으며 그 중 하나가 다른 흐름의 중첩이므로 그 중 하나를 남겨 두어야 합니다.

솔루션 : 실제로 Checkmarx는 때때로 중복되거나 다른 데이터 흐름의 단축 버전일 수 있는 여러 데이터 흐름을 표시합니다. 이런 경우에는 특별한 방법이 있습니다 감소흐름. 매개변수에 따라 가장 짧거나 가장 긴 흐름이 선택됩니다.

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

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

작업 : 도구가 반응하는 민감한 데이터 목록 확장

솔루션 : Checkmarx에는 기본 규칙이 있으며 그 결과는 다른 많은 쿼리에서 사용됩니다. 이러한 규칙 중 일부를 애플리케이션 관련 데이터로 보완하면 스캔 결과를 즉시 향상시킬 수 있습니다. 다음은 시작하기 위한 예제 규칙입니다.

General_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 로그인과 직접 관련이 있습니다.

FindAndroidLog_Outputs

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

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

또한 Android 애플리케이션을 사용하는 경우 WorkManager 비동기 작업의 경우 작업에서 데이터를 가져오는 방법을 추가하여 이에 대해 Checkmarx에 추가로 알리는 것이 좋습니다. getInputData:

찾기안드로이드읽기

// Получаем результат выполнения базового правила
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. HTTP 트래픽 사용을 허용하는 Android 설정을 찾기 위한 올바른 쿼리는 다음과 같습니다.

// Правильный вариант с использованием 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용 모바일 애플리케이션 개발과 관련된 대규모 프로젝트 중 하나에서 난독화 설정을 결정하는 규칙에 대한 오탐이 발생했습니다. 사실은 기본적으로 적용되는 규칙이 파일에서 검색된다는 것입니다. 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 난독화NotInUse

// Поиск метода 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가 항상 그 존재를 아는 것은 아니며 우리의 임무는 특정 방법이 이 프레임워크에 구체적으로 속한다는 것을 이해하도록 가르치는 것입니다. 때때로 이는 프레임워크가 매우 일반적인 함수 이름을 사용하고 특정 라이브러리에 대한 특정 호출의 관계를 명확하게 결정하는 것이 불가능하다는 사실로 인해 복잡해집니다.

어려운 점은 이러한 라이브러리의 구문이 항상 정확하게 인식되는 것은 아니며 많은 수의 잘못된 긍정을 피하기 위해 실험을 해야 한다는 것입니다. 스캔 정확도를 향상하고 문제를 해결하기 위한 몇 가지 옵션이 있습니다.

  • 첫 번째 옵션은 라이브러리가 특정 프로젝트에서 사용되고 팀 수준에서 규칙을 적용할 수 있다는 것을 확실히 알고 있습니다. 그러나 팀이 다른 접근 방식을 취하기로 결정하거나 함수 이름이 겹치는 여러 라이브러리를 사용하면 수많은 거짓 긍정에 대한 그다지 유쾌하지 않은 그림을 얻을 수 있습니다.

  • 두 번째 옵션은 라이브러리를 명확하게 가져온 파일을 검색하는 것입니다. 이 접근 방식을 사용하면 필요한 라이브러리가 이 파일에서 정확하게 사용되는지 확인할 수 있습니다.

  • 세 번째 옵션은 위의 두 가지 접근 방식을 함께 사용하는 것입니다.

예를 들어, 좁은 범위에서 잘 알려진 도서관을 살펴보겠습니다. 멋진 Scala 프로그래밍 언어의 경우, 즉 기능 리터럴 값 접합. 일반적으로 SQL 쿼리에 매개변수를 전달하려면 연산자를 사용해야 합니다. $, 미리 형성된 SQL 쿼리로 데이터를 대체합니다. 즉, 실제로 Java의 준비된 명령문과 직접적으로 유사합니다. 그러나 예를 들어 테이블 이름을 전달해야 하는 경우와 같이 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 고정을 확인하는 것은 드문 일이 아닙니다. 보안 관점에서 이러한 항목을 코드에 저장하는 것은 최선의 방법이 아닙니다. 저장소에서 유사한 파일을 검색하는 규칙을 작성해 보겠습니다.

// Найдем все сертификаты по маске файла
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 규칙을 개발하는 동안 새로운 아이디어를 수집할 수 있는 리소스가 부족합니다. 그게 우리가 만든 이유야 Github의 저장소, CxQL을 사용하는 모든 사람이 그 안에서 유용한 것을 찾을 수 있고 자신의 작업을 커뮤니티와 공유할 수 있는 기회를 가질 수 있도록 작업을 게시할 것입니다. 저장소는 콘텐츠를 채우고 구조화하는 과정에 있으므로 기여자를 환영합니다!

감사합니다!

출처 : habr.com

코멘트를 추가