Talos Vulnerability Report

TALOS-2018-0683

Webroot BrightCloud SDK HTTP headers-parsing code execution vulnerability

December 17, 2018
CVE Number

CVE-2018-4012

Summary

An exploitable buffer overflow vulnerability exists in the HTTP header-parsing function of the Webroot BrightCloud SDK. The function bc_http_read_header incorrectly handles overlong headers, leading to arbitrary code execution. An unauthenticated attacker could impersonate a remote BrightCloud server to trigger this vulnerability.

Tested Versions

Webroot BrightCloud SDK

Product URLs

https://www.brightcloud.com/

CVSSv3 Score

9.0 - CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

CWE

CWE-122: Heap-based Buffer Overflow

Details

BrightCloud provides an API service that allows its clients to access websites’ classification and reputation data. Their service can be queried to retrieve the category for the content of a specific URL and its reputation index. BrightCloud also provides an SDK to access their web services API that can be used, for example, by appliances that want to restrict access to non-malicious sites.

A binary using this SDK, called webroot.so, was found to be inside the CUJO Smart Firewall — an internet-of-things device that monitors the wireless internet in the user’s home — running version 7003, and is used to detect and deny access to potentially malicious websites. Specifically, in the device that we tested, CUJO accesses the BrightCloud API at the URL bcap15.brightcloud.com over a plain HTTP connection.

While the following analysis is written in the context of the CUJO Smart Firewall, this advisory does apply to the Webroot BrightCloud SDK itself.

The SDK provides the function bc_http_read_header, which is used to store the headers of an HTTP connection into a global buffer. This function is used during any communication with BrightCloud servers.

.text:000176BC     bc_http_read_header:
.text:000176BC
.text:000176BC 000         li      $gp, 0x54B14
.text:000176C4 000         addu    $gp, $t9
.text:000176C8 000         addiu   $sp, -0x38
.text:000176CC 038         sw      $ra, 0x38+var_4($sp)
.text:000176D0 038         sw      $fp, 0x38+var_8($sp)
.text:000176D4 038         move    $fp, $sp
.text:000176D8 038         sw      $gp, 0x38+var_28($sp)
.text:000176DC 038         sw      $a0, 0x38+struct($fp)
.text:000176E0 038         sw      $a1, 0x38+var_20($fp)
.text:000176E4 038         la      $v0, __stack_chk_guard
.text:000176E8 038         nop
.text:000176EC 038         lw      $v0, (__stack_chk_guard - 0x95528)($v0)
.text:000176F0 038         nop
.text:000176F4 038         sw      $v0, 0x38+var_C($fp)
.text:000176F8 038         la      $v0, unk_70000
.text:000176FC 038         nop
.text:00017700 038         addiu   $v0, (HeaderBuf - 0x70000)             ; [1]
.text:00017704 038         sw      $v0, 0x38+header_buf_ptr($fp)
.text:00017708 038         lw      $v0, 0x38+header_buf_ptr($fp)
.text:0001770C 038         nop
.text:00017710 038         sw      $v0, 0x38+header_buf_start($fp)
.text:00017714 038         sw      $zero, 0x38+lines_counter($fp)
.text:00017718 038         b       loc_17770
.text:0001771C 038         nop
.text:00017720      # ---------------------------------------------------------------------------
.text:00017720
.text:00017720     loc_17720:                                             ; [2] loop
.text:00017720 038         lw      $v0, 0x38+lines_counter($fp)
.text:00017724 038         nop
.text:00017728 038         addiu   $v0, 1
.text:0001772C 038         sw      $v0, 0x38+lines_counter($fp)
.text:00017730 038         lw      $v0, 0x38+header_buf_ptr($fp)
.text:00017734 038         nop
.text:00017738 038         move    $a1, $v0
.text:0001773C 038         lw      $a0, 0x38+header_buf_start($fp)
.text:00017740 038         li      $v0, 0x10000
.text:00017744 038         nop
.text:00017748 038         addiu   $v0, (is_empty - 0x10000)
.text:0001774C 038         move    $t9, $v0
.text:00017750 038         bal     is_empty                               ; [6]
.text:00017754 038         nop
.text:00017758 038         lw      $gp, 0x38+var_28($fp)
.text:0001775C 038         bnez    $v0, loc_177AC                         ; exit loop
.text:00017760 038         nop
.text:00017764 038         lw      $v0, 0x38+header_buf_ptr($fp)
.text:00017768 038         nop
.text:0001776C 038         sw      $v0, 0x38+header_buf_start($fp)
.text:00017770
.text:00017770     loc_17770:
.text:00017770 038         addiu   $v0, $fp, 0x38+header_buf_ptr          ; [5]
.text:00017774 038         li      $a3, 0x2000
.text:00017778 038         move    $a2, $v0
.text:0001777C 038         lw      $a1, 0x38+header_buf_start($fp)        ; [4]
.text:00017780 038         lw      $a0, 0x38+struct($fp)
.text:00017784 038         la      $v0, bc_net_readline
.text:00017788 038         nop
.text:0001778C 038         move    $t9, $v0
.text:00017790 038         jalr    $t9                                    ; [3] bc_net_readline
.text:00017794 038         nop
.text:00017798 038         lw      $gp, 0x38+var_28($fp)
.text:0001779C 038         bgtz    $v0, loc_17720                         ; [2] loop
.text:000177A0 038         nop
.text:000177A4 038         b       loc_177B0                              ; exit loop
.text:000177A8 038         nop
.text:000177AC      # ---------------------------------------------------------------------------
.text:000177AC
.text:000177AC     loc_177AC:
.text:000177AC 038         nop
.text:000177B0
.text:000177B0     loc_177B0:
...

Initially, the function saves a reference to the global buffer HeaderBuf [1], which is stored in the .bss section.

Then, the function enters a loop [2] that reads lines (terminated by \n) from the socket using bc_net_readline: the invocation at [3] reads a maximum of 0x2000 bytes, storing the data (without stripping newline terminators) in the buffer pointed by header_buf_start [4]. A pointer to the end of the header line is stored in header_buf_ptr [5].

If no errors occurred, the function checks if the last line read is empty [6], in which case, the loop exits. Otherwise, the loop continues until an empty line is found.

Since there are no exit conditions related to the size of the destination buffer, an attacker could exploit this vulnerability to write past the HeaderBuf buffer, which has a size of 0x2000 bytes, overwriting data in the .bss section that could eventually allow the attacker to execute arbitrary code.

In the context of the CUJO Smart Firewall, the webroot.so library is executed by the threatd LUA script, running as user root. Every time a new host is resolved via DNS in the network monitored by the device, the function lwebroot_lookup is called, which eventually queries BrightCloud's servers using the supplied SDK, which is using the vulnerable bc_http_read_header function.

In this case, an attacker can impersonate the remote server bcap15.brightcloud.com. For example, a malicious actor could carry out a man-in-the-middle attack to execute arbitrary code in the device with root user privileges.

Exploit Proof of Concept

The following proof of concept shows how to crash the threatd process in CUJO Smart Firewall, assuming the attacker has redirected connections toward bcap15.brightcloud.com:80 to their own machine.

$ perl -e 'print "AAAAAAAAA\n"x1200,"\n"' | nc -l -p 80

Timeline

2018-10-10 - Vendor Disclosure
2018-10-17 - Vendor Patched
2018-12-17 - Public Release

Credit

Discovered by Claudio Bozzato of Cisco Talos.