Zaawansowane najlepsze rozwiązania dotyczące zapytań dotyczących wyszukiwania zagrożeń

Uwaga

Chcesz skorzystać z usługi Microsoft Defender XDR? Dowiedz się więcej o tym, jak można oceniać i pilotować usługę Microsoft Defender XDR.

Dotyczy:

  • Microsoft Defender XDR

Zastosuj te zalecenia, aby szybciej uzyskać wyniki i uniknąć przekroczenia limitu czasu podczas uruchamiania złożonych zapytań. Aby uzyskać więcej wskazówek dotyczących poprawy wydajności zapytań, przeczytaj Najlepsze rozwiązania dotyczące zapytań Kusto.

Omówienie limitów przydziału zasobów procesora CPU

W zależności od rozmiaru każda dzierżawa ma dostęp do określonej ilości zasobów procesora CPU przydzielonych do uruchamiania zaawansowanych zapytań wyszukiwania zagrożeń. Aby uzyskać szczegółowe informacje o różnych parametrach użycia, przeczytaj o zaawansowanych limitach przydziałów zagrożeń i parametrach użycia.

Po uruchomieniu zapytania można zobaczyć czas wykonywania i jego użycie zasobów (niski, średni, wysoki). Wysoka wartość wskazuje, że zapytanie zajęło więcej zasobów do uruchomienia i można je ulepszyć w celu wydajniejszego zwracania wyników.

Szczegóły zapytania na karcie **Wyniki** w portalu Microsoft Defender

Klienci, którzy regularnie uruchamiają wiele zapytań, powinni śledzić użycie i stosować wskazówki dotyczące optymalizacji w tym artykule, aby zminimalizować zakłócenia wynikające z przekroczenia limitów przydziału lub parametrów użycia.

Obejrzyj optymalizowanie zapytań KQL , aby zobaczyć niektóre z najpopularniejszych sposobów ulepszania zapytań.

Ogólne porady dotyczące optymalizacji

  • Rozmiar nowych zapytań — jeśli podejrzewasz, że zapytanie zwróci duży zestaw wyników, najpierw oceń je przy użyciu operatora count. Użyj limitu lub jego synonimu take , aby uniknąć dużych zestawów wyników.

  • Zastosuj filtry wcześnie — zastosuj filtry czasu i inne filtry, aby zmniejszyć zestaw danych, zwłaszcza przed użyciem funkcji przekształcania i analizowania, takich jak podciąg(), replace(), trim(), toupper()lub parse_json(). W poniższym przykładzie funkcja analizowania extractjson() jest używana po zmniejszeniu liczby rekordów przez operatory filtrowania.

    DeviceEvents
    | where Timestamp > ago(1d)
    | where ActionType == "UsbDriveMount"
    | where DeviceName == "user-desktop.domain.com"
    | extend DriveLetter = extractjson("$.DriveLetter", AdditionalFields)
    
  • Zawiera bity — aby uniknąć niepotrzebnego wyszukiwania podciągów w słowach, użyj has operatora zamiast contains. Dowiedz się więcej o operatorach ciągów

  • Wyszukiwanie w określonych kolumnach — wyszukiwanie w określonej kolumnie zamiast wykonywania wyszukiwania pełnotekstowego we wszystkich kolumnach. Nie używaj do * sprawdzania wszystkich kolumn.

  • Wielkość liter dla szybkości — wyszukiwania uwzględniające wielkość liter są bardziej szczegółowe i ogólnie bardziej wydajne. Nazwy operatorów ciągów uwzględniających wielkość liter, takich jak has_cs i contains_cs, zwykle kończą się ciągiem _cs. Można również użyć operatora == równości uwzględniania wielkości liter zamiast =~.

  • Analizuj, nie wyodrębniaj — jeśli to możliwe, użyj operatora analizy lub funkcji analizy, takiej jak parse_json(). matches regex Unikaj operatora ciągu lub funkcji extract(), które używają wyrażenia regularnego. Zarezerwuj użycie wyrażenia regularnego w bardziej złożonych scenariuszach. Przeczytaj więcej na temat analizowania funkcji

  • Nie filtruj tabel — nie filtruj kolumny obliczeniowej, jeśli możesz filtrować kolumnę tabeli.

  • Brak terminów trzyznakowych — unikaj porównywania lub filtrowania przy użyciu terminów z trzema znakami lub mniejszą liczbą znaków. Te terminy nie są indeksowane, a ich dopasowanie będzie wymagało większej ilości zasobów.

  • Selektywne projektowanie — ułatwić zrozumienie wyników przez projekcję tylko potrzebnych kolumn. Projekcja określonych kolumn przed uruchomieniem sprzężenia lub podobnych operacji również pomaga zwiększyć wydajność.

