1.搭建samba環境
注意:在Linux系統上使用源碼編譯來安裝samba必須要將系統自帶的全部關於samba的軟件均刪除,可用如下命令查看已安裝的samba軟件:
[bb@localhost bin]$ rpm -qa | grep samba
samba-common-3.6.23-14.el6_6.x86_64
samba-winbind-3.6.23-14.el6_6.x86_64
samba-winbind-clients-3.6.23-14.el6_6.x86_64
samba-client-3.6.23-14.el6_6.x86_64
然後直接全部刪除:
[bb@localhost bin]$ rpm -qa | grep samba | xargs sudo rpm -e --nodeps
刪除完成之後從https://download.samba.org/pub/samba/stable/samba-3.6.23.tar.gz下載源碼,解壓後進入source3目錄,先執行./configure進行配置,然後對Makefile文件稍作更改,在編譯選項中加入-g以便gdb對其進行調試。隨後就是make編譯,make install安裝,默認安裝路徑是/usr/local/samba,安裝後目錄如下:
[bb@localhost samba]$ ls
bin include lib private sbin share swat var
首先進入lib目錄,將源碼中example目錄下的smb.conf.dufault文件複製到當前目錄並改名爲smb.conf;然後執行以下命令:
[bb@localhost bin]$ sudo vim /etc/ld.so.conf
在打開的文件中添加一行
/usr/local/samba/lib
退出並執行ldconfig更新動態鏈接庫緩存。然後就是到/usr/local/samba/bin目錄下執行:
[bb@localhost bin]$ sudo ./smbpasswd -a bb
New SMB password:
Retype new SMB password:
Added user bb.
然後需要關閉防火牆:
[bb@localhost ~]$ sudo service iptables stop
[sudo] password for bb:
iptables: Flushing firewall rules: [ OK ]
iptables: Setting chains to policy ACCEPT: filter [ OK ]
iptables: Unloading modules: [ OK ]
最後則是到sbin目錄下啓動smbd和nmbd程序,完成後可以看到進程如下:
[bb@localhost sbin]$ pgrep smbd
6359
6360
[bb@localhost sbin]$ pgrep nmbd
6365
然後我在Windows上登陸便會提示輸入用戶名和密碼,正確之後便能訪問bb用戶的目錄文件:
2.源碼分析過程
首先是在redhat的博客https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/上看到了漏洞的代碼是在_netr_ServerPasswordSet函數中,如下圖所示:
其中第一方框處事creds的聲明的地方,這裏並未對其進行初始化,第二處將creds的地址作爲參數,想必是對其進行初始化,第三處則是當第二處的函數netr_creds_server_step_check返回錯誤也就是驗證creds失敗則會將creds指向的內存釋放,但是釋放之前並未對creds指針進行有效性檢查。那什麼條件會使得netr_creds_server_step_check函數運行失敗呢?
下面是netr_creds_server_step_check的代碼:
顯然schannel_check_creds_state函數用來初始化creds結構指針的,繼續看這個函數:
帶有方框的兩部分都是受客戶端的控制並可能導致驗證失敗的地方,第一處只要客戶端的機器沒有在服務器上驗證通過過就會觸發(可以是一個新的計算機名或是僞造一個計算機名),第二處只要隨意僞造一個憑證即可觸發。現在需要驗證這兩種情況的可能性。
3.驗證POC
在github上https://gist.github.com/worawit/33cc5534cb555a0b710b看到了一個poc。
3.1
首先驗證第一個原因,也就是未經過驗證的計算機導致崩潰。運行samba服務端,使用gdb attach上去然後在_netr_ServerPasswordSet函數處下斷點:
(gdb) c
Continuing.
Breakpoint 1, _netr_ServerPasswordSet (p=0x7f1ceeb92d10, r=0x7f1ceeb96520)
at rpc_server/netlogon/srv_netlog_nt.c:1205
1205 {
一直運行到根據主機名獲取相應憑證的函數,可以看到這裏的計算機名就是客戶端的ip地址192.168.1.132:
(gdb) s
schannel_fetch_session_key_tdb (tdb_sc=0x7fa334355f30, mem_ctx=0x7fa334355e40,
computer_name=0x7fa33436d890 "192.168.1.132", pcreds=0x7fff538e2ba8)
at ../libcli/auth/schannel_state_tdb.c:128
隨後構造一個字符串:
(gdb) n
144 keystr = talloc_asprintf(mem_ctx, "%s/%s",
(gdb) p keystr
$15 = 0x7fa334360650 "SECRETS/SCHANNEL/192.168.1.132"
然後根據這個字符串從tdb中獲取相應的憑證:
(gdb) n
151 value = tdb_fetch_bystring(tdb_sc->tdb, keystr);
繼續跟蹤可以看到其計算hash值的算法:
/* This is based on the hash algorithm from gdbm */
unsigned int tdb_old_hash(TDB_DATA *key)
{
uint32_t value; /* Used to compute the hash value. */
uint32_t i; /* Used to cycle through random values. */
/* Set the initial value from the key size. */
for (value = 0x238F13AF * key->dsize, i=0; i < key->dsize; i++)
value = (value + (key->dptr[i] << (i*5 % 24)));
return (1103515243 * value + 12345);
}
查看運行返回的值已經是NULL了,隨後返回失敗的結果,指向creds的指針並沒有初始化:
(gdb) n
152 if (!value.dptr) {
(gdb) p value
$16 = {dptr = 0x0, dsize = <value optimized out>}
最後則是在釋放指針的時候崩潰:
(gdb) n
1224 TALLOC_FREE(creds);
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x00007fa331e572f0 in talloc_parent_chunk (ptr=<value optimized out>) at ../lib/talloc/talloc.c:407
407 while (tc->prev) tc=tc->prev;
3.2
接下去是使用驗證過的計算機但將其憑證修改爲其他值,這就需要先驗證通過,http://www.freebuf.com/vuls/59898.html和https://technet.microsoft.com/zh-tw/exchange/cc237127中都表明需要使用netlogon服務都必須先通過NetrServerAuthenticate驗證。代碼如下所示:
但是經過多次嘗試和改變,服務器端一直都是密碼錯誤,不知爲何:
所以第二種方法就暫時無法驗證。
4.修復方法
知道了原因修復起來就簡單多了,直接在creds聲明的時候初始化爲NULL,然後在釋放前判斷其是否爲NULL即可。