From 359b46ee41b879ef7eb477e86d25a307562c8070 Mon Sep 17 00:00:00 2001 From: wargio Date: Wed, 29 Jul 2020 17:32:03 +0200 Subject: [PATCH 01/14] Added hex and base64 strings detection --- android/tests/strings.py | 71 +++++++++++++++++++++++-- ios/tests/strings.py | 109 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 172 insertions(+), 8 deletions(-) diff --git a/android/tests/strings.py b/android/tests/strings.py index 979b9df..02a8f0b 100644 --- a/android/tests/strings.py +++ b/android/tests/strings.py @@ -1,6 +1,6 @@ ## fufluns - Copyright 2019-2021 - deroad -import re +import re, base64 STRINGS_SIGNATURES = [ ':"', @@ -19,6 +19,56 @@ ] JAVA_REGEX = r'(L([a-zA-Z\d\/\$_\-]+)(([a-zA-Z\d\.<>\$]+)?(\(\)|\([\[a-zA-Z\d\/\$_\-;]+\))([\[a-zA-Z\d\/\$_\-;]+|[\[ZBSCIJFDV]))?)' +HEX_REGEX = r'^[A-Fa-f0-9]{5,}$' +BASE64_REGEX = r'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$' +BADB64_REGEX = r'^[A-Za-z$+]+$' + +KNOWN_BADB64 = [ + '0123456789abcdef', + '0123456789ABCDEF', + '0123456789ABCDEFGHJKMNPQRSTVWXYZ', + '0123456789ABCDEFGHIJKLMNOPQRSTUV', + '0123456789ABCDEFGHIJKLMNOPQRSTUVZ', + '0oO1iIlLAaBbCcDdEeFfGgHhJjKkMmNnPpQqRrSsTtVvWwXxYyZz', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + 'AES/CBC/NoPadding', + 'AES/CBC/PKCS5Padding', + 'AES/ECB/NoPadding', + 'AES/ECB/PKCS5Padding', + 'DES/CBC/NoPadding', + 'DES/CBC/PKCS5Padding', + 'DES/ECB/NoPadding', + 'DES/ECB/PKCS5Padding', + 'DESede/CBC/NoPadding', + 'DESede/CBC/PKCS5Padding', + 'DESede/ECB/NoPadding', + 'DESede/ECB/PKCS5Padding', + 'RSA/ECB/PKCS1Padding', + 'RSA/ECB/OAEPWithSHA-1AndMGF1Padding', + 'RSA/ECB/OAEPWithSHA-256AndMGF1Padding' +] + +def is_hex(value): + if value in KNOWN_BADB64: + return False + return re.match(HEX_REGEX, value, flags=re.M) + +def is_base64(value): + if value in KNOWN_BADB64: + return False + found = re.search(BASE64_REGEX, value, flags=re.M) + if found and not re.match(BADB64_REGEX, value, flags=re.M): + found = found.group(0) + try: + decoded = base64.b64decode(found.encode('ascii')) + return len(decoded) > 0 + except Exception: + pass + return False class ContextStrings(object): def __init__(self, apk, utils): @@ -28,18 +78,29 @@ def __init__(self, apk, utils): self.file = '' self.found = [] - def add(self, offset, value): + def add(self, offset, value, stype="String"): if value not in self.found: self.found.append(value) - self.apk.strings.add(self.file, "String", offset, value) + self.apk.strings.add(self.file, stype, offset, value) def size(self): return len(self.found) def find_strings(offset, string, ctx): + if re.search(JAVA_REGEX, string, flags=re.M): + return None + + if is_hex(string): + ctx.add(offset, string, "hex") + return None + + if len(string) > 4 and is_base64(string): + ctx.add(offset, string, "base64") + return None + ustring = string.strip().upper() for key in STRINGS_SIGNATURES: - if key.upper() in ustring and not (re.search(JAVA_REGEX, string)): + if key.upper() in ustring: ctx.add(offset, string) return None @@ -52,4 +113,4 @@ def run_tests(apk, pipes, u, rzh, au): apk.logger.info("[OK] No interesting strings signatures found") def name_test(): - return "Detection of interesting string signatures" \ No newline at end of file + return "Detection of interesting string signatures" diff --git a/ios/tests/strings.py b/ios/tests/strings.py index 27af820..8dec97a 100644 --- a/ios/tests/strings.py +++ b/ios/tests/strings.py @@ -16,6 +16,83 @@ 'sha256', ] +HEX_REGEX = r'^[A-Fa-f0-9]{5,}$' +BASE64_REGEX = r'^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$' +BADB64_REGEX = r'^[A-Za-z$+]+$|^[A-Za-z/+]+$' + +KNOWN_BADB64 = [ + '0123456789abcdef', + '0123456789ABCDEF', + '0123456789ABCDEFGHJKMNPQRSTVWXYZ', + '0123456789ABCDEFGHIJKLMNOPQRSTUV', + '0123456789ABCDEFGHIJKLMNOPQRSTUVZ', + '0oO1iIlLAaBbCcDdEeFfGgHhJjKkMmNnPpQqRrSsTtVvWwXxYyZz', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ01234567', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', + 'getInt32', + 'getInt64', + 'GPBInt32DoubleDictionary', + 'GPBInt32ObjectDictionary', + 'GPBInt32UInt32Dictionary', + 'GPBInt32UInt64Dictionary', + 'GPBInt64DoubleDictionary', + 'GPBInt64ObjectDictionary', + 'GPBInt64UInt32Dictionary', + 'GPBInt64UInt64Dictionary', + 'GPBStringInt32Dictionary', + 'GPBStringInt64Dictionary', + 'GPBUInt32FloatDictionary', + 'GPBUInt32Int32Dictionary', + 'GPBUInt32Int64Dictionary', + 'GPBUInt64FloatDictionary', + 'GPBUInt64Int32Dictionary', + 'GPBUInt64Int64Dictionary', + 'ISO8601DateFormatter', + 'readSFixed32', + 'readSFixed64', + 'St9exception', + 'SyntaxProto2', + 'SyntaxProto3', + 'TypeSfixed32', + 'TypeSfixed64', + 'gost2001', + 'gost94cc', + 'hmacWithSHA1', + 'md2WithRSAEncryption', + 'md4WithRSAEncryption', + 'md5WithRSAEncryption', + 'ripemd160WithRSA', + 'x500UniqueIdentifier', + 'h7BadExpectedAccessE', + '0JSExecutorE', + '11ColumnNoVecEEE', + '4JSBigStdStringE', + '6InstanceCallbackEEE', + '6RowSumI', + '8MessageQueueThreadE', + '8RowNoVecEEE', + '9BaseValueEE' + 'N2cv13BaseRowFilterE', + 'N2cv6RowSumIddEE', + 'N2cv6RowSumIfdEE', + 'N2cv6RowSumIhdEE', + 'N2cv6RowSumIhiEE', + 'N2cv6RowSumIiiEE', + 'N2cv6RowSumIsdEE', + 'N2cv6RowSumIsiEE', + 'N2cv6RowSumItdEE', + 'N2cv6RowSumItiEE', + 'N5folly22OptionalEmptyExceptionE', + 'N8facebook3jsi15InstrumentationE', + 'N8facebook5react17JSBigBufferStringE', + 'N8facebook5react17JSExecutorFactoryE', + 'N8facebook5react17JSModulesUnbundleE', + 'N8facebook5react17RAMBundleRegistryE', +] + class ContextStrings(object): def __init__(self, ipa, utils, file): super(ContextStrings, self).__init__() @@ -24,15 +101,41 @@ def __init__(self, ipa, utils, file): self.file = file self.found = [] - def add(self, offset, value): + def add(self, offset, value, stype="String"): if value not in self.found: self.found.append(value) - self.ipa.strings.add(self.file, "String", offset, value) + self.ipa.strings.add(self.file, stype, offset, value) def size(self): return len(self.found) +def is_hex(value): + if value in KNOWN_BADB64: + return False + return re.match(HEX_REGEX, value, flags=re.M) + +def is_base64(value): + if value in KNOWN_BADB64: + return False + found = re.search(BASE64_REGEX, value, flags=re.M) + if found and not re.match(BADB64_REGEX, value, flags=re.M): + found = found.group(0) + try: + decoded = base64.b64decode(found.encode('ascii')) + return len(decoded) > 0 + except Exception: + pass + return False + def find_strings(offset, string, ctx): + if is_hex(string): + ctx.add(offset, string, "hex") + return None + + if len(string) > 4 and is_base64(string): + ctx.add(offset, string, "base64") + return None + ustring = string.strip().upper() for key in STRINGS_SIGNATURES: if key.upper() in ustring: @@ -46,4 +149,4 @@ def run_tests(ipa, pipe, u, rzh): ipa.logger.info("[OK] No interesting strings signatures found") def name_test(): - return "Detection of interesting string signatures" \ No newline at end of file + return "Detection of interesting string signatures" From be99135d90950c4a2875fdf3b95e4cd95fa4dfb6 Mon Sep 17 00:00:00 2001 From: wargio Date: Wed, 29 Jul 2020 17:33:04 +0200 Subject: [PATCH 02/14] added fullBackupOnly check in AndroidManifest --- android/tests/manifest_flags.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/android/tests/manifest_flags.py b/android/tests/manifest_flags.py index 482ced9..b94b18d 100644 --- a/android/tests/manifest_flags.py +++ b/android/tests/manifest_flags.py @@ -38,9 +38,10 @@ def find_any(apk, u, root, keys, attval, keywords, issue, descr, severity): def run_tests(apk, pipes, u, rzh, au): manifest = os.path.join(apk.apktool, "AndroidManifest.xml") root = ET.parse(manifest).getroot() - find_any(apk, u, root, DEBUGGABLE_APP_KEYS, "debuggable" , TRUES, DEBUGGABLE_APP_ISSUE, DEBUGGABLE_APP_DESCRIPTION, DEBUGGABLE_APP_SEVERITY) - find_any(apk, u, root, EXPORT_RCV_KEYS , "exported" , TRUES, EXPORT_RCV_ISSUE , EXPORT_RCV_DESCRIPTION , EXPORT_RCV_SEVERITY ) - find_any(apk, u, root, BACKUP_APP_KEYS , "allowBackup", TRUES, BACKUP_APP_ISSUE , BACKUP_APP_DESCRIPTION , BACKUP_APP_SEVERITY ) + find_any(apk, u, root, DEBUGGABLE_APP_KEYS, "debuggable" , TRUES, DEBUGGABLE_APP_ISSUE, DEBUGGABLE_APP_DESCRIPTION, DEBUGGABLE_APP_SEVERITY) + find_any(apk, u, root, EXPORT_RCV_KEYS , "exported" , TRUES, EXPORT_RCV_ISSUE , EXPORT_RCV_DESCRIPTION , EXPORT_RCV_SEVERITY ) + find_any(apk, u, root, BACKUP_APP_KEYS , "allowBackup" , TRUES, BACKUP_APP_ISSUE , BACKUP_APP_DESCRIPTION , BACKUP_APP_SEVERITY ) + find_any(apk, u, root, BACKUP_APP_KEYS , "fullBackupOnly", TRUES, BACKUP_APP_ISSUE , BACKUP_APP_DESCRIPTION , BACKUP_APP_SEVERITY ) def name_test(): return "Detection interesting tag flags in AndroidManifest.xml" From 6145b7ae14fb69bc48580165ce4b015caa1f813b Mon Sep 17 00:00:00 2001 From: wargio Date: Wed, 29 Jul 2020 17:31:21 +0200 Subject: [PATCH 03/14] Search for nonce --- android/tests/apikeys.py | 7 +++++-- ios/tests/apikeys.py | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/android/tests/apikeys.py b/android/tests/apikeys.py index 106393f..ef10c35 100644 --- a/android/tests/apikeys.py +++ b/android/tests/apikeys.py @@ -51,7 +51,7 @@ def run_tests(apk, pipes, u, rzh, au): elif ("api_key" in lkey or "apikey" in lkey) and " " not in value: details = "Insecure storage of a generic API key in application resource." descrip = "Easily discoverable of API key ({}: {}) embedded inside {}".format(key, value, file) - elif ("privatekey" in lkey or "private_key" in lkey) and " " not in value: + elif ("privatekey" in lkey or "private_key" in lkey or "secret" in lkey) and " " not in value: details = "Insecure storage of a generic Private Key in application resource." descrip = "Easily discoverable of Private Key ({}: {}) embedded inside {}".format(key, value, file) elif "secret" in lkey and " " not in value: @@ -69,9 +69,12 @@ def run_tests(apk, pipes, u, rzh, au): elif "seed" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Seed in application resource." descrip = "Easily discoverable of Seed ({}: {}) embedded inside {}".format(key, value, file) + elif "nonce" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): + details = "Insecure storage of a generic Nonce in application resource." + descrip = "Easily discoverable of Nonce ({}: {}) embedded inside {}".format(key, value, file) if len(descrip) > 0 and len(details) > 0: u.test(apk, False, details, descrip, severity) def name_test(): - return "Detection insecure API secrets values" \ No newline at end of file + return "Detection insecure API secrets values" diff --git a/ios/tests/apikeys.py b/ios/tests/apikeys.py index 9dac022..c59ecdd 100644 --- a/ios/tests/apikeys.py +++ b/ios/tests/apikeys.py @@ -67,6 +67,10 @@ def test_recursive(ipa, u, d, file): elif "seed" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Seed in application resource." descrip = "Easily discoverable of Seed ({}: {}) embedded inside {}".format(key, value, file) + elif "nonce" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): + details = "Insecure storage of a generic Nonce in application resource." + descrip = "Easily discoverable of Nonce ({}: {}) embedded inside {}".format(key, value, file) + if len(descrip) > 0 and len(details) > 0: u.test(ipa, False, details, descrip, severity) From 284dd18209198beded3ef7dae110bb750b4d9222 Mon Sep 17 00:00:00 2001 From: wargio Date: Wed, 29 Jul 2020 17:32:47 +0200 Subject: [PATCH 04/14] added firebaseio vulnerable instance detection --- android/tests/firebaseio.py | 73 ++++++++++++++++++++++++++++ ios/tests/firebaseio.py | 94 +++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + 3 files changed, 168 insertions(+) create mode 100644 android/tests/firebaseio.py create mode 100644 ios/tests/firebaseio.py diff --git a/android/tests/firebaseio.py b/android/tests/firebaseio.py new file mode 100644 index 0000000..67bb490 --- /dev/null +++ b/android/tests/firebaseio.py @@ -0,0 +1,73 @@ +## fufluns - Copyright 2021 - deroad + +import os +import xml.etree.ElementTree as ET +import urllib3 +import re + +urllib3.disable_warnings() + +## CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N +SEVERITY = 7.1 +DESCRIPTION = "Firebase Real-time Databases contains sensitive information on their users, including their email addresses, usernames, passwords, phone numbers, full names, chat messages and location data." +NO_NETWORK = "No network connection. Please check the network connectivity." +FIREBASE_REGEX = r'https*://(.+?)\.firebaseio.com' + +def run_tests(apk, rzs, u, rzh, au): + projects = [] + misconfigured = [] + + file = os.path.join("res", "values", "strings.xml") + manifest = os.path.join(apk.apktool, file) + apk.extra.add_text_file(manifest) + root = ET.parse(manifest).getroot() + tags = root.findall("string") + + for tag in tags: + if tag.text == None: + continue + project = re.findall(FIREBASE_REGEX, tag.text.strip()) + if len(project) > 0: + projects.extend(project) + + apk.logger.notify("Found {} firebaseio.com projects.".format(len(projects))) + + http = urllib3.PoolManager() + for project in projects: + url = 'https://{}.firebaseio.com/.json'.format(project) + try: + resp = http.request('GET', url) + if resp.status == 200: + misconfigured.append(project) + elif resp.status == 401: + apk.logger.info("[OK] https://{}.firebaseio.com is secure.".format(project)) + elif resp.status == 404: + apk.logger.notify("[--] https://{}.firebaseio.com/ was not found.".format(project)) + except urllib3.exceptions.NewConnectionError: + ipa.logger.error(NO_NETWORK) + return + except urllib3.exceptions.MaxRetryError: + ipa.logger.error(NO_NETWORK) + return + url = 'https://firestore.googleapis.com/v1/projects/{}/databases/(default)/'.format(project) + try: + resp = http.request('GET', url) + if resp.status == 200: + misconfigured.append(project) + elif resp.status == 401: + apk.logger.info("[OK] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is secure.".format(project)) + elif resp.status == 404: + apk.logger.notify("[--] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ was not found.".format(project)) + except urllib3.exceptions.NewConnectionError: + ipa.logger.error(NO_NETWORK) + return + except urllib3.exceptions.MaxRetryError: + ipa.logger.error(NO_NETWORK) + return + + + msg = "Misconfigured firebaseio instance {0} over {1} found.".format(len(misconfigured), len(projects)) + u.test(apk, len(misconfigured) < 1, msg, DESCRIPTION, SEVERITY) + +def name_test(): + return "Misconfigured Firebaseio Instance" \ No newline at end of file diff --git a/ios/tests/firebaseio.py b/ios/tests/firebaseio.py new file mode 100644 index 0000000..b65bc22 --- /dev/null +++ b/ios/tests/firebaseio.py @@ -0,0 +1,94 @@ +## fufluns - Copyright 2021 - deroad + +import glob +import os +import plistlib +import urllib3 +import re + +urllib3.disable_warnings() + +## CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N +SEVERITY = 7.1 +DESCRIPTION = "Firebase Real-time Databases contains sensitive information on their users, including their email addresses, usernames, passwords, phone numbers, full names, chat messages and location data." +NO_NETWORK = "No network connection. Please check the network connectivity." +FIREBASE_REGEX = rb'https*://(.+?)\.firebaseio.com' + +def iterate_object(obj): + projects = [] + if isinstance(obj, list): + for value in obj: + if isinstance(value, (list, dict)): + projects.extend(iterate_object(value)) + continue + elif not isinstance(value, str): + continue + value = value.encode('utf-8') + project = re.findall(FIREBASE_REGEX, value) + if len(project) > 0: + projects.extend(project) + else: + for key in obj: + value = obj[key] + if isinstance(value, (list, dict)): + projects.extend(iterate_object(value)) + continue + elif not isinstance(value, str): + continue + value = value.encode('utf-8') + project = re.findall(FIREBASE_REGEX, value) + if len(project) > 0: + projects.extend(project) + return projects + +def run_tests(ipa, rz, u, rzh): + projects = [] + misconfigured = [] + plists = glob.glob(os.path.join(ipa.directory, "**", "*.plist"), recursive=True) + for file in plists: + plist = plistlib.readPlist(file) + projects.extend(iterate_object(plist)) + + projects = list(filter(lambda x: len(x) > 0, projects)) + + ipa.logger.notify("Found {} firebaseio.com projects.".format(len(projects))) + + if len(projects) > 0: + http = urllib3.PoolManager() + for project in projects: + project = project.decode('utf-8') + url = 'https://{}.firebaseio.com/.json'.format(project) + try: + resp = http.request('GET', url) + if resp.status == 200: + misconfigured.append(project) + elif resp.status == 401: + ipa.logger.info("[OK] https://{}.firebaseio.com/ is secure.".format(project)) + elif resp.status == 404: + ipa.logger.notify("[--] https://{}.firebaseio.com/ was not found.".format(project)) + except urllib3.exceptions.NewConnectionError: + ipa.logger.error(NO_NETWORK) + return + except urllib3.exceptions.MaxRetryError: + ipa.logger.error(NO_NETWORK) + return + url = 'https://firestore.googleapis.com/v1/projects/{}/databases/(default)/'.format(project) + try: + resp = http.request('GET', url) + if resp.status == 200: + misconfigured.append(project) + elif resp.status == 401: + ipa.logger.info("[OK] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is secure.".format(project)) + elif resp.status == 404: + ipa.logger.notify("[--] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ was not found.".format(project)) + except urllib3.exceptions.NewConnectionError: + ipa.logger.error(NO_NETWORK) + return + except urllib3.exceptions.MaxRetryError: + ipa.logger.error(NO_NETWORK) + return + msg = "Misconfigured firebaseio instance {0} over {1} found.".format(len(misconfigured), len(projects)) + u.test(ipa, len(misconfigured) < 1, msg, DESCRIPTION, SEVERITY) + +def name_test(): + return "Misconfigured Firebaseio Instance" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 417e2b8..8a5a9d4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ tornado rzpipe +urllib3 From e33ea20be9951467c0e748cb393d08911127da7b Mon Sep 17 00:00:00 2001 From: wargio Date: Mon, 2 Nov 2020 12:04:16 +0100 Subject: [PATCH 05/14] Fixed small mistake on what to show on the UI. --- android/tests/manifest_flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/tests/manifest_flags.py b/android/tests/manifest_flags.py index b94b18d..ba67ac4 100644 --- a/android/tests/manifest_flags.py +++ b/android/tests/manifest_flags.py @@ -33,7 +33,7 @@ def find_any(apk, u, root, keys, attval, keywords, issue, descr, severity): if value in keywords: found += 1 if found > 0: - u.test(apk, False, DEBUGGABLE_APP_ISSUE, DEBUGGABLE_APP_DESCRIPTION, DEBUGGABLE_APP_SEVERITY) + u.test(apk, False, issue, descr, severity) def run_tests(apk, pipes, u, rzh, au): manifest = os.path.join(apk.apktool, "AndroidManifest.xml") From c82594b4a36a532a230b2cea90b81f1321112914 Mon Sep 17 00:00:00 2001 From: wargio Date: Thu, 5 Nov 2020 15:39:43 +0100 Subject: [PATCH 06/14] Added TrustKit test --- ios/tests/apptransportsecurity.py | 5 +++ ios/tests/trustkit.py | 70 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 ios/tests/trustkit.py diff --git a/ios/tests/apptransportsecurity.py b/ios/tests/apptransportsecurity.py index 0b01f50..a907c56 100644 --- a/ios/tests/apptransportsecurity.py +++ b/ios/tests/apptransportsecurity.py @@ -37,6 +37,11 @@ def run_tests(ipa, pipe, u, rzh): if len(tmp) > 0: plist = u.load_plist(tmp[0]) + # check for TrustKit + if u.dk(plist, "TSKConfiguration") != None: + ipa.logger.notify("TrustKit was found, so App Transport Security config is ignored.") + return + tmp = u.dk(plist, "NSAppTransportSecurity.NSExceptionDomains", {}) for domain in tmp.keys(): domain_msg = domain diff --git a/ios/tests/trustkit.py b/ios/tests/trustkit.py new file mode 100644 index 0000000..79b4887 --- /dev/null +++ b/ios/tests/trustkit.py @@ -0,0 +1,70 @@ +## fufluns - Copyright 2021 - deroad + +import glob +import os +import plistlib + +SEVERITY="severity" +DESCRIPTION="description" + +ISSU_NSURLSWIZZLING = "TrustKit swizzling on NSURL is disabled" +DESC_NSURLSWIZZLING = "Swizzling allows enabling pinning within an App without having to find every instance of NSURLConnection or NSURLSession delegates (best practice from docs)." +SVRT_NSURLSWIZZLING = 4.2 + +ISSU_NODOMAINS = "TrustKit No Pinned Domains" +DESC_NODOMAINS = "There are no pinned domains in the TrustKit configuration (maybe they are hardcoded in the code)." +SVRT_NODOMAINS = 8.2 + +ISSU_NOSUBDOMS = "TrustKit Pinning is not applied to subdomains on {}" +DESC_NOSUBDOMS = "TrustKit will not check pinning for all the subdomains of the specified domain." +SVRT_NOSUBDOMS = 4.3 + +ISSU_NOENFORCE = "TrustKit Pinning is not enforced on {}" +DESC_NOENFORCE = "TrustKit will not block SSL connections that caused a pin or certificate validation error." +SVRT_NOENFORCE = 8.2 + +ISSU_NOKEYHASHES = "TrustKit Public Key Hashes missing on {}" +DESC_NOKEYHASHES = "TrustKit will not be able to verify the certificate chain received from the server." +SVRT_NOKEYHASHES = 8.2 + +ISSU_NOBACKUPKEY = "TrustKit No Backup Public Key Hash on {}" +DESC_NOBACKUPKEY = "TrustKit documentation suggests to always provide at least one backup pin to prevent accidental blocking." +SVRT_NOBACKUPKEY = 8.2 + + +def run_tests(ipa, rz, u, rzh): + tmp = [f for f in glob.glob(os.path.join(ipa.directory, "Payload", "*", "Info.plist"), recursive=True)] + plist = {} + if len(tmp) > 0: + plist = plistlib.readPlist(tmp[0]) + + if u.dk(plist, "TSKConfiguration", None) == None: + ipa.logger.notify("TrustKit not found") + return + + ## Allows Arbitrary Loads + b = u.dk(plist, "TSKConfiguration.TSKSwizzleNetworkDelegates") + if b is not None: + u.test(ipa, not b, ISSU_NSURLSWIZZLING, DESC_NSURLSWIZZLING, SVRT_NSURLSWIZZLING) + + domains = u.dk(plist, "TSKConfiguration.TSKPinnedDomains", {}) + if len(domains.keys()) < 1: + u.test(ipa, False, ISSU_NODOMAINS, DESC_NODOMAINS, SVRT_NODOMAINS) + return + + for domain in domains: + config = domains[domain] + b = u.dk(config, "TSKIncludeSubdomains", False) + u.test(ipa, b, ISSU_NOSUBDOMS.format(domain), DESC_NOSUBDOMS, SVRT_NOSUBDOMS) + + b = u.dk(config, "TSKEnforcePinning", True) + u.test(ipa, b, ISSU_NOENFORCE.format(domain), DESC_NOENFORCE, SVRT_NOENFORCE) + + a = u.dk(config, "TSKPublicKeyHashes", []) + if len(a) < 1: + u.test(ipa, False, ISSU_NOKEYHASHES.format(domain), DESC_NOKEYHASHES, SVRT_NOKEYHASHES) + elif len(a) < 2: + u.test(ipa, False, ISSU_NOBACKUPKEY.format(domain), DESC_NOBACKUPKEY, SVRT_NOBACKUPKEY) + +def name_test(): + return "Detection TrustKit (Certificate Pinning)" \ No newline at end of file From 64a97c008884114d1c3b8b45734ab49c08cd47d8 Mon Sep 17 00:00:00 2001 From: wargio Date: Mon, 9 Nov 2020 17:38:43 +0100 Subject: [PATCH 07/14] fixed compiler flag message --- ios/tests/compiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/tests/compiler.py b/ios/tests/compiler.py index 6559bf3..b088af0 100644 --- a/ios/tests/compiler.py +++ b/ios/tests/compiler.py @@ -14,8 +14,8 @@ def run_tests(ipa, pipe, u, rzh): u.test(ipa, rzh.has_import(pipe, LIST_STACK_GUARD), "Stack smashing protection missing (-fstack-protector-all)", DESC_STACK_GUARD, SVRT_STACK_GUARD) - u.test(ipa, rzh.has_import(pipe, LIST_OBJC_ARC ), "Objective-C automatic reference counting (-fobjc-arc)", DESC_OBJC_ARC, SVRT_OBJC_ARC) + u.test(ipa, rzh.has_import(pipe, LIST_OBJC_ARC ), "Objective-C automatic reference counting is missing (-fobjc-arc)", DESC_OBJC_ARC, SVRT_OBJC_ARC) u.test(ipa, rzh.has_info (pipe, "pic" ), "Full ASLR support is missing (-pie)", DESC_PIE, SVRT_PIE) def name_test(): - return "Detection Compiler flags" \ No newline at end of file + return "Detection Compiler flags" From 9bdb57dc0752fce1025ce0fbe16ca312c432dce4 Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 12 Jun 2021 02:17:49 +0200 Subject: [PATCH 08/14] Show all the resources (xml) on android that are not views. --- android/tests/apikeys.py | 1 - android/tests/firebaseio.py | 1 - android/tests/resources.py | 38 +++++++++++++++++++++++++++++++++++++ report.py | 2 +- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 android/tests/resources.py diff --git a/android/tests/apikeys.py b/android/tests/apikeys.py index ef10c35..8c29657 100644 --- a/android/tests/apikeys.py +++ b/android/tests/apikeys.py @@ -32,7 +32,6 @@ def is_hex(value): def run_tests(apk, pipes, u, rzh, au): file = os.path.join("res", "values", "strings.xml") manifest = os.path.join(apk.apktool, file) - apk.extra.add_text_file(manifest) root = ET.parse(manifest).getroot() tags = root.findall("string") for tag in tags: diff --git a/android/tests/firebaseio.py b/android/tests/firebaseio.py index 67bb490..15e77f6 100644 --- a/android/tests/firebaseio.py +++ b/android/tests/firebaseio.py @@ -19,7 +19,6 @@ def run_tests(apk, rzs, u, rzh, au): file = os.path.join("res", "values", "strings.xml") manifest = os.path.join(apk.apktool, file) - apk.extra.add_text_file(manifest) root = ET.parse(manifest).getroot() tags = root.findall("string") diff --git a/android/tests/resources.py b/android/tests/resources.py new file mode 100644 index 0000000..9c168b8 --- /dev/null +++ b/android/tests/resources.py @@ -0,0 +1,38 @@ +## fufluns - Copyright 2021 - deroad + +import glob +import os + +SKIP_FILES = [ + '/res/anim' + '/res/color', + '/res/drawable', + '/res/layout', + '/res/mipmap', + '/res/menu' +] + +def can_skip(file): + for prefix in SKIP_FILES: + if file.startswith(prefix): + return True + return False + +def run_tests(apk, rz, u, rzh, au): + files = glob.glob(os.path.join(apk.apktool, "**", "*.xml"), recursive=True) + dirlen = len(apk.apktool) + resources = 0 + for file in files: + fpath = file[dirlen:] + + if can_skip(fpath): + continue + + resources += 1 + with open(file, 'r', errors='replace') as fp: + text = "".join(fp.readlines()) + apk.extra.add(fpath, text) + apk.logger.notify("Found {} resources.".format(resources)) + +def name_test(): + return "Application resources" diff --git a/report.py b/report.py index 9c5c980..5cb2a9d 100644 --- a/report.py +++ b/report.py @@ -171,7 +171,7 @@ def add(self, key, string): def add_text_file(self, filename): basename = os.path.basename(filename) - with open(filename, "r") as fp: + with open(filename, 'r', errors='replace') as fp: self.add(basename, "".join(fp.readlines())) def json(self): From 704bc412dba033a8bab8ec5da4bcfa02f91a5b2d Mon Sep 17 00:00:00 2001 From: wargio Date: Tue, 10 Nov 2020 12:43:46 +0100 Subject: [PATCH 09/14] filter more files. --- android/tests/manifest.py | 11 ----------- android/tests/resources.py | 14 ++++++++++++-- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/android/tests/manifest.py b/android/tests/manifest.py index bc558c9..e69de29 100644 --- a/android/tests/manifest.py +++ b/android/tests/manifest.py @@ -1,11 +0,0 @@ -## fufluns - Copyright 2019-2021 - deroad - -import os - -def run_tests(apk, pipes, u, rzh, au): - manifest = os.path.join(apk.apktool, "AndroidManifest.xml") - apk.extra.add_text_file(manifest) - apk.logger.notify("AndroidManifest found.") - -def name_test(): - return "Dumping AndroidManifest.xml" \ No newline at end of file diff --git a/android/tests/resources.py b/android/tests/resources.py index 9c168b8..04c1210 100644 --- a/android/tests/resources.py +++ b/android/tests/resources.py @@ -4,12 +4,22 @@ import os SKIP_FILES = [ - '/res/anim' + '/res/anim', '/res/color', + '/res/colour', '/res/drawable', + '/res/font', '/res/layout', + '/res/menu', '/res/mipmap', - '/res/menu' + # langs usually are `/res/values-en` `/res/values-fr`.. + '/res/values-', + # fullpaths + '/original/AndroidManifest.xml', + '/res/values/colors.xml', + '/res/values/dimens.xml', + '/res/values/drawables.xml', + '/res/values/styles.xml' ] def can_skip(file): From 82f109d622942ef901220a768d3b02bdc9a1eef1 Mon Sep 17 00:00:00 2001 From: wargio Date: Thu, 26 Nov 2020 14:24:08 +0100 Subject: [PATCH 10/14] Allow uploads of files up to 1GB --- web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web.py b/web.py index f9707b5..8782fdf 100644 --- a/web.py +++ b/web.py @@ -70,6 +70,6 @@ def __init__(self, core, listen=8080, proto="http", debug=False): self.app = make_app({'debug': debug}, dict(core=core)) def run(self): - self.app.listen(self.listen) + self.app.listen(self.listen, max_buffer_size=1073741274) # 1GB max print("Server available at {proto}://localhost:{port}".format(proto=self.proto, port=self.listen)) tornado.ioloop.IOLoop.instance().start() \ No newline at end of file From 75dda41dd4ef5231467a48e8643bd8da54323ddd Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 12 Jun 2021 02:40:01 +0200 Subject: [PATCH 11/14] fixed english --- android/tests/apikeys.py | 18 +++++++++--------- ios/tests/apikeys.py | 16 ++++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/android/tests/apikeys.py b/android/tests/apikeys.py index 8c29657..8b637fd 100644 --- a/android/tests/apikeys.py +++ b/android/tests/apikeys.py @@ -49,28 +49,28 @@ def run_tests(apk, pipes, u, rzh, au): descrip = "Easily discoverable of {} ({}: {}) embedded inside {}".format(common_api_keys[key][API_DESCRIPTION], key, value, file) elif ("api_key" in lkey or "apikey" in lkey) and " " not in value: details = "Insecure storage of a generic API key in application resource." - descrip = "Easily discoverable of API key ({}: {}) embedded inside {}".format(key, value, file) - elif ("privatekey" in lkey or "private_key" in lkey or "secret" in lkey) and " " not in value: + descrip = "Easily discoverable API key ({}: {}) embedded inside {}".format(key, value, file) + elif ("privatekey" in lkey or "private_key" in lkey) and " " not in value: details = "Insecure storage of a generic Private Key in application resource." - descrip = "Easily discoverable of Private Key ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Private Key ({}: {}) embedded inside {}".format(key, value, file) elif "secret" in lkey and " " not in value: details = "Insecure storage of a generic Secret in application resource." - descrip = "Easily discoverable of Secret ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Secret ({}: {}) embedded inside {}".format(key, value, file) elif ("appkey" in lkey or "app_key" in lkey) and " " not in value: details = "Insecure storage of a generic Application Key in application resource." - descrip = "Easily discoverable of Application Key ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Application Key ({}: {}) embedded inside {}".format(key, value, file) elif "password" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Password in application resource." - descrip = "Easily discoverable of Password ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Password ({}: {}) embedded inside {}".format(key, value, file) elif "token" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Token in application resource." - descrip = "Easily discoverable of Token ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Token ({}: {}) embedded inside {}".format(key, value, file) elif "seed" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Seed in application resource." - descrip = "Easily discoverable of Seed ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Seed ({}: {}) embedded inside {}".format(key, value, file) elif "nonce" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Nonce in application resource." - descrip = "Easily discoverable of Nonce ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Nonce ({}: {}) embedded inside {}".format(key, value, file) if len(descrip) > 0 and len(details) > 0: u.test(apk, False, details, descrip, severity) diff --git a/ios/tests/apikeys.py b/ios/tests/apikeys.py index c59ecdd..1868c5a 100644 --- a/ios/tests/apikeys.py +++ b/ios/tests/apikeys.py @@ -48,28 +48,28 @@ def test_recursive(ipa, u, d, file): continue elif ("api_key" in lkey or "apikey" in lkey) and " " not in value: details = "Insecure storage of a generic API key in application resource." - descrip = "Easily discoverable of API key ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable API key ({}: {}) embedded inside {}".format(key, value, file) elif ("privatekey" in lkey or "private_key" in lkey) and " " not in value: details = "Insecure storage of a generic Private Key in application resource." - descrip = "Easily discoverable of Private Key ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Private Key ({}: {}) embedded inside {}".format(key, value, file) elif "secret" in lkey and " " not in value: details = "Insecure storage of a generic Secret in application resource." - descrip = "Easily discoverable of Secret ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Secret ({}: {}) embedded inside {}".format(key, value, file) elif ("appkey" in lkey or "app_key" in lkey) and " " not in value: details = "Insecure storage of a generic Application Key in application resource." - descrip = "Easily discoverable of Application Key ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Application Key ({}: {}) embedded inside {}".format(key, value, file) elif "password" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Password in application resource." - descrip = "Easily discoverable of Password ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Password ({}: {}) embedded inside {}".format(key, value, file) elif "token" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Token in application resource." - descrip = "Easily discoverable of Token ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Token ({}: {}) embedded inside {}".format(key, value, file) elif "seed" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Seed in application resource." - descrip = "Easily discoverable of Seed ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Seed ({}: {}) embedded inside {}".format(key, value, file) elif "nonce" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): details = "Insecure storage of a generic Nonce in application resource." - descrip = "Easily discoverable of Nonce ({}: {}) embedded inside {}".format(key, value, file) + descrip = "Easily discoverable Nonce ({}: {}) embedded inside {}".format(key, value, file) if len(descrip) > 0 and len(details) > 0: u.test(ipa, False, details, descrip, severity) From bc3b83e2df92347556d0564d2a42056463988a88 Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 12 Jun 2021 02:40:34 +0200 Subject: [PATCH 12/14] fixed plist and logs issues --- android/tests/firebaseio.py | 9 +++++---- android/tests/manifest.py | 0 ios/tests/firebaseio.py | 4 ++-- ios/tests/strings.py | 2 ++ ios/tests/trustkit.py | 3 +-- 5 files changed, 10 insertions(+), 8 deletions(-) delete mode 100644 android/tests/manifest.py diff --git a/android/tests/firebaseio.py b/android/tests/firebaseio.py index 15e77f6..71d13cc 100644 --- a/android/tests/firebaseio.py +++ b/android/tests/firebaseio.py @@ -38,15 +38,16 @@ def run_tests(apk, rzs, u, rzh, au): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) + apk.logger.error("[XX] https://{}.firebaseio.com/ is insecure.".format(project)) elif resp.status == 401: apk.logger.info("[OK] https://{}.firebaseio.com is secure.".format(project)) elif resp.status == 404: apk.logger.notify("[--] https://{}.firebaseio.com/ was not found.".format(project)) except urllib3.exceptions.NewConnectionError: - ipa.logger.error(NO_NETWORK) + apk.logger.error(NO_NETWORK) return except urllib3.exceptions.MaxRetryError: - ipa.logger.error(NO_NETWORK) + apk.logger.error(NO_NETWORK) return url = 'https://firestore.googleapis.com/v1/projects/{}/databases/(default)/'.format(project) try: @@ -58,10 +59,10 @@ def run_tests(apk, rzs, u, rzh, au): elif resp.status == 404: apk.logger.notify("[--] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ was not found.".format(project)) except urllib3.exceptions.NewConnectionError: - ipa.logger.error(NO_NETWORK) + apk.logger.error(NO_NETWORK) return except urllib3.exceptions.MaxRetryError: - ipa.logger.error(NO_NETWORK) + apk.logger.error(NO_NETWORK) return diff --git a/android/tests/manifest.py b/android/tests/manifest.py deleted file mode 100644 index e69de29..0000000 diff --git a/ios/tests/firebaseio.py b/ios/tests/firebaseio.py index b65bc22..9839998 100644 --- a/ios/tests/firebaseio.py +++ b/ios/tests/firebaseio.py @@ -2,7 +2,6 @@ import glob import os -import plistlib import urllib3 import re @@ -46,7 +45,7 @@ def run_tests(ipa, rz, u, rzh): misconfigured = [] plists = glob.glob(os.path.join(ipa.directory, "**", "*.plist"), recursive=True) for file in plists: - plist = plistlib.readPlist(file) + plist = u.load_plist(file) projects.extend(iterate_object(plist)) projects = list(filter(lambda x: len(x) > 0, projects)) @@ -62,6 +61,7 @@ def run_tests(ipa, rz, u, rzh): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) + ipa.logger.error("[XX] https://{}.firebaseio.com/ is insecure.".format(project)) elif resp.status == 401: ipa.logger.info("[OK] https://{}.firebaseio.com/ is secure.".format(project)) elif resp.status == 404: diff --git a/ios/tests/strings.py b/ios/tests/strings.py index 8dec97a..837b509 100644 --- a/ios/tests/strings.py +++ b/ios/tests/strings.py @@ -1,5 +1,7 @@ ## fufluns - Copyright 2019-2021 - deroad +import re, base64 + STRINGS_SIGNATURES = [ ':"', ': "', diff --git a/ios/tests/trustkit.py b/ios/tests/trustkit.py index 79b4887..6c59240 100644 --- a/ios/tests/trustkit.py +++ b/ios/tests/trustkit.py @@ -2,7 +2,6 @@ import glob import os -import plistlib SEVERITY="severity" DESCRIPTION="description" @@ -36,7 +35,7 @@ def run_tests(ipa, rz, u, rzh): tmp = [f for f in glob.glob(os.path.join(ipa.directory, "Payload", "*", "Info.plist"), recursive=True)] plist = {} if len(tmp) > 0: - plist = plistlib.readPlist(tmp[0]) + plist = u.load_plist(tmp[0]) if u.dk(plist, "TSKConfiguration", None) == None: ipa.logger.notify("TrustKit not found") From 889eba542c5ac37be81bdeff6458d9f0e3c2c013 Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 12 Jun 2021 02:48:12 +0200 Subject: [PATCH 13/14] fixed logs --- android/tests/firebaseio.py | 1 + ios/tests/firebaseio.py | 1 + 2 files changed, 2 insertions(+) diff --git a/android/tests/firebaseio.py b/android/tests/firebaseio.py index 71d13cc..e27323a 100644 --- a/android/tests/firebaseio.py +++ b/android/tests/firebaseio.py @@ -54,6 +54,7 @@ def run_tests(apk, rzs, u, rzh, au): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) + apk.logger.error("[XX] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is insecure.".format(project)) elif resp.status == 401: apk.logger.info("[OK] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is secure.".format(project)) elif resp.status == 404: diff --git a/ios/tests/firebaseio.py b/ios/tests/firebaseio.py index 9839998..bd83882 100644 --- a/ios/tests/firebaseio.py +++ b/ios/tests/firebaseio.py @@ -77,6 +77,7 @@ def run_tests(ipa, rz, u, rzh): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) + ipa.logger.error("[XX] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is insecure.".format(project)) elif resp.status == 401: ipa.logger.info("[OK] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is secure.".format(project)) elif resp.status == 404: From 49c64eb4e6d9d3cc6f56ee3a5df60410b419b8b6 Mon Sep 17 00:00:00 2001 From: wargio Date: Sat, 12 Jun 2021 03:05:31 +0200 Subject: [PATCH 14/14] fixed severity and firebase --- android/tests/apikeys.py | 7 ++++--- android/tests/firebaseio.py | 9 ++++++--- ios/tests/apikeys.py | 27 +++++++++++++++++++-------- ios/tests/firebaseio.py | 11 ++++++++--- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/android/tests/apikeys.py b/android/tests/apikeys.py index 8b637fd..1633718 100644 --- a/android/tests/apikeys.py +++ b/android/tests/apikeys.py @@ -4,6 +4,7 @@ import re import xml.etree.ElementTree as ET +API_GOOGLE_SEVERITY = 2.5 API_DEFAULT_SEVERITY = 6.5 API_DETAILS = "details" @@ -11,9 +12,9 @@ API_SEVERITY = "severity" common_api_keys = { - "google_api_key": { API_SEVERITY: API_DEFAULT_SEVERITY, API_DETAILS: "Google API Key found", API_DESCRIPTION: "Google API Key" }, - "google_maps_key": { API_SEVERITY: API_DEFAULT_SEVERITY, API_DETAILS: "Google Maps Key found", API_DESCRIPTION: "Google Maps Key" }, - "google_crash_reporting_api_key": { API_SEVERITY: API_DEFAULT_SEVERITY, API_DETAILS: "Google Crash Report found", API_DESCRIPTION: "Google Crash Report API Key" }, + "google_api_key": { API_SEVERITY: API_GOOGLE_SEVERITY, API_DETAILS: "Google API Key found", API_DESCRIPTION: "Google API Key" }, + "google_maps_key": { API_SEVERITY: API_GOOGLE_SEVERITY, API_DETAILS: "Google Maps Key found", API_DESCRIPTION: "Google Maps Key" }, + "google_crash_reporting_api_key": { API_SEVERITY: API_GOOGLE_SEVERITY, API_DETAILS: "Google Crash Report found", API_DESCRIPTION: "Google Crash Report API Key" }, "seed_crypto_keystore_password": { API_SEVERITY: API_DEFAULT_SEVERITY, API_DETAILS: "Seed Crypto Keystore Password found", API_DESCRIPTION: "Seed Crypto Keystore Password" }, "seed_crypto_privatekey_alias": { API_SEVERITY: API_DEFAULT_SEVERITY, API_DETAILS: "Seed Crypto Privatekey Alias found", API_DESCRIPTION: "Seed Crypto Privatekey Alias" }, "seed_crypto_privatekey_password": { API_SEVERITY: API_DEFAULT_SEVERITY, API_DETAILS: "Seed Crypto Privatekey Password found", API_DESCRIPTION: "Seed Crypto Privatekey Password" }, diff --git a/android/tests/firebaseio.py b/android/tests/firebaseio.py index e27323a..b51d0f4 100644 --- a/android/tests/firebaseio.py +++ b/android/tests/firebaseio.py @@ -38,7 +38,7 @@ def run_tests(apk, rzs, u, rzh, au): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) - apk.logger.error("[XX] https://{}.firebaseio.com/ is insecure.".format(project)) + apk.logger.warning("[XX] https://{}.firebaseio.com/ is insecure.".format(project)) elif resp.status == 401: apk.logger.info("[OK] https://{}.firebaseio.com is secure.".format(project)) elif resp.status == 404: @@ -54,7 +54,7 @@ def run_tests(apk, rzs, u, rzh, au): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) - apk.logger.error("[XX] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is insecure.".format(project)) + apk.logger.warning("[XX] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is insecure.".format(project)) elif resp.status == 401: apk.logger.info("[OK] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is secure.".format(project)) elif resp.status == 404: @@ -66,8 +66,11 @@ def run_tests(apk, rzs, u, rzh, au): apk.logger.error(NO_NETWORK) return + verb = "found" + if len(misconfigured) < 1: + verb = "not found" - msg = "Misconfigured firebaseio instance {0} over {1} found.".format(len(misconfigured), len(projects)) + msg = "Misconfigured firebaseio instance {}.".format(verb) u.test(apk, len(misconfigured) < 1, msg, DESCRIPTION, SEVERITY) def name_test(): diff --git a/ios/tests/apikeys.py b/ios/tests/apikeys.py index 1868c5a..bd429cc 100644 --- a/ios/tests/apikeys.py +++ b/ios/tests/apikeys.py @@ -4,7 +4,12 @@ import os import re +API_GOOGLE_FILE = "GoogleService-Info.plist" +API_GOOGLE_PROVIDER = "Google" +API_GOOGLE_SEVERITY = 2.5 + API_DEFAULT_SEVERITY = 6.5 +API_DEFAULT_PROVIDER = "generic" API_DETAILS = "details" API_DESCRIPTION = "description" @@ -35,8 +40,14 @@ def test_recursive(ipa, u, d, file): return for key in d: severity = API_DEFAULT_SEVERITY + provider = API_DEFAULT_PROVIDER details = "" descrip = "" + + if file.endswith(API_GOOGLE_FILE): + severity = API_GOOGLE_SEVERITY + provider = API_GOOGLE_PROVIDER + lkey = key.lower() if key in common_api_keys: continue @@ -47,28 +58,28 @@ def test_recursive(ipa, u, d, file): elif not isinstance(value, str): continue elif ("api_key" in lkey or "apikey" in lkey) and " " not in value: - details = "Insecure storage of a generic API key in application resource." + details = "Insecure storage of a {} API key in application resource.".format(provider) descrip = "Easily discoverable API key ({}: {}) embedded inside {}".format(key, value, file) elif ("privatekey" in lkey or "private_key" in lkey) and " " not in value: - details = "Insecure storage of a generic Private Key in application resource." + details = "Insecure storage of a {} Private Key in application resource.".format(provider) descrip = "Easily discoverable Private Key ({}: {}) embedded inside {}".format(key, value, file) elif "secret" in lkey and " " not in value: - details = "Insecure storage of a generic Secret in application resource." + details = "Insecure storage of a {} Secret in application resource.".format(provider) descrip = "Easily discoverable Secret ({}: {}) embedded inside {}".format(key, value, file) elif ("appkey" in lkey or "app_key" in lkey) and " " not in value: - details = "Insecure storage of a generic Application Key in application resource." + details = "Insecure storage of a {} Application Key in application resource.".format(provider) descrip = "Easily discoverable Application Key ({}: {}) embedded inside {}".format(key, value, file) elif "password" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): - details = "Insecure storage of a generic Password in application resource." + details = "Insecure storage of a {} Password in application resource.".format(provider) descrip = "Easily discoverable Password ({}: {}) embedded inside {}".format(key, value, file) elif "token" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): - details = "Insecure storage of a generic Token in application resource." + details = "Insecure storage of a {} Token in application resource.".format(provider) descrip = "Easily discoverable Token ({}: {}) embedded inside {}".format(key, value, file) elif "seed" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): - details = "Insecure storage of a generic Seed in application resource." + details = "Insecure storage of a {} Seed in application resource.".format(provider) descrip = "Easily discoverable Seed ({}: {}) embedded inside {}".format(key, value, file) elif "nonce" in lkey and (is_base64(value) or is_uuid(value) or is_hex(value)): - details = "Insecure storage of a generic Nonce in application resource." + details = "Insecure storage of a {} Nonce in application resource.".format(provider) descrip = "Easily discoverable Nonce ({}: {}) embedded inside {}".format(key, value, file) if len(descrip) > 0 and len(details) > 0: diff --git a/ios/tests/firebaseio.py b/ios/tests/firebaseio.py index bd83882..834d373 100644 --- a/ios/tests/firebaseio.py +++ b/ios/tests/firebaseio.py @@ -61,7 +61,7 @@ def run_tests(ipa, rz, u, rzh): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) - ipa.logger.error("[XX] https://{}.firebaseio.com/ is insecure.".format(project)) + ipa.logger.warning("[XX] https://{}.firebaseio.com/ is insecure.".format(project)) elif resp.status == 401: ipa.logger.info("[OK] https://{}.firebaseio.com/ is secure.".format(project)) elif resp.status == 404: @@ -77,7 +77,7 @@ def run_tests(ipa, rz, u, rzh): resp = http.request('GET', url) if resp.status == 200: misconfigured.append(project) - ipa.logger.error("[XX] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is insecure.".format(project)) + ipa.logger.warning("[XX] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is insecure.".format(project)) elif resp.status == 401: ipa.logger.info("[OK] https://firestore.googleapis.com/v1/projects/{}/databases/(default)/ is secure.".format(project)) elif resp.status == 404: @@ -88,7 +88,12 @@ def run_tests(ipa, rz, u, rzh): except urllib3.exceptions.MaxRetryError: ipa.logger.error(NO_NETWORK) return - msg = "Misconfigured firebaseio instance {0} over {1} found.".format(len(misconfigured), len(projects)) + + verb = "found" + if len(misconfigured) < 1: + verb = "not found" + + msg = "Misconfigured firebaseio instance {}.".format(verb) u.test(ipa, len(misconfigured) < 1, msg, DESCRIPTION, SEVERITY) def name_test():