Optymalizowanie join operatora

Operator sprzężenia scala wiersze z dwóch tabel, dopasowując wartości w określonych kolumnach. Zastosuj te wskazówki, aby zoptymalizować zapytania korzystające z tego operatora.

  • Mniejsza tabela po lewej stroniejoin operator dopasowuje rekordy w tabeli po lewej stronie instrukcji join do rekordów po prawej stronie. Mając mniejszą tabelę po lewej stronie, należy dopasować mniej rekordów, co przyspieszy zapytanie.

    W poniższej tabeli zmniejszamy tabelę DeviceLogonEvents po lewej stronie, aby objąć tylko trzy określone urządzenia przed dołączeniem IdentityLogonEvents jej do niej według identyfikatorów SID konta.

    DeviceLogonEvents
    | where DeviceName in ("device-1.domain.com", "device-2.domain.com", "device-3.domain.com")
    | where ActionType == "LogonFailed"
    | join
        (IdentityLogonEvents
        | where ActionType == "LogonFailed"
        | where Protocol == "Kerberos")
    on AccountSid
    
  • Użyj smaku sprzężenia wewnętrznego — domyślny smak sprzężenia lub innerunique-join deduplikuje wiersze w lewej tabeli przy użyciu klucza sprzężenia przed zwróceniem wiersza dla każdego dopasowania do prawej tabeli. Jeśli tabela po lewej stronie zawiera wiele wierszy o tej samej wartości dla join klucza, te wiersze zostaną deduplikowane, aby pozostawić pojedynczy losowy wiersz dla każdej unikatowej wartości.

    To domyślne zachowanie może pominąć ważne informacje z lewej tabeli, które mogą dostarczyć przydatnych szczegółowych informacji. Na przykład poniższe zapytanie będzie zawierać tylko jedną wiadomość e-mail zawierającą określony załącznik, nawet jeśli ten sam załącznik został wysłany przy użyciu wielu wiadomości e-mail:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    

    Aby rozwiązać ten problem, stosujemy smak sprzężenia wewnętrznego , określając kind=inner , aby wyświetlić wszystkie wiersze w lewej tabeli z pasującymi wartościami po prawej stronie:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Dołączanie rekordów z przedziału czasu — podczas badania zdarzeń zabezpieczeń analitycy poszukają powiązanych zdarzeń występujących w tym samym okresie. Zastosowanie tego samego podejścia w przypadku korzystania join z tej samej metody zapewnia również korzyści z wydajności dzięki zmniejszeniu liczby rekordów do sprawdzenia.

    Poniższe zapytanie sprawdza zdarzenia logowania w ciągu 30 minut od otrzymania złośliwego pliku:

    EmailEvents
    | where Timestamp > ago(7d)
    | where ThreatTypes has "Malware"
    | project EmailReceivedTime = Timestamp, Subject, SenderFromAddress, AccountName = tostring(split(RecipientEmailAddress, "@")[0])
    | join (
    DeviceLogonEvents
    | where Timestamp > ago(7d)
    | project LogonTime = Timestamp, AccountName, DeviceName
    ) on AccountName
    | where (LogonTime - EmailReceivedTime) between (0min .. 30min)
    
  • Stosowanie filtrów czasu po obu stronach — nawet jeśli nie badasz określonego przedziału czasu, zastosowanie filtrów czasu w tabelach po lewej i prawej stronie może zmniejszyć liczbę rekordów w celu sprawdzenia i zwiększenia join wydajności. Poniższe zapytanie dotyczy Timestamp > ago(1h) obu tabel, dzięki czemu łączy tylko rekordy z ostatniej godziny:

    EmailAttachmentInfo
    | where Timestamp > ago(1h)
    | where Subject == "Document Attachment" and FileName == "Document.pdf"
    | join kind=inner (DeviceFileEvents | where Timestamp > ago(1h)) on SHA256
    
  • Użyj wskazówek dotyczących wydajności — użyj wskazówek z operatorem, join aby poinstruować zaplecze o rozłożeniu obciążenia podczas wykonywania operacji intensywnie korzystających z zasobów. Dowiedz się więcej o wskazówkach dotyczących dołączania

    Na przykład wskazówki dotyczące mieszania pomagają zwiększyć wydajność zapytań podczas łączenia tabel przy użyciu klucza o wysokiej kardynalności — klucza z wieloma unikatowymi wartościami, na przykład AccountObjectId w poniższym zapytaniu:

    IdentityInfo
    | where JobTitle == "CONSULTANT"
    | join hint.shufflekey = AccountObjectId
    (IdentityDirectoryEvents
        | where Application == "Active Directory"
        | where ActionType == "Private data retrieval")
    on AccountObjectId
    

    Wskazówka dotycząca emisji pomaga, gdy tabela po lewej stronie jest mała (do 100 000 rekordów), a prawa tabela jest bardzo duża. Na przykład poniższe zapytanie próbuje dołączyć kilka wiadomości e-mail, które mają określone tematy ze wszystkimi wiadomościami zawierającymi linki w EmailUrlInfo tabeli:

    EmailEvents
    | where Subject in ("Warning: Update your credentials now", "Action required: Update your credentials now")
    | join hint.strategy = broadcast EmailUrlInfo on NetworkMessageId
    

