HOME > 最新消息> 品牌新訊

Log4Shell──偵測 Log4j 漏洞 (CVE-2021-44228) 續

2021/12/29
更新:Log4j RCE

Splunk 的 SURGe 團隊撰寫了一篇部落格文章,並提供了 Splunk 相關產品的 Log4Shell 安全通告,這是一個讓藍隊徹夜難眠的 Log4j 漏洞。
在這篇部落格文章中,我們將提供其他有助於偵測出環境中潛在漏洞利用的指引。如果您還沒有記錄偵測出初始攻擊所需的所有內容,也無須氣餒。您還可以調查其他地方,確認主機是否已經成為攻擊目標。 

捕獵 Log4Shell 的行為
瑞士 CERT 發布了一篇有用的部落格文章,概述了這個漏洞利用的攻擊步驟。這張圖表中包含了一些關鍵的搜尋區域:
 



大多數偵測都是針對第 1 和第 2 階段,也就是攻擊者會向受攻擊的伺服器發出初始 JNDI 請求。
但如果您沒有記錄該資訊,那怎麼辦?此時,第 3 階段就是我們開始的好起點。我們可以在這裡使用兩個關鍵資料來源:網路流量和 DNS 查詢日誌。我們來看這兩個資料來源如何幫助我們在環境中找出受感染的主機。

使用 Splunk 偵測潛在的 Log4Shell (Log4j 2 RCE) 漏洞利用

入侵偵測警報
不要忘記您在整個環境中部署的 IDS。確保您已經更新規則,並在 Splunk 中為它們建立索引。這裡我們使用的是 Suricata,但作法同樣適用於任何已經為此漏洞提供特徵碼的 IDS。對這個索引進行快速搜尋,是一個尋找入侵跡象的起點:
index=suricata ("2021-44228" OR "Log4j" OR "Log4Shell") 
| table _time, dest_ip, alert.signature, alert.signature_id
 



偵測網路上的Outbound LDAP 存取
Outbound LDAP 流量是否可以流過您的外圍防火牆?應該不行。這可能是網路上一個 Log4Shell 的初始存取行為。這裡有一個利用 tstats 並結合 Splunk 最佳作法與網路流量 (Network Traffic) 資料模型的搜尋。這個搜尋可判斷您是否有任何 LDAP 連線到私有 (RFC1918) 地址空間之外的 IP 地址。
| tstats earliest(_time) as earliest_time latest(_time) as latest_time values(All_Traffic.dest_ip) from datamodel=Network_Traffic.All_Traffic where All_Traffic.dest_port = 1389 OR All_Traffic.dest_port = 389 OR All_Traffic.dest_port = 636 AND NOT (All_Traffic.dest_ip = 10.0.0.0/8 OR All_Traffic.dest_ip=192.168.0.0/16 OR All_Traffic.dest_ip = 172.16.0.0/12) by All_Traffic.src_ip
| convert ctime(earliest_time) ctime(latest_time)

JNDI 探測與 DNS 查詢的相關性
我們偵測到了 JNDI 字串,指出可能有試圖利用 Log4j 漏洞的攻擊。我們如何利用它成功找出攻擊行為?讓 DNS 上場。 
第一個搜尋使用正則表示式來擷取 JNDI 字串中的網域。然後更新到包含這些網域的查閱表格,我們將在後續搜尋中繼續使用這個表格。請記住,這個查詢可能需要花費較多 CPU 週期,因為它處理的對象是非結構化資料,所以需要一點時間,具體取決於您搜尋的資料量。首次執行這個搜尋時,您需要槓掉 (把它變成註解) 此查詢中的查閱行,以確保會先建立查閱檔案。
index=*  jndi
| rex field=_raw max_match=0 "[jJnNdDiI]{4}(\:|\%3A|\/|\%2F)(?\w+)(\:\/\/|\%3A\%2F\%2F)(\$\{.*?\}(\.)?)?(?[a-zA-Z0-9\.\-\_\$\{\:]+)"
| mvexpand rce_dest
| rex field=rce_dest "(?\d+\.\d+\.\d+\.\d+)"
| eval rce_domain = case(isnull(rce_ip),rce_dest)
| rex field=rce_domain "(?[0-9a-zA-A\-]+\.[0-9a-zA-A\-]+$)"
| dedup top_level_domain
| eval top_level_domain = "*.".top_level_domain
| where top_level_domain!=""
| lookup log4j_scanning_domain.csv query as top_level_domain OUTPUT query AS old_query
| where isnull(old_query)
| rename top_level_domain as query
| table query
| outputlookup append=t log4j_scanning_domain.csv

