Skip to content

Commit

Permalink
alexa/google-home: fix potential vulnerability. do not allow local ne…
Browse files Browse the repository at this point in the history
…twork control using cloud tokens belonging to a different user. the plugins are now locked to a specific scrypted cloud account once paired.
  • Loading branch information
koush committed Feb 21, 2024
1 parent 41d042b commit 8fa5e23
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 33 deletions.
2 changes: 1 addition & 1 deletion plugins/alexa/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

{
"scrypted.debugHost": "10.10.0.51",
"scrypted.debugHost": "scrypted-server",
}
148 changes: 148 additions & 0 deletions plugins/alexa/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<details>
<summary>Changelog</summary>

### 0.3.0

alexa/google-home: additional auth token checks to harden endpoints for cloud sharing
alexa: removed unneeded packages (#1319)
alexa: added support for `light`, `outlet`, and `fan` device types (#1318)


### 0.2.10

alexa: fix potential response race


### 0.2.9

alexa: fix race condition in sendResponse


### 0.2.8

alexa: display camera on doorbell press (#1066)


### 0.2.7

alexa: added helpful error messages regarding token expiration (#1007)


### 0.2.6

alexa: fix doorbells


### 0.2.5

alexa: publish w/ storage fix


### 0.2.4

alexa: add setting to publish debug events to console (#685)


### 0.2.3

webrtc/alexa: add option to disable TURN on peers that already have externally reachable addresses


### 0.2.1

alexa: set screen ratio to 720p (#625)


### 0.2.0

alexa: refactor code structure (#606)


### 0.1.0

alexa: ensure we are talking to the correct API endpoint (#580)


### 0.0.20

alexa: provide hint that medium resolution is always used.


### 0.0.19

various: minor cleanups
alexa: added logging around `tokenInfo` resets (#488)
sdk: rename sdk.version to sdk.serverVersion
plugins: update tsconfig.json
alexa: publish beta


### 0.0.18

alexa: rethrow login failure error
added support for type `Garage` and refactored the controller for future support (#479)
updated install instructions (#478)
webrtc/alexa: fix race condition with intercoms and track not received yet.


### 0.0.17

alexa: close potential security hole if scrypted is exposed to the internet directly (ie, user is not using the cloud plugin against recommendations)


### 0.0.16

plugins: remove postinstall
plugins: add tsconfig.json
alexa: doorbell motion sensor support


### 0.0.15

alexa: fix harmless crash in log


### 0.0.14

alexa: fix empty endpoint list


### 0.0.13

all: prune package.json
alexa: fix doorbell syncing


### 0.0.12

alexa: publish


### 0.0.10

alexa: 2 way audio


### 0.0.4

alexa: 2 way audio
alexa: motion events


### 0.0.3

webrtc: refactor
alexa: use rtc signaling channel
alexa: publish


### 0.0.1

alexa: doorbells
alexa: sync devices properly
alexa: add camera/doorbell, fix webrtc to work with amazon reqs
alexa: initial pass with working cameras
cloud: stub out alexa


</details>
4 changes: 2 additions & 2 deletions plugins/alexa/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion plugins/alexa/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@scrypted/alexa",
"version": "0.3.0",
"version": "0.3.1",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
Expand Down
13 changes: 12 additions & 1 deletion plugins/alexa/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
onPut(oldValue: boolean, newValue: boolean) {
DEBUG = newValue;
}
}
},
pairedUserId: {
title: "Pairing Key",
description: "The pairing key used to validate requests from Alexa. Clear this key or delete the plugin to allow pairing with a different Alexa login.",
},
});

accessToken: Promise<string>;
Expand Down Expand Up @@ -608,6 +612,13 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
// validate this. old tokens will be grandfathered in.
if (getcookieResponse.data.expiry && getcookieResponse.data.clientId !== 'amazon')
throw new Error('client id mismatch');
if (!this.storageSettings.values.pairedUserId) {
this.storageSettings.values.pairedUserId = getcookieResponse.data.id;
}
else if (this.storageSettings.values.pairedUserId !== getcookieResponse.data.id) {
this.log.a('This plugin is already paired with a different account. Clear the existing key in the plugin settings to pair this plugin with a different account.');
throw new Error('user id mismatch');
}
this.validAuths.add(authorization);
}
catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion plugins/google-home/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

{
"scrypted.debugHost": "koushik-ubuntu",
"scrypted.debugHost": "scrypted-server",
}
4 changes: 2 additions & 2 deletions plugins/google-home/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions plugins/google-home/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"interfaces": [
"HttpRequestHandler",
"EngineIOHandler",
"MixinProvider"
"MixinProvider",
"Settings"
],
"pluginDependencies": [
"@scrypted/cloud",
Expand All @@ -48,5 +49,5 @@
"@types/lodash": "^4.14.168",
"@types/url-parse": "^1.4.3"
},
"version": "0.0.56"
"version": "0.0.57"
}
69 changes: 47 additions & 22 deletions plugins/google-home/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { homegraph_v1 } from "@googleapis/homegraph/v1";
import sdk, { EngineIOHandler, HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, Refresh, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty } from '@scrypted/sdk';
import sdk, { EngineIOHandler, HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, Refresh, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceProperty, Setting, SettingValue, Settings } from '@scrypted/sdk';
import type { SmartHomeV1DisconnectRequest, SmartHomeV1DisconnectResponse, SmartHomeV1ExecuteRequest, SmartHomeV1ExecuteResponse, SmartHomeV1ExecuteResponseCommands } from 'actions-on-google/dist/service/smarthome/api/v1';
import axios from 'axios';
import { GoogleAuth } from "google-auth-library";
Expand All @@ -8,6 +8,7 @@ import throttle from 'lodash/throttle';
import './commands';
import { supportedTypes } from './common';
import './types';
import { StorageSettings } from '@scrypted/sdk/storage-settings';

import { canAccess } from './commands/camerastream';
import { commandHandlers } from './handlers';
Expand Down Expand Up @@ -44,10 +45,36 @@ const googleAuth = new GoogleAuth({

const includeToken = 3;

class GoogleHome extends ScryptedDeviceBase implements HttpRequestHandler, EngineIOHandler, MixinProvider {
linkTracker = localStorage.getItem('linkTracker');
agentUserId = localStorage.getItem('agentUserId');
localAuthorization = localStorage.getItem('localAuthorization');
class GoogleHome extends ScryptedDeviceBase implements HttpRequestHandler, EngineIOHandler, MixinProvider, Settings {
storageSettings = new StorageSettings(this, {
// the tracker tracks whether this device has been reported in a sync request payload.
// this is because reporting too many devices in the initial sync fails upstream at google.
linkTracker: {
hide: true,
persistedDefaultValue: Math.random().toString(),
},
agentUserId: {
hide: true,
persistedDefaultValue: uuidv4(),
},
localAuthorization: {
hide: true,
persistedDefaultValue: uuidv4(),
},
pairedUserId: {
title: "Pairing Key",
description: "The pairing key used to validate requests from Google Home. Clear this key or delete the plugin to allow pairing with a different Google Home login.",
},
});
get linkTracker() {
return this.storageSettings.values.linkTracker;
}
get agentUserId() {
return this.storageSettings.values.agentUserId;
}
get localAuthorization() {
return this.storageSettings.values.localAuthorization;
}
reportQueue = new Set<string>();
reportStateThrottled = throttle(() => this.reportState(), 2000);
throttleSync = throttle(() => this.requestSync(), 15000, {
Expand All @@ -71,23 +98,6 @@ class GoogleHome extends ScryptedDeviceBase implements HttpRequestHandler, Engin
this.googleAuthClient = googleAuth.fromJSON(this.jwt);
}

// the tracker tracks whether this device has been reported in a sync request payload.
// this is because reporting too many devices in the initial sync fails upstream at google.
if (!this.linkTracker) {
this.linkTracker = Math.random().toString();
localStorage.setItem('linkTracker', this.linkTracker);
}

if (!this.agentUserId) {
this.agentUserId = uuidv4();
localStorage.setItem('agentUserId', this.agentUserId);
}

if (!this.localAuthorization) {
this.localAuthorization = uuidv4();
localStorage.setItem('localAuthorization', this.localAuthorization);
}

try {
this.defaultIncluded = JSON.parse(localStorage.getItem('defaultIncluded'));
}
Expand Down Expand Up @@ -150,6 +160,14 @@ class GoogleHome extends ScryptedDeviceBase implements HttpRequestHandler, Engin
});
}

getSettings(): Promise<Setting[]> {
return this.storageSettings.getSettings();
}

putSetting(key: string, value: SettingValue): Promise<void> {
return this.storageSettings.putSetting(key, value);
}

async isSyncable(device: ScryptedDevice): Promise<boolean> {
const plugins = await this.plugins;
const mixins = (device.mixins || []).slice();
Expand Down Expand Up @@ -528,6 +546,13 @@ class GoogleHome extends ScryptedDeviceBase implements HttpRequestHandler, Engin
// validate this. old tokens will be grandfathered in.
if (getcookieResponse.data.expiry && getcookieResponse.data.clientId !== 'google')
throw new Error('client id mismatch');
if (!this.storageSettings.values.pairedUserId) {
this.storageSettings.values.pairedUserId = getcookieResponse.data.id;
}
else if (this.storageSettings.values.pairedUserId !== getcookieResponse.data.id) {
this.log.a('This plugin is already paired with a different account. Clear the existing key in the plugin settings to pair this plugin with a different account.');
throw new Error('user id mismatch');
}
this.validAuths.add(authorization);
}
catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion plugins/google-home/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "Node16",
"target": "ES2021",
"resolveJsonModule": true,
"moduleResolution": "Node16",
Expand Down

0 comments on commit 8fa5e23

Please sign in to comment.