Optymalizowanie summarize operatora

Operator podsumowania agreguje zawartość tabeli. Zastosuj te wskazówki, aby zoptymalizować zapytania korzystające z tego operatora.

  • Znajdź odrębne wartości — ogólnie rzecz biorąc, użyj polecenia summarize , aby znaleźć różne wartości, które mogą być powtarzalne. Użycie go do agregowania kolumn, które nie mają powtarzających się wartości, może być niepotrzebne.

    Chociaż pojedyncza wiadomość e-mail może być częścią wielu zdarzeń, poniższy przykład nie jest efektywnym użyciem summarize , ponieważ identyfikator wiadomości sieciowej dla pojedynczej wiadomości e-mail zawsze zawiera unikatowy adres nadawcy.

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize by NetworkMessageId, SenderFromAddress
    

    Operator summarize można łatwo zastąpić elementem project, uzyskując potencjalnie te same wyniki, zużywając mniej zasobów:

    EmailEvents
    | where Timestamp > ago(1h)
    | project NetworkMessageId, SenderFromAddress
    

    Poniższy przykład jest bardziej wydajnym zastosowaniem, summarize ponieważ może istnieć wiele różnych wystąpień adresu nadawcy wysyłającego wiadomość e-mail na ten sam adres adresata. Takie kombinacje są mniej odrębne i mogą mieć duplikaty.

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize by SenderFromAddress, RecipientEmailAddress
    
  • Mieszanie zapytania — podczas gdy summarize najlepiej używać w kolumnach z powtarzającymi się wartościami, te same kolumny mogą mieć również wysoką kardynalność lub dużą liczbę unikatowych wartości. join Podobnie jak operator, możesz również zastosować wskazówkę mieszania, summarize aby rozłożyć obciążenie przetwarzania i potencjalnie zwiększyć wydajność podczas pracy na kolumnach o wysokiej kardynalności.

    Poniższe zapytanie używa summarize do zliczania odrębnego adresu e-mail adresata, który może być uruchamiany w setkach tysięcy w dużych organizacjach. Aby zwiększyć wydajność, obejmuje hint.shufflekeyona:

    EmailEvents
    | where Timestamp > ago(1h)
    | summarize hint.shufflekey = RecipientEmailAddress count() by Subject, RecipientEmailAddress
    

Scenariusze zapytań

Identyfikowanie unikatowych procesów przy użyciu identyfikatorów procesów

Identyfikatory procesów (PID) są poddawane recyklingowi w systemie Windows i ponownie używane w nowych procesach. Same w sobie nie mogą służyć jako unikatowe identyfikatory dla określonych procesów.

Aby uzyskać unikatowy identyfikator procesu na określonej maszynie, użyj identyfikatora procesu wraz z czasem tworzenia procesu. Podczas dołączania lub podsumowywania danych dotyczących procesów dołącz kolumny identyfikatora maszyny ( DeviceId lub DeviceName), identyfikator procesu (ProcessId lub InitiatingProcessId), a czas tworzenia procesu (ProcessCreationTime lub InitiatingProcessCreationTime)

Poniższe przykładowe zapytanie znajduje procesy, które uzyskują dostęp do ponad 10 adresów IP za pośrednictwem portu 445 (SMB), prawdopodobnie skanując udziały plików.

Przykładowe zapytanie:

DeviceNetworkEvents
| where RemotePort == 445 and Timestamp > ago(12h) and InitiatingProcessId !in (0, 4)
| summarize RemoteIPCount=dcount(RemoteIP) by DeviceName, InitiatingProcessId, InitiatingProcessCreationTime, InitiatingProcessFileName
| where RemoteIPCount > 10

Zapytanie jest podsumowywane zarówno InitiatingProcessId przez, jak i InitiatingProcessCreationTime w taki sposób, że analizuje jeden proces bez mieszania wielu procesów z tym samym identyfikatorem procesu.

Wiersze poleceń zapytania

Istnieje wiele sposobów konstruowania wiersza polecenia w celu wykonania zadania. Na przykład osoba atakująca może odwoływać się do pliku obrazu bez ścieżki, bez rozszerzenia pliku, przy użyciu zmiennych środowiskowych lub z cudzysłowami. Osoba atakująca może również zmienić kolejność parametrów lub dodać wiele cudzysłowów i spacji.

Aby utworzyć bardziej trwałe zapytania wokół wierszy poleceń, zastosuj następujące rozwiązania:

  • Zidentyfikuj znane procesy (takie jak net.exe lub psexec.exe), dopasowując je do pól nazw plików, zamiast filtrować w samym wierszu polecenia.
  • Analizowanie sekcji wiersza polecenia przy użyciu funkcji parse_command_line()
  • Podczas wykonywania zapytań o argumenty wiersza polecenia nie szukaj dokładnego dopasowania dla wielu niepowiązanych argumentów w określonej kolejności. Zamiast tego użyj wyrażeń regularnych lub użyj wielu oddzielnych operatorów zawiera.
  • Używanie dopasowań bez uwzględniania wielkości liter. Na przykład użyj wartości =~, in~i contains zamiast ==, ini contains_cs.
  • Aby wyeliminować techniki zaciemniania wiersza polecenia, rozważ usunięcie cudzysłowów, zastąpienie przecinków spacjami i zastąpienie wielu kolejnych spacji pojedynczym spacji. Istnieją bardziej złożone techniki zaciemniania, które wymagają innych metod, ale te poprawki mogą pomóc w rozwiązaniu typowych z nich.