完成上述搜尋後,您可以獲得一個包含網域的查詢表格,並可以使用網路解析 (Network Resolution) 資料模型執行 tstats 搜尋,找出與 JNDI 探測中的網域符合的任何 DNS 查詢。

| tstats summariesonly=true allow_old_summaries=true
      values(host) as host, values(DNS.query_type) as DNS.query_type, values(DNS.reply_code) as DNS.reply_code, values(DNS.transport) as DNS.transport
      count from datamodel=Network_Resolution.DNS
      where [| inputlookup log4j_scanning_domain.csv | rename query as DNS.query | format] 
      by "DNS.src",sourcetype, DNS.query index _time span=1s
| stats earliest(_time) as first_seen, latest(_time) as last_seen sum(count) as count, values(DNS.reply_code) as DNS.reply_code, values(index) as index, values(DNS.src) as DNS.src, values(DNS.query_type) as DNS.query_type, values(DNS.transport) as DNS.transport by host DNS.query sourcetype
| convert timeformat="%m/%d/%Y %H:%M:%S" ctime(first_seen), ctime(last_seen)
 
新的Outbound Traffic偵測
您還可以搜尋在 2021 年 12 月 9 日之前,未產生Outbound Traffci的內部伺服器的流量。為此,請將時間範圍設為 2021 年 12 月 9 日之前至少 24 小時,納入一些標準流量以進行比較。這種行為搜尋範圍廣但緩慢,但好處是您可以盡可能擴大範圍來捕捉攻擊的跡象。以下是可幫助您開始的 SPL 搜尋命令:
index=* src_ip=* dest_ip=* 
(NOT (dest_category="internal" OR dest_ip=10.0.0.0/8 OR dest_ip=172.16.0.0/12 OR dest_ip=192.168.0.0/16 OR dest_ip=100.64.0.0/10))
| stats
earliest(_time) as earliest 
latest(_time) as latest 
values(action) as action 
values(app) as app 
values(dest_port) as dest_port 
values(sourcetype) as sourcetype count 
by src_ip dest_ip
| eventstats max(latest) as maxlatest
```This is 2021-12-09 00:00:00```
| eval comparisonTime="1639008000"
```| eval comparisonTime="-1d@d" ```
| eval isOutlier=if(earliest >= relative_time(maxlatest, comparisonTime), 1, 0)
| convert timeformat="%Y-%m-%dT%H:%M:%S" ctime(earliest),ctime(latest) ,ctime(maxlatest)
| where isOutlier=1

