目前的隱蔽利用只偽裝了網頁請求,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/nullxtool_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 呼叫乾淨執行。