Discovering Misconfigured Amazon permissions in Chromebook monitoring software
I’ve been putting this post off for a long time. The events took place the in high school, but I always meant to make some sort of blog post or write-up about this. Me and my friend Winston found a series of vulnerabilities related to misconfigured IAM and S3 permissions in software used by our school district. I wrote most of this over a year ago but didn’t get around to publishing it. A lot of this is pieced together from old chat logs and emails, but I don’t guarantee complete accuracy. For reference this would’ve been a little over four years ago, but I’ll leave the exact dates vague.
One thing I decided before publishing was to remove references to the specific company. It’s not a secret, but this far from the event I’m sure they have much better security practices and it’s not the most relevant part of the story.
Spoiler: we responsibly disclose the vulnerability at the end.
At the time of these events we were seniors at Morristown High School. Morris School District was just starting its Chromebook Initiative to “cultivate and support learning that reflects contemporary exchanges and interactions.” To us, this meant that every student in the school received a Chromebook (typically a Dell Chromebook 11 or Samsung Series 3 Chromebook) managed by the high school.
Our interest in discovering vulnerabilities in the Chromebooks followed rumors that someone had already discovered issues in the software. I never heard specific details and never confirmed the rumors. True or not, they peaked our interest and led us to further investigate the nature of the software.
The Chromebooks given to students were set up as managed devices owned by the
district. Access to the Developer Tools were disabled, Chromebooks were
automatically updated whenever a new Chrome OS version came out, and the
monitoring software was preinstalled on all Chromebooks. Despite
these restrictions, we could still view installed extensions, install anything
from the Chrome Web Store and prepend
view-source: to any URL or path to get
I don’t recall all the individual steps we took or red herrings we followed before finding the two issues we would eventually report. It took about a week of trial and error both in class and after practice before finding everything. Since these wrong turns are not recorded in the chat, what’s here is a far more streamlined version of what actually happened.
Our investigation started with looking for the source of the management
software. It was quite obvious that there was such software installed on the
Chromebooks, as attempting to access certain sites (such as the beloved
slither.io) were blocked locally. Furthermore, some of the more
technologically-savvy teachers had begun to utilize the classroom-facing
features of the software, and it is there that we learned that the software was
developed by redacted. However, there were no extensions proudly labelled
“[company name]” or “Chromebook Management” so we focused on the two extensions that
came preinstalled with each Chromebook. Hidden in the minified source of one of
secretAccessKey. This seemed like a promising start, so we
tried to find looking for what these keys had access to.
Setting up the AWS cli and plugging in the discovered IAM keys revealed two services we had access to: dynamodb and kinesis. We only had readonly access to dynamodb and the data looked uninteresting so we moved on to kinesis. Although it took some reading and fiddling trying to figure out what kinesis actually did and how to use it we eventually found we had the ability to read from streams of student data spread across several so-called “shards.” The cli proved ineffective at sampling the data passing through the shards so I wrote the following script to assist us (apologies for code quality).
import boto3 import json import sys strName = sys.argv client = boto3.client("kinesis"); response0 = client.describe_stream( StreamName=strName ) print("Found an "+response0["StreamDescription"]["StreamStatus"]+" stream!") print("Stream has "+str(len(response0["StreamDescription"]["Shards"]))+" shards.") print("Retention Period: "+str(response0["StreamDescription"]["RetentionPeriodHours"])+" hours.") for s in response0["StreamDescription"]["Shards"]: print(" Shard: "+s["ShardId"]) response1 = client.get_shard_iterator( StreamName=strName, ShardId=s["ShardId"], ShardIteratorType="LATEST", ) shard = response1["ShardIterator"]; response2 = client.get_records( ShardIterator=shard, Limit=1, ) records = response2["Records"] if ( len(records) < 1 ): print("No records in shard.") continue data = records["Data"] print( data )
The output was JSON-formatted data clearly sent from student chromebooks across the country announcing its user had accessed a site with a bad word. At this point we decided to look for someone to contact at at the company, although in the day it took to send the email we managed to find something else more interesting.
Although knowing which students are visiting naughty pages is somewhat
interesting, we were convinced the company was collecting more detailed
information from students. We had already found debugging the extension on the
Chromebooks was a lost cause so I decided to try my luck debugging the software
on a personal computer. However, the management extensions and restrictions,
discussed earlier, are loaded into Chrome after associating a user profile with
the school Google account. This meant that all the policies were also active on
our personal devices. Winston managed to circumvent the devtools restrictions
--remote-debugging-port switch, which opens up the managed Chrome
instance to debugging from another Chrome instance’s dev tools. By starting up
a personal Chrome instance to do just that, we hit the second roadblock: the
district had configured the software to disable itself when not
running on a Chromebook. After looking through the pretty-printed code we
determined that it was simply checking for a Chrome OS user agent, and so we
were able to trick the software by spoofing our user-agent through another
--user-agent. After successfully enabling the extension
and looking at its network activity
through the ‘remote’ Chrome instance, we saw the software occasionally uploading
screenshots to an S3 bucket. These screenshots revealed the other half of what
we had found in the kinesis shards: whenever someone visits a page with
blacklisted words, the page URL is sent to kinesis while a screenshot of the
page is uploaded to S3. The URL was random and there was far too much entropy
to randomly guess other screenshots, but Winston discovered that crucially, the
root path of the bucket was unsecured and had a listing of all the screenshots
collected by the software. Furthermore, the screenshots could be filtered by
query parameters, such as by organization or even by user. Due to the amount of
personally-identifiable information freely searchable in the bucket we contacted
the company with both issues the next day.
It took a few times contacting the company who managed the software before getting a response. Unfortunately they did not have a bug bounty program at the time, but eventually we were able to get a response. Despite my worries, they seemed really thankful for our efforts discovering and reporting everything and we were given a nice reward for our efforts.
If there’s a moral to this story, it’s probably that Amazon permissions are confusing and to be careful how you architect things with regards to security. I know there’s been some high-profile data breaches related to AWS permissions, and that Amazon has acted on them to make these types of events less likely. Also, don’t put things off because it might be four years until you get to them.
Update (2020-03-30): I removed some references to the company that made the software and replaced it with “the company” or “the software” as appropriate.