這個搜尋可有多種修改方式。
●    如修改 “comparisonTime” 加上昨天 (-1d@d),或是任意相對或絕對時間。 
●    修改 by 子句以排除 dest_ip。當您只想識別製造Outbound Traffic的伺服器時,這會非常有用。排除該欄位,就可以減少基數來提高搜尋效能。
●    這裡我們使用內置 SPL 註釋 (注意是三個 ` 符號)。如果您執行的是舊版本的 Splunk,這種作法可能行不通,只要直接刪除這些行即可。
●    如果您在網路流量資料模型上使用資料模型加速,可以將命令開關從 “summariesonly=false” 修改為 “summariesonly=true”,以提高搜尋的效能。 
 
| tstats summariesonly=false allow_old_summaries=true 
earliest(_time) as earliest 
latest(_time) as latest 
values(All_Traffic.action) as action 
values(All_Traffic.app) as app 
values(All_Traffic.dest_ip) as dest_ip 
values(All_Traffic.dest_port) as dest_port 
values(sourcetype) as sourcetype count 
from datamodel=Network_Traffic 
where (NOT (All_Traffic.dest_category="internal" OR All_Traffic.dest_ip=10.0.0.0/8 OR All_Traffic.dest_ip=172.16.0.0/12 OR All_Traffic.dest_ip=192.168.0.0/16 OR All_Traffic.dest_ip=100.64.0.0/10))
by All_Traffic.src_ip All_Traffic.dest_ip 
| rename "All_Traffic.*" as * 
| eventstats max(latest) as maxlatest
```This is 2021-12-09 00:00:00```
| eval comparisonTime="1639008000"
```| eval comparisonTime="-1d@d" ```
| eval isOutlier=if(earliest >= relative_time(maxlatest, comparisonTime), 1, 0)
| convert timeformat="%Y-%m-%dT%H:%M:%S" ctime(earliest),ctime(latest) ,ctime(maxlatest)
| where isOutlier=1
 
使用基線的新Outbound Traffic偵測
這是前一個 tstats 版本搜尋的變化。它經過修改,將使用儲存在查閱檔中的過去活動的基線。一開始必須使用更長的時間範圍進行搜尋來填充基線,然後就可以透過頻繁地執行查詢 (例如每小時一次) 來保持最新狀態。這種方法可使基線與您環境中的最新活動保持同步。 

注意:第一次執行此搜尋時,除非已經建立好查閱檔“egress_src_dest_tracker.csv”,否則將會發生錯誤。您可以手動建立同名的空查閱檔,或在暫時刪除此行後再執行搜尋來避免這種情形:
| lookup egress_src_dest_tracker.csv dest_ip src_ip OUTPUT earliest AS previous_earliest latest AS previous_latest
 

| tstats summariesonly=false allow_old_summaries=true 
    earliest(_time) as earliest 
    latest(_time) as latest 
    values(All_Traffic.action) as action 
    values(All_Traffic.app) as app 
    values(All_Traffic.dest_ip) as dest_ip 
    values(All_Traffic.dest_port) as dest_port 
    values(sourcetype) as sourcetype count 
    from datamodel=Network_Traffic 
    where (NOT (All_Traffic.dest_category="internal" OR All_Traffic.dest_ip=10.0.0.0/8 OR All_Traffic.dest_ip=172.16.0.0/12 OR All_Traffic.dest_ip=192.168.0.0/16 OR All_Traffic.dest_ip=100.64.0.0/10))
    by All_Traffic.src_ip All_Traffic.dest_ip 
| rename "All_Traffic.*" as * 
| lookup egress_src_dest_tracker.csv dest_ip src_ip OUTPUT earliest AS previous_earliest latest AS previous_latest 
| eval earliest=min(earliest, previous_earliest), latest=max(latest, previous_latest) 
| fields - previous_*
| appendpipe 
    [
    | fields src_ip dest_ip latest earliest
    | stats min(earliest) as earliest max(latest) as latest by src_ip, dest_ip 
    | inputlookup append=t egress_src_dest_tracker.csv
    | stats min(earliest) as earliest max(latest) as latest by src_ip, dest_ip 
    | outputlookup egress_src_dest_tracker.csv
    | where a=b
        ] 
| eventstats max(latest) as maxlatest
| eval comparisonTime="-1h@h" 
| eval isOutlier=if(earliest >= relative_time(maxlatest, comparisonTime), 1, 0) 
| convert timeformat="%Y-%m-%dT%H:%M:%S" ctime(earliest),ctime(latest) ,ctime(maxlatest)
| where isOutlier=1
 
使用基線的新國家/地區的Inbound Traffic偵測
這個技巧會使用前面搜尋用過的概念。不同之處在於它尋找連線到內部 IP 的外部 IP,並為外部 IP 加上位置資訊。這個查閱會根據來源、目的地和來源國家來進行。與之前的搜尋一樣,它會回傳很多結果,其中部分可能是誤報。您可以將此搜尋的範圍限制在某些感興趣的應用程式伺服器上,並可能要排除桌上型電腦。這個搜尋可能最適合於手動執行的搜尋查詢,直到您滿意它回傳的結果為止。 
最初必須使用更長的時間範圍進行搜尋來填充基線,然後就可以透過頻繁地執行查詢 (例如每小時一次) 來保持最新狀態。這種方法可使基線與您環境中的最新活動保持同步。 
注意:第一次執行此搜尋時,除非已經建立查閱檔“ingess_src_dest_country_tracker.csv”,否則將會發生錯誤。您可以手動建立同名的空查閱,或在暫時刪除此行後再執行搜尋來避免這種情況:
| lookup ingess_src_dest_country_tracker.csv dest_ip src_ip Country OUTPUT earliest AS previous_earliest latest AS previous_latest


| tstats summariesonly=false allow_old_summaries=true 
    earliest(_time) as earliest 
    latest(_time) as latest 
    values(All_Traffic.action) as action 
    values(All_Traffic.app) as app 
    values(All_Traffic.dest_ip) as dest_ip 
    values(All_Traffic.dest_port) as dest_port 
    values(sourcetype) as sourcetype count 
    from datamodel=Network_Traffic 
    where 
    (All_Traffic.dest_category="internal" OR All_Traffic.dest_ip=10.0.0.0/8 OR All_Traffic.dest_ip=172.16.0.0/12 OR All_Traffic.dest_ip=192.168.0.0/16 OR All_Traffic.dest_ip=100.64.0.0/10)
    AND (All_Traffic.src_category="external" OR (All_Traffic.src_ip!=10.0.0.0/8 AND All_Traffic.src_ip!=172.16.0.0/12 AND All_Traffic.src_ip!=192.168.0.0/16 AND All_Traffic.src_ip!=100.64.0.0/10))
    by All_Traffic.src_ip All_Traffic.dest_ip 
| rename "All_Traffic.*" as * 
| iplocation src_ip
| lookup ingess_src_dest_country_tracker.csv dest_ip src_ip Country OUTPUT earliest AS previous_earliest latest AS previous_latest 
| eval earliest=min(earliest, previous_earliest), latest=max(latest, previous_latest) 
| fields - previous_*
| appendpipe 
    [
    | fields src_ip dest_ip Country latest earliest
    | stats min(earliest) as earliest max(latest) as latest by src_ip, dest_ip, Country
    | inputlookup append=t ingess_src_dest_country_tracker.csv
    | stats min(earliest) as earliest max(latest) as latest by src_ip, dest_ip, Country 
    | outputlookup ingess_src_dest_country_tracker.csv
    | where a=b
        ] 
| eventstats max(latest) as maxlatest
| eval comparisonTime="-1h@h" 
| eval isOutlier=if(earliest >= relative_time(maxlatest, comparisonTime), 1, 0) 
| convert timeformat="%Y-%m-%dT%H:%M:%S" ctime(earliest),ctime(latest) ,ctime(maxlatest)
| where isOutlier=1

結論仍然是修補、修補、修補
修補仍然是對抗這個漏洞的最佳選擇。如果無法進行修補,那麼實施緩解技術將是大幅縮小受攻擊面的第二條路。SURGe 正在監控此漏洞的變化,並將根據需要提供其他資訊。此外,Splunk 的威脅研究團隊 一直致力於為 ESCU 建立新的偵測以及可用於自動回應的 SOAR 劇本,並將盡快發布。 
________________________________________

Splunk 的 SURGe 是一個安全研究團隊,致力於提供專家分析和見解,幫助客戶發現、調查和應對新出現的威脅。請註冊 SURGe Alerts 以獲得安全研究和技術指引。
 
作者
Marcus LaFerrera
US | JP | Pentagon | DARPA | Splunk