目前的隱蔽利用只偽裝了網頁請求,IP 位址與時間戳仍被寫進日誌。這讓攻擊較難發現,卻非隱形——被寫進可能保存多年的日誌的 IP 位址,未來恐招來麻煩。既然我們正在 tinyweb daemon 內部搗弄,應該能把行蹤藏得更好。
偽造被記錄的 IP 位址#
寫進日誌的 IP 來自傳給 handle_connection() 的 client_addr_ptr:
sprintf(log_buffer, "From %s:%d \"%s\"\t",
inet_ntoa(client_addr_ptr->sin_addr),
ntohs(client_addr_ptr->sin_port), request);要偽造 IP,只需注入自己的
sockaddr_in結構、並覆寫client_addr_ptr指向它。生成結構最好的辦法是寫一個小 C 程式(addr_struct.c),用命令列引數建結構、直接寫到標準輸出:
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
write(1, &addr, sizeof(struct sockaddr_in));把假結構注入在假請求之後、NOP sled 之前。假請求 15 位元組、緩衝區起於 0xbffff5c0,所以假位址注入在 0xbffff5cf。
(gdb) x/s log_buffer
0xbffff1c0: "From 12.34.56.78:9090 \"GET / HTTP/1.1\"\t"
無日誌利用#
理想上我們想完全不留痕跡。若日誌被鏡像到最小存取的安全日誌伺服器、甚至行式印表機,事後刪除就不是選項。timestamp() 函式試圖直接寫入開啟的檔案描述元以求安全——我們無法阻止它被呼叫、也無法還原它的寫入。這本是相當有效的對策,卻實作得很糟。
雖然
logfd是全域變數,它也作為函式引數傳給handle_connection()——這建立了另一個同名的堆疊變數。由於這個引數在堆疊上緊跟client_addr_ptr,它被漏洞利用緩衝區結尾的 null 終止符與多出的0x0a位元組部分覆寫了。
(gdb) x/4xb &logfd
0xbffff7e8: 0x00 0x0a 0x00 0x00 # logfd 變成 0x00000a00 = 2560
只要日誌檔描述元不巧是 2560,每次
handle_connection()試圖寫日誌都會失敗。用strace -e trace=write可見寫入回傳-1 EBADF (Bad file descriptor)。但這會產生錯誤——不夠優雅。
由於 tinyweb daemon 把標準輸出重導到 /dev/null,xtool_tinywebd_silent.sh 把傳入的 logfd 覆寫成 1(標準輸出):在漏洞利用緩衝區尾端加 \x01\x00\x00\x00。這樣仍阻止日誌寫入,卻乾淨無誤(一切寫到 /dev/null)。
(gdb) # 覆寫後 logfd = 1
write(1, "09/19/2007 23:36:31> ", 21) = 21 # 乾淨執行,寫到 /dev/null
日誌檔的大小與存取時間都保持不變。用這技術,我們能利用 tinywebd 而在日誌中不留任何痕跡,且 write 呼叫乾淨執行。