CVE-2019-11477 SACK Panic漏洞利用分析

一到下大雨天就想做点什么有意思的。皮鞋不会进水,因为根本就没有皮鞋,自然也就不会湿…

疯子带着小小去旅游了,家里就剩我一个,所以自然也就回来继续工作咯,不然还能干什么。当然,这也不是工作,算自娱自乐吧。当我工作的时候,我感到充实,我将休息,又觉得空虚,不工作也不休息,只能随便玩玩了。


前面写了几篇关于CVE-2019-11477漏洞的利用手段,但是都不够直接,为什么说不够直接呢?因为这些过程往往过于复杂,序列中的每一个步骤都把人搞得晕头转向,但其实这些让人眼花缭乱的东西是可以分离出去的。

本文提供一个干净清爽的方法。不再注入ICMP Need Frag,而是直接将mss协商成48。

先说下这些眼花缭乱的东西包括:

  • 如何诱导被攻击侧增窗并发送18个分散/聚集的整32KB的frag page。
  • 如何中途将mss改成最小值48字节。
  • 如何将raw data的mss凑成8字节。

其中,第一个还是相对容易的,现如今下载服务器发送大文件都是这种方式发送的,你可以试下sendfile系统调用。

第二个前面的文章分散讲过,参见:
CVE-2019-11478 Sack Slowness&Excess Resource Usage漏洞解析与利用
链接是:https://blog.csdn.net/dog250/article/details/94654620
当然,这个条件只是可选,你完全可以一开始建连握手时的SYN报文就把mss协商成48字节,只是需要更大的拥塞窗口罢了,当然这个和SACK Panic本身无关,这是另一个话题,所以本文的假设也就是直接将mss在握手时就协商成48字节。

第三点,也容易,攻击者发送三个空洞数据即可让被攻击者发包时携带3个SACK段,然后加上时间戳和nop对齐,正好占据40字节options,剩余raw data的mss,不多不少,8字节。

好了,我们假设上述三点均已经满足(虽然它们不是那么容易满足,但是请试试看,并不难),这样一来实施漏洞利用就超级简单了。

情况1:发送缓冲区存在一个17个frags组成的大skb的情况

现在,被攻击侧的发送缓冲区为:
在这里插入图片描述

接下来,攻击者构造SACK段:
在这里插入图片描述
此时,一个包含17个frag总长 17 × 32768 17\times 32768 17×32768字节的skb被一次性SACK。

关键是第三步,攻击者再构造一个SACK段:
在这里插入图片描述
其中 m 1 m_1 m1 m 2 m_2 m2 m 3 m_3 m3满足下面的条件时,被攻击侧将gg:

  • m 2 8 > 65535 \dfrac{m_2}{8}>65535 8m2>65535
  • m 3 > 0 m_3>0 m3>0 m 1 > 0 m_1>0 m1>0
  • m 1 m_1 m1 m 2 m_2 m2合在一起小于17个frags。

这是因为内核将尝试将长度 m 2 m_2 m2的大skb的一部分和长度为 m 1 m_1 m1的skb进行合并,在合并过程中,在tcp_shift_skb_data中:
在这里插入图片描述

具体调到tcp_shifted_skb内部后,由于pconut参数已经大于了u16所能表示的最大值,就会在u16类型的gso_segs和pcount比较时BUG_ON。


非常清晰的一个过程,现在我们再回过头来反向推敲。

有几个问题:

  • 如何满足17个32KB的frags拼接成一个大的skb?
  • 如何保证大的skb前面有一个小的skb?

其实,上面的条件很难直接被满足,为什么呢?

如果我们看tcp_write_xmit这个正规的TCP发送函数,会发现几乎很难会让一个拥有 17 × 32768 8 \dfrac{17\times 32768}{8} 817×32768个segs的skb一次被GSO发送出去,拥有各种限制,最终可能会调用tso_fragment将系统调用送来的满足攻击条件的大skb拆分成小的。我们看到,这个还和计算出来的pacing rate有关。

当然了,如果恰好有个服务器参数正好满足攻击条件,那太好不过了,如果不呢?

于是,我们可以换一种思路来间接满足。请问,我有说上面第一幅图的理想发送队列是被攻击侧自己形成的吗?没有啊!其实它也是被诱导形成的啊。

换句话说,即便诸多个skb是被拆分成小skb后一个个发出去的,我也能想办法将其合并成一个大的,就用SACK段合并的原理呗。

那就好办了,我们假设skb都是通过tso_fragment发送出去的,那又如何?注意到tso_fragment函数里只是split了一个skb,移动了frag page的position,offset指针而已,并没有拷贝数据到线性区域:
在这里插入图片描述

这意味着,分散聚集的frag page组成的skb是可以被重新合并起来的。

现在,我补上上文第一幅图之前的图。首先是初始,当拥塞窗口cwnd满足了攻击条件,即inflight字节至少为 ( 17 + 2 ) × 32768 (17+2)\times 32768 (17+2)×32768字节时的情景。
在这里插入图片描述

之所以要+2,是因为一个skb作为una,憋住发送队列,另一个用来做最后的合并,见前文。

接下来先来一波SACK,让从第三个开始的skb合并成一个:
在这里插入图片描述在合并的过程中,gso_segs字段就已经溢出了,但这个方法中,我们不关注它的溢出,我只需要知道它不会超过65535即可,后面的BUG_ON来自于最后一个合并时,tcp_shifted_skb的参数pcount的值大于65535。注意pcount可是个int型哦,这是关键!

OK,这便和第一幅图接上了。


现在,我要吐槽下我自己几天前的方法了。简直太复杂了。简直就是炫技:

  • 中间半途中塞入ICMP Need Frag后超时二分递减mss到达48。
  • 为了重传重新SACK,竟然构建SACK reneg以便超时后全部Mark lost。
  • 两次利用tcp_fragment来重新计算gso_segs,第一次溢出,第二次BUG。

本文的方法超级清爽,并没有直接让gso_segs溢出,而是让 l e n 8 \dfrac{len}{8} 8len超过65535字节,这办法好。


浙江温州皮鞋湿,下雨进水不会胖。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值