diff --git a/agent/src/android/scanner.ts b/agent/src/android/scanner.ts new file mode 100644 index 00000000..aa614ddb --- /dev/null +++ b/agent/src/android/scanner.ts @@ -0,0 +1,44 @@ +import { getNSMainBundle } from "../ios/lib/helpers"; +import { wrapJavaPerform, getApplicationContext } from "./lib/libjava"; +import { ArrayList } from "./lib/types"; + +export namespace scanner { + export const getfbdatabase = (): Promise => { + return wrapJavaPerform(() => { + // -- Sample Java + // + // String dburl = (String)getString(R.string.firebase_database_url); + const context = getApplicationContext(); + const myid = context.getResources().getIdentifier("firebase_database_url", "string", context.getPackageName()); + const dburl = context.getString(myid); + return dburl; + }); + } + export const getapikeys = (): Promise => { + return wrapJavaPerform(() => { + const keynames = [ + "google_maps_geocoder_key", + "notification_server_key", + "server_key", + "com.google.android.geo.API_KEY", + "com.google.android.maps.v2.API_KEY", + "googlePlacesWebApi", + "google_crash_reporting_api_key", + "google_api_key" + ]; + const context = getApplicationContext(); + var keys : string[] = new Array; + var count = 0; + for (var i = 0; i < keynames.length; i++) { + try { + var key = context.getResources().getIdentifier(keynames[i], "string", context.getPackageName()); + keys[count] = context.getString(key); + count++; + } catch (error) { + + } + } + return keys; + }); + } +} \ No newline at end of file diff --git a/agent/src/rpc/android.ts b/agent/src/rpc/android.ts index 7560e75f..828f783d 100644 --- a/agent/src/rpc/android.ts +++ b/agent/src/rpc/android.ts @@ -10,6 +10,7 @@ import { sslpinning } from "../android/pinning"; import { root } from "../android/root"; import { androidshell } from "../android/shell"; import { userinterface } from "../android/userinterface"; +import { scanner } from "../android/scanner"; export const android = { // android clipboard @@ -67,6 +68,10 @@ export const android = { androidRootDetectionDisable: () => root.disable(), androidRootDetectionEnable: () => root.enable(), + // android scanner declarations + androidGetFbDatabase: () => scanner.getfbdatabase(), + androidGetApiKeys: () => scanner.getapikeys(), + // android user interface androidUiScreenshot: () => userinterface.screenshot(), androidUiSetFlagSecure: (v: boolean): Promise => userinterface.setFlagSecure(v), diff --git a/objection/commands/android/scanner.py b/objection/commands/android/scanner.py new file mode 100644 index 00000000..9337a9bb --- /dev/null +++ b/objection/commands/android/scanner.py @@ -0,0 +1,82 @@ +import click +import requests +import re + +from objection.state.connection import state_connection + +def firebase(args: list) -> None: + """ + Search for a Firebase Database and check if it's leaking data. + + :param args: + :return: + """ + api = state_connection.get_api() + try: + fbdb = api.android_get_fb_database() + click.secho('Scanning FireBase DB: {0}'.format(fbdb), fg='green') + click.secho('Note: If the DB is exposed, it may take a while to download', fg='red') + response = requests.get(fbdb + '/.json') + if response.status_code == 401: + click.secho('Firebase DB is not leaking data', fg='green') + elif response.status_code == 200: + click.secho('Firebase DB is leaking data!', fg='red') + if len(response.text) < 1000: + click.secho('Size: {:,.0f}'.format(len(response.text)) + "B", fg='red') + elif len(response.text) >= 1000 and len(response.text) < 1000000: + click.secho('Size: {:,.0f}'.format(len(response.text)/float(1<<10)) + "KB", fg='red') + elif len(response.text) >= 1000000: + click.secho('Size: {:,.0f}'.format(len(response.text)/float(1<<20)) + "MB", fg='red') + else: + click.secho('Something weird happened. Please report the issue.', fg='red') + except: + click.secho('Application doesn''t make use of FireBase', fg='red') + +def apikeys(args: list) -> None: + """ + Search for Firebase Cloud Messaging API Keys. + Ref: https://abss.me/posts/fcm-takeover/ + + :param args: + :return: + """ + api = state_connection.get_api() + output = [] + output = api.android_get_api_keys() + + # Firebase Cloud Messaging Web API Key + pattern = r'AIzaSy[0-9A-Za-z_-]{33}' + # Firebase Cloud Messaging Server Key + pattern2 = r'AAAA[A-Za-z0-9_-]{7}:[A-Za-z0-9_-]{140}' + + data = '{"registration_ids":["ABC"]}' + + for x in output: + if re.search(pattern2, x): + # Now lets create the request to validate the keys + # If the keys validate, they are server keys and can be used to + # send messages + headers = { + 'Authorization': 'key={0}'.format(x), + 'Content-Type': 'application/json', + } + response = requests.post('https://fcm.googleapis.com/fcm/send', headers=headers, data=data) + if response.status_code == 200: + click.secho('FCM Server Key: {0}'.format(x) + ' - [VALID]', fg='green') + elif response.status_code == 401: + click.secho('FCM Server Key: {0}'.format(x) + ' - [INVALID]', fg='red') + if re.search(pattern, x): + # Now lets create the request to validate the keys + # If the keys validate, they are server keys and can be used to + # send messages + headers = { + 'Authorization': 'key={0}'.format(x), + 'Content-Type': 'application/json', + } + response = requests.post('https://fcm.googleapis.com/fcm/send', headers=headers, data=data) + if response.status_code == 200: + click.secho('Legacy FCM Server Key: {0}'.format(x) + ' - [VALID]', fg='green') + elif response.status_code == 401: + click.secho('Web API Key: {0}'.format(x) + ' - [Nothing to do here]', fg='red') + + diff --git a/objection/console/commands.py b/objection/console/commands.py index 35e2ebb6..8ed15572 100644 --- a/objection/console/commands.py +++ b/objection/console/commands.py @@ -18,6 +18,7 @@ from ..commands.android import keystore from ..commands.android import pinning as android_pinning from ..commands.android import root +from ..commands.android import scanner as android_scanner from ..commands.ios import binary from ..commands.ios import bundles from ..commands.ios import cookies @@ -277,6 +278,19 @@ 'meta': 'Execute a shell command', 'exec': command.execute }, + 'scanner': { + 'meta': 'Command used to invoke the scanner', + 'commands': { + 'firebase': { + 'meta': 'Scan for leaking firebase DBs', + 'exec': android_scanner.firebase + }, + 'apikeys' : { + 'meta': 'Scan for Firebase Cloud Messaging Keys', + 'exec': android_scanner.apikeys + } + }, + }, 'hooking': { 'meta': 'Commands used for hooking methods in Android', 'commands': {