歡迎您光臨本站 註冊首頁

BIND8安全漏洞分析

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  對BIND幾個缺陷的分析
綜述
現在隨著Internet的日益普及,而Internet非常依賴於域名服務(DNS)。在RFC845中對域名服務作了如下定義:一個迭代的分散式資料庫系統,它為Internet操作提供了基本的信息,例如:域名<-->IP地址的相互轉換,郵件處理信息。BIND(Berkeley Inetnet Name Domain,伯克利Internet域名是一種使用最廣的域名系統。它有安全缺陷對Internet無疑於是一場災難。
2001年月29日,Network Associates of California發表了一個報告,指出了BIND最近出現的四個安全缺陷。其中有兩個是關於緩衝區溢出,可以使攻擊者關閉DNS或者獲得root許可權,一個叫做"TSIG bug",影響BIND8,另一個是叫「complain bug"的緩衝區溢出缺陷,影響BIND4。其餘兩個一個叫做"infoleak",影響BIND4和BIND8,另一個叫做"complain bug"格式化字元串缺陷,隻影響BIND4。本文將著重講述infoleak和TSIG bug。其中,infoleak bug不能直接用來進行攻擊,但是它可以泄露棧的信息,甚至使攻擊者得到BIND運行時的內存布局,為使用TSIG進行攻擊創造了便利。這恐怕也是最近兩個蠕蟲:lion和adore都使用BIND的漏洞進行傳播的主要原因之一。

細節
1.infoleak
infoleak bug是由Claudio Musmarra發現的,最早在CERT安全建議CA-2001-02對這個BUG進行了報道。它使攻擊者能夠直接得到named程序棧禎的信息,從而直接計算出進行單位元組緩衝區溢出所需要的信息,大大增加了攻擊的成功率。
程序執行時,在棧中保存了程序運行的內部變數和函數的局部變數,以及函數調用的返回地址等信息。infoleak bug可以使攻擊者直接讀出在棧中的這些信息,甚至程序運行時的內存布局。通過向運行有這個缺陷的BIND版本的DNS伺服器發送一個特製的查詢包,就可以達成上述目的。
所謂特製的查詢包就是向一個合法的很大的IQUERY(反向查詢)查詢包。向一個運行BIND的DNS伺服器發出一個合法的IQUERY請求,DNS伺服器把應答記錄放在這個查詢包之後返回。應答包括一個域名、類型(type)、類別(class)和ttl(包的生存時間)。在構造這個反向查詢包時,只要使域名對named程序的dn_skipname()函數是合法的就可以了。把這個反向查詢包的數據長度設置為一個和很大的數值,就會是應答記錄超出緩衝區的邊界。named程序的req_iquery()函數會發現這個反向查詢包非法,並且返回一個指示錯誤的字元串。不幸的是,它在檢查是否有錯誤時,不管反向查詢包的數據區有多長,首先把指向包尾的指針cp向後推,這樣很可能使cp指針超出了緩衝區的邊界。從req_iquery()函數返回后,ns_req()函數就會發出大小是cp-msg(指向緩衝區的頭)個位元組含有錯誤信息的應答包。如果這個應答包已經超出了緩衝區的大小,就會包含named程序當前棧禎的信息如ebp等等,然後攻擊者就可以使用TSIG安全缺陷進行單位元組緩衝區溢出攻擊了。
因為相對於TSIG安全缺陷關於infoleak的分析資料較少,所以我將以bind-8.2為例對infoleak進行分析。BIND在查詢包小於512個位元組時,使用UDP/53埠接受數據(更詳細的信息請參考TSIG部分),具體接受數據的函數就是datagram_read(),以下是datagram_read()函數的相關源代碼
static void
datagram_read(evContext lev, void *uap, int fd, int evmask) {
interface *ifp = uap;
struct sockaddr_in from;
int from_len = sizeof from;
int n, nudp;
union {
HEADER h; /* Force alignment of uf. */
u_char buf[PACKETSZ+1];
} u;<--這就是named函數存放小於512個位元組的查詢包的緩衝區,後面對於查詢的處理操作都是針對於這個緩衝區的,也就是說,datagram_read是使用傳址方式把查詢包傳遞給以後的處理函數*/
..................
dispatch_message(u.buf, n, PACKETSZ, NULL, from, fd, ifp);
if (++nudp < nudptrans)
goto more;
}

這時,棧的布局如下:
------------------
|參數 |
| |
| |
------------------
| |
| 返回地址 |
------------------
|ebp |
------------------
|各個局部變數 |
-----------------
|u.buff[513] |
-----------------
|u.buff[512] |
-----------------
| ..... |
-----------------
|u.buff[0] |<----緩衝區
-----------------
接著,dispatch_message函數調用ns_req()函數:
void
ns_req(u_char *msg, int msglen, int buflen, struct qstream *qsp,
struct sockaddr_in from, int dfd)
{
HEADER *hp = (HEADER *) msg;
u_char *cp, *eom;/*<---cp指向請求包的數據區*/
/*cp = msg + HFIXEDSZ*/
/*eom指向請求包的尾*/
/*eom = msg + msglen*/
................

if (error == NOERROR) {
switch (hp->opcode) {
case ns_o_query:
action = req_query(hp, &cp, eom, qsp,
&buflen, &msglen,
msg, dfd, from, in_tsig);
break;

case ns_o_iquery:
action = req_iquery(hp, &cp, eom, &buflen, msg, from);
break;
/*反向請求包由req_iquery函數處理*/

此時,棧如圖所示:
------------------
|參數 |
| |
| |
------------------
| |
| 返回地址 |
------------------
|ebp |
------------------
|各個局部變數 |
-----------------<----eom
|u.buff[513] |
-----------------
|u.buff[512] |
-----------------
| ..... |
-----------------
|u.buff[12] |
-----------------<----cp
| ..... |
-----------------
|u.buff[0] |
-----------------<---msg
下面是req_iquery()函數:
static enum req_action
req_iquery(HEADER *hp, u_char **cpp, u_char *eom, int *buflenp,
u_char *msg, struct sockaddr_in from)
{
int dlen, alen, n, type, class, count;
char dnbuf[MAXDNAME], anbuf[PACKETSZ], *data, *fname;

nameserIncr(from.sin_addr, nssRcvdIQ);

if (ntohs(hp->ancount) != 1
|| ntohs(hp->qdcount) != 0
|| ntohs(hp->nscount) != 0
|| ntohs(hp->arcount) != 0) {
ns_debug(ns_log_default, 1,
"FORMERR IQuery header counts wrong");
hp->qdcount = htons(0);
hp->ancount = htons(0);
hp->nscount = htons(0);
hp->arcount = htons(0);
hp->rcode = FORMERR;
return (Finish);
}/*構造包時,使其能夠通過這些檢查*/
/*
* Skip domain name, get class, and type.
*/
if ((n = dn_skipname(*cpp, eom)) < 0) {
ns_debug(ns_log_default, 1,
"FORMERR IQuery packet name problem");
hp->rcode = FORMERR;
return (Finish);
}
/*dn_skipname函數接著調用ns_name_skip函數*/
*cpp += n;
/*使攻擊程序構造的包數據區很大*/
假設這時,棧如圖所示:

------------------
|參數 |
| |
| |
------------------
| |
| 返回地址 |
------------------
|ebp |
------------------
|各個局部變數 |
-----------------<----eom
|u.buff[512] |
-----------------
|u.buff[511] |
-----------------
| ..... |
-----------------
|u.buff[446] |
-----------------<----cp
| ... |
-----------------
|u.buff[12] |
-----------------
| ..... |
-----------------
|u.buff[0] |
-----------------<---msg
/*但是符合*cpp+3*INT16SZ+INT32SZ<=eom*/
if (*cpp + 3 * INT16SZ + INT32SZ > eom) {
ns_debug(ns_log_default, 1,
"FORMERR IQuery message too short");
hp->rcode = FORMERR;
return (Finish);
}
/*named處理type,class*/
GETSHORT(type, *cpp);
GETSHORT(class, *cpp);
*cpp += INT32SZ; /* ttl */
GETSHORT(dlen, *cpp);
/*cpp已經接近緩衝區的邊界了*/
此時,棧如圖所示:

------------------
|參數 |
| |
| |
------------------
| |
| 返回地址 |
------------------
|ebp |
------------------
|各個局部變數 |
-----------------<----eom
|u.buff[512] |
-----------------
|u.buff[511] |
-----------------
| .... |
-----------------
|u.buff[458]=255|
-----------------<---cp
|u.buff[457]=0 |
-----------------
|u.buff[456]=255|<--假設dlen為255
-----------------
| ..... |
-----------------
|u.buff[446] |
-----------------
| ... |
-----------------
|u.buff[12] |
-----------------
| ..... |
-----------------
|u.buff[0] |
-----------------<---msg
*cpp += dlen;
/*攻擊程序發出的反向查詢包的dlen為一個很大的值*/
/*此時,再向後推dlen個位元組*/
/*哈,越界了*/
if (*cpp != eom) {
ns_debug(ns_log_default, 1,
"FORMERR IQuery message length off");
hp->rcode = FORMERR;
return (Finish);
}

接下來,就是由ns_req()將cp-msg個位元組發送給攻擊程序。攻擊者就可以得到named棧的信息,為下一步的單位元組緩衝區攻擊作好準備。

2.TSIG bug
DNS域名伺服器使用TISG(tranaction signature)來進行驗證通訊。TSIG BUG因此而得名。在最近出現的四個BIND BUG中,TSIG BUG危害是最為嚴重的。一個TSIG是一個高層的DNS資源記錄,在請求或者應答中是分別計算的,用完后丟棄,不能重複使用,也不應該保存在高速緩存中。TSIG是一個複雜的安全機制。它必須在消息的最後。如果在資源記錄(RR)中有幾個TSIG,或者位置不正確,BIND就會丟棄這個包並且送回一個錯誤信息。TSIG有幾個驗證機制,阻止了攻擊者從網路上截取含有TSIG的包使用。
TSIG BUG影響的BIND版本有:8.2(any service pack),8.2.1,8.2.2(packs1-7),和所有的8.2.3beta版本。
當BIND接到一個請求,它會根據接受請求使用的傳輸機制,把請求放在棧或者堆中。如果DNS請求小於512個位元組,它就使用UDP/53接受請求的數據,並將其放在棧區中;如果DNS請求大於512個位元組,它就使用TCP/53接受請求的數據,並把請求數據放在堆中。
當請求小於或者等於512個位元組時,由datagram_read()函數把請求放到棧中的一個513個位元組緩衝區中,即u.buff;當收到一個TCP請求時,就由stream_getlen()函數把請求數據讀到一個64K的緩衝區中,這個緩衝區叫做sp->s_buff,是在堆中為每個套接字分配的。其中,有一個很有意思的特徵,無論是使用TCP傳輸協議還是UDP傳輸協議,BIND都是只在緩衝區中對數據進行操作,然後做出相應的響應。
BIND使用兩個變數來跟蹤緩衝區的使用情況:msglen保存緩衝區中現有數據的位元組數;bufflen保存緩衝區沒有使用的位元組數。
當接到一個DNS消息,msglen被初始化為從網路上接到的數據的長度。對於UDP來說,就是由recvfrom()系統調用返回的數;而TCP消息的msglen是由客戶端給出的。buflen被設置為讀取消息的緩衝區的大小,UDP是512,TCP是64K。
通常情況下,在處理一個DNS查詢時,BIND回在查詢的後面加上應答、驗證以及其它的記錄信息。接著,BIND就會修改這個DNS查詢的頭來顯示上面所做的修改,將其送出。在處理過程中,msglen將會反映構造應答信息的緩衝區使用情況,而buflen將跟蹤緩衝區空閑區域的情況。在整個處理過程中,BIND都假設buflen+msglen等於緩衝區的長度。
根據消息頭的設置,BIND會區分請求還是應答,分別對其進行處理。如果接到一個請求,它就區分這個請求是查詢(query)、反向查詢(iquery)、update還是notification。從BIND8.2開始,在處理請求數據之前,BIND首先要檢驗有沒有TSIG,這個功能是由ns_find_tsig()來完成的,同時這個函數還會對TSIG的合法性進行基本的驗證。如果有一個正確的TSIG而沒有準確的security key,BIND就發出一個錯誤信號,並跳過正常的處理操作。此時,msglen和buflen也保留為其初始值,而不是被設置為其工作值。
因為有一個正確的TSIG但沒有準確的security key,BIND就進行錯誤處理。在產生錯誤信息時,BIND會重新起用緩衝區並且在這個有問題請求之後加上一個TSIG。此時,BIND假定msglen+buflen等於緩衝區的大小,當然在通常情況下,這時對的,但是只是在通常情況下-:(,在一些特殊的情況下msglen+buflen幾乎可以達到緩衝區大小的兩倍。接下來,BIND就調用ns_sign()函數在查詢之後加上一個TSIG,可能已經超出了緩衝區的範圍。
下面是BIND處理請求的大體過程的示意圖:(本來是應該使用HTML格式的,但----是我一直對製作頁面缺乏興趣,所以只要如此了,不過我會抓緊學一學的-:) )
----------------
|收到DNS請求 |
----------------
| |
| |
/ /
-------------- ---------------
|UDP請求由 | |TCP請求由 |
|datagram_read| |stream_getlen |
|放到 | |放到 |
-------------- ---------------
| |
| |
/ /
------------ -----------------
|棧 | |堆heap |
|u.buff[513]| | sp->s_buff |
| | | |
------------ ------------------
跟蹤變數:msglen---數據長度
buflen---未使用緩衝區的長度

圖2.1
|----------------------------------------------------------------|
| 接到一個DNS請求 |
| msglen設置為從網路上接受的數據的長度 |buflen被初始化為緩衝區的長度|
| UDP:recvfrom() |UDP:513 bytes |
| TCP:由客戶端給出 |TCP:64K bytes |
|-----------------------------------------------------------------|
判斷請求是:
query
iquery
update
notification

檢查是否有TSIG
並檢驗其合法性
----------------------------------------------------------------------
通常情況下, 異常情況下,
TSIG和security key合法 正確的TSIG,而security key非法

處理請求 發出錯誤信號:
BIND跳過正常對請求的處理
進行錯誤處理

在查詢信息之後 msglen和buflen保留為其原來的值
加上驗證及其它
記錄

修改查詢包的頭 重用緩衝區產生錯誤信息

msglen等於數據的長度 假設nsglen+buflen等於原來緩衝區的長度
buflen空閑緩衝區的長度
假設原來緩衝區的長度等於
msglen+buflen

發出應答 加上TSIG,溢出
------------------------------------------------------------------
例如,A公司的DNS使用的是BIND8.2.2-Patch5。一名攻擊者通過埠掃描知道了相關的信息,接著攻擊者向這台域名伺服器發出了一個請求,而這個請求包有一個TSIG而security key非法。A公司的DNS收到這個請求進行處理,它發現一個TSIG但是沒有合法的security key就發出了錯誤信號,而此時msglen和buflen都被鎖定,不能在處理錯誤時準確反映緩衝區的情況。datagram_read()或者stream_getlen()函數返回之前,BIND在請求包之後加上了新的TSIG,就超出了緩衝區。

總結
針對這個BUG可以使用單位元組緩衝區溢出和堆溢出exploit。由於這兩個BUG和系統設置無關,所以應該趕快升級BIND系統。有一些途徑可以用來對named進行保護:
1).不以root運行named;
2).使用chroot保護文件系統。
此外還可以使用其它一些方式保護自己的系統。


[火星人 ] BIND8安全漏洞分析已經有812次圍觀

http://coctec.com/docs/security/show-post-72906.html