W poniższych przykładach przedstawiono różne sposoby konstruowania zapytania, które szuka pliku net.exe zatrzymania usługi zapory "MpsSvc":

// Non-durable query - do not use
DeviceProcessEvents
| where ProcessCommandLine == "net stop MpsSvc"
| limit 10

// Better query - filters on file name, does case-insensitive matches
DeviceProcessEvents
| where Timestamp > ago(7d) and FileName in~ ("net.exe", "net1.exe") and ProcessCommandLine contains "stop" and ProcessCommandLine contains "MpsSvc"

// Best query also ignores quotes
DeviceProcessEvents
| where Timestamp > ago(7d) and FileName in~ ("net.exe", "net1.exe")
| extend CanonicalCommandLine=replace("\"", "", ProcessCommandLine)
| where CanonicalCommandLine contains "stop" and CanonicalCommandLine contains "MpsSvc"

Pozyskiwanie danych ze źródeł zewnętrznych

Aby uwzględnić długie listy lub duże tabele w zapytaniu, użyj operatora externaldata do pozyskiwania danych z określonego identyfikatora URI. Dane z plików można pobierać w formatach TXT, CSV, JSON lub innych. W poniższym przykładzie pokazano, jak można użyć obszernej listy skrótów sha-256 złośliwego oprogramowania dostarczonych przez malwareBazaar (abuse.ch) do sprawdzania załączników w wiadomościach e-mail:

let abuse_sha256 = (externaldata(sha256_hash: string)
[@"https://bazaar.abuse.ch/export/txt/sha256/recent/"]
with (format="txt"))
| where sha256_hash !startswith "#"
| project sha256_hash;
abuse_sha256
| join (EmailAttachmentInfo
| where Timestamp > ago(1d)
) on $left.sha256_hash == $right.SHA256
| project Timestamp,SenderFromAddress,RecipientEmailAddress,FileName,FileType,
SHA256,ThreatTypes,DetectionMethods

Analizowanie ciągów

Istnieją różne funkcje, których można użyć do wydajnego obsługi ciągów, które wymagają analizowania lub konwersji.

Ciąg Funkcja Przykład użycia
Wiersze polecenia parse_command_line() Wyodrębnij polecenie i wszystkie argumenty.
Ścieżki parse_path() Wyodrębnij sekcje ścieżki pliku lub folderu.
Numery wersji parse_version() Zdekonstruuj numer wersji z maksymalnie czterema sekcjami i maksymalnie ośmioma znakami na sekcję. Użyj przeanalizowanych danych, aby porównać wiek wersji.
Adresy IPv4 parse_ipv4() Konwertuj adres IPv4 na długą liczbę całkowitą. Aby porównać adresy IPv4 bez ich konwertowania, użyj ipv4_compare().
Adresy IPv6 parse_ipv6() Przekonwertuj adres IPv4 lub IPv6 na kanoniczną notację IPv6. Aby porównać adresy IPv6, użyj ipv6_compare().

Aby dowiedzieć się więcej o wszystkich obsługiwanych funkcjach analizowania, przeczytaj o funkcjach ciągu Kusto.

Uwaga

Niektóre tabele w tym artykule mogą nie być dostępne w Ochrona punktu końcowego w usłudze Microsoft Defender. Włącz Microsoft Defender XDR, aby wyszukiwać zagrożenia przy użyciu większej liczby źródeł danych. Zaawansowane przepływy pracy wyszukiwania zagrożeń można przenieść z Ochrona punktu końcowego w usłudze Microsoft Defender do Microsoft Defender XDR, wykonując kroki opisane w temacie Migrowanie zaawansowanych zapytań wyszukiwania zagrożeń z Ochrona punktu końcowego w usłudze Microsoft Defender.

Porada

Chcesz dowiedzieć się więcej? Zaangażuj się w społeczność rozwiązań zabezpieczających firmy Microsoft w naszej społeczności technicznej Społeczność techniczna usługi Microsoft Defender XDR.