narvy-cli 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- narvy/__init__.py +3 -0
- narvy/android/__init__.py +0 -0
- narvy/android/source_analyzer.py +268 -0
- narvy/android/split_bundle.py +475 -0
- narvy/android_rule_context.py +196 -0
- narvy/apk_memory_preflight.py +248 -0
- narvy/auth.py +40 -0
- narvy/ci_templates/bitbucket.yml +25 -0
- narvy/ci_templates/github.yml +60 -0
- narvy/ci_templates/gitlab.yml +32 -0
- narvy/cloud/__init__.py +0 -0
- narvy/cloud/aws_scan.py +1188 -0
- narvy/comment_filter.py +134 -0
- narvy/crypto_taint_lite.py +82 -0
- narvy/decompiler.py +337 -0
- narvy/doctor.py +244 -0
- narvy/host/__init__.py +0 -0
- narvy/host/audit.py +157 -0
- narvy/host/host_knowledge.py +982 -0
- narvy/host/lynis_bootstrap.py +400 -0
- narvy/host/lynis_parser.py +358 -0
- narvy/host/narvy_checks.py +789 -0
- narvy/host/report.py +69 -0
- narvy/host/ssh_exec.py +219 -0
- narvy/ios/__init__.py +0 -0
- narvy/ios/binary_analyzer.py +1201 -0
- narvy/ios/plist_checks.py +249 -0
- narvy/ios/source_analyzer.py +301 -0
- narvy/ios/third_party_filter.py +242 -0
- narvy/ios/trust_all_context.py +66 -0
- narvy/main.py +1983 -0
- narvy/native_hardening.py +503 -0
- narvy/reporter.py +151 -0
- narvy/rule_engine.py +57 -0
- narvy/rules/android/config.yml +77 -0
- narvy/rules/android/crypto.yml +34 -0
- narvy/rules/android/secrets.yml +161 -0
- narvy/rules/android/storage.yml +24 -0
- narvy/rules/android/webview.yml +23 -0
- narvy/rules/android.yml +219 -0
- narvy/rules/ios/objc/crypto.yml +56 -0
- narvy/rules/ios/objc/network.yml +67 -0
- narvy/rules/ios/objc/secrets.yml +79 -0
- narvy/rules/ios/objc/storage.yml +45 -0
- narvy/rules/ios/objc/webview.yml +45 -0
- narvy/rules/ios_swift.yml +327 -0
- narvy/rules/web/go.yml +2837 -0
- narvy/rules/web/java.yml +1576 -0
- narvy/rules/web/javascript.yml +3683 -0
- narvy/rules/web/kotlin.yml +413 -0
- narvy/rules/web/local/csharp_narvy/config.yml +61 -0
- narvy/rules/web/local/csharp_narvy/crypto.yml +64 -0
- narvy/rules/web/local/csharp_narvy/deserialization.yml +59 -0
- narvy/rules/web/local/csharp_narvy/injection.yml +122 -0
- narvy/rules/web/local/csharp_narvy/xxe.yml +48 -0
- narvy/rules/web/local/java_narvy/auth_jwt.yml +134 -0
- narvy/rules/web/local/java_narvy/deserialization.yml +108 -0
- narvy/rules/web/local/java_narvy/mybatis.yml +39 -0
- narvy/rules/web/local/java_narvy/snakeyaml.yml +34 -0
- narvy/rules/web/local/java_narvy/spring_authz.yml +32 -0
- narvy/rules/web/local/java_narvy/spring_config.yml +67 -0
- narvy/rules/web/local/java_narvy/spring_hardening.yml +291 -0
- narvy/rules/web/local/java_narvy/sqli.yml +235 -0
- narvy/rules/web/local/java_narvy/xxe.yml +212 -0
- narvy/rules/web/php.yml +1644 -0
- narvy/rules/web/python.yml +3967 -0
- narvy/rules/web/ruby.yml +703 -0
- narvy/rules/web/rust.yml +258 -0
- narvy/rules/web/secrets.yml +1420 -0
- narvy/rules/web/secrets_supplement.yml +383 -0
- narvy/sca/__init__.py +1 -0
- narvy/sca/android_deps.py +349 -0
- narvy/sca/ios_deps.py +578 -0
- narvy/sca/osv_client.py +617 -0
- narvy/sca/web_deps.py +955 -0
- narvy/scope_config.py +195 -0
- narvy/semgrep_engine.py +219 -0
- narvy/stack_protector_evidence.py +96 -0
- narvy/third_party_filter.py +211 -0
- narvy/uploader.py +92 -0
- narvy/weak_prng_context.py +270 -0
- narvy/web/__init__.py +0 -0
- narvy/web/nuclei_binary.py +105 -0
- narvy/web/scan_blocklist.py +96 -0
- narvy/web/scanner.py +842 -0
- narvy/web/source_analyzer.py +714 -0
- narvy/web/ssrf_guard.py +374 -0
- narvy_cli-1.0.0.dist-info/METADATA +165 -0
- narvy_cli-1.0.0.dist-info/RECORD +93 -0
- narvy_cli-1.0.0.dist-info/WHEEL +5 -0
- narvy_cli-1.0.0.dist-info/entry_points.txt +2 -0
- narvy_cli-1.0.0.dist-info/licenses/LICENSE +202 -0
- narvy_cli-1.0.0.dist-info/top_level.txt +1 -0
narvy/rule_engine.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
import re
|
|
3
|
+
import os
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
|
|
6
|
+
from .android_rule_context import apply_gate
|
|
7
|
+
from . import comment_filter
|
|
8
|
+
|
|
9
|
+
def load_rules_from_dir(rules_directory: str) -> List[Dict[str, Any]]:
|
|
10
|
+
"""Load rules from a directory of YAML files."""
|
|
11
|
+
all_rules = []
|
|
12
|
+
for filename in os.listdir(rules_directory):
|
|
13
|
+
if filename.endswith(('.yml', '.yaml')):
|
|
14
|
+
with open(os.path.join(rules_directory, filename), 'r') as f:
|
|
15
|
+
rules_in_file = yaml.safe_load(f)
|
|
16
|
+
if isinstance(rules_in_file, list):
|
|
17
|
+
all_rules.extend(rules_in_file)
|
|
18
|
+
# YAML `|` block scalars keep a trailing newline; strip it off patterns.
|
|
19
|
+
for rule in all_rules:
|
|
20
|
+
pattern = rule.get('pattern')
|
|
21
|
+
if isinstance(pattern, str):
|
|
22
|
+
rule['pattern'] = pattern.strip()
|
|
23
|
+
return all_rules
|
|
24
|
+
|
|
25
|
+
def run_rules_on_file(file_path: str, rules: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
26
|
+
"""Run the rules against a single file, one finding per occurrence."""
|
|
27
|
+
findings = []
|
|
28
|
+
try:
|
|
29
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
30
|
+
content = f.read()
|
|
31
|
+
|
|
32
|
+
# Comment-aware post-filter, built only when a comment opener is present.
|
|
33
|
+
comment_mask = None
|
|
34
|
+
if '//' in content or '/*' in content:
|
|
35
|
+
comment_mask = comment_filter.build_comment_mask(content)
|
|
36
|
+
|
|
37
|
+
for rule in rules:
|
|
38
|
+
for match in re.finditer(rule['pattern'], content, re.MULTILINE):
|
|
39
|
+
if comment_mask is not None and comment_mask[match.start()]:
|
|
40
|
+
continue
|
|
41
|
+
line = content.count('\n', 0, match.start()) + 1
|
|
42
|
+
finding = {
|
|
43
|
+
"rule_id": rule['id'],
|
|
44
|
+
"file_path": file_path,
|
|
45
|
+
"name": rule['name'],
|
|
46
|
+
"severity": rule['severity'],
|
|
47
|
+
"confidence": rule.get('confidence', 'MEDIUM'),
|
|
48
|
+
"details": rule['details'],
|
|
49
|
+
"line": line,
|
|
50
|
+
}
|
|
51
|
+
# Fail-open: any gate error keeps the finding.
|
|
52
|
+
finding = apply_gate(rule['id'], content, match, finding)
|
|
53
|
+
if finding is not None:
|
|
54
|
+
findings.append(finding)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
print(f"Could not analyze file {file_path}: {e}")
|
|
57
|
+
return findings
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Android manifest and configuration checks.
|
|
2
|
+
|
|
3
|
+
- id: AND-CONF-001
|
|
4
|
+
name: "Application is Debuggable"
|
|
5
|
+
pattern: 'android:debuggable\s*=\s*"true"'
|
|
6
|
+
severity: "CRITICAL"
|
|
7
|
+
confidence: "HIGH"
|
|
8
|
+
details:
|
|
9
|
+
cwe: "CWE-489"
|
|
10
|
+
masvs: "MSTG-CODE-2"
|
|
11
|
+
description: "The application is set as debuggable, allowing attackers to attach a debugger and inspect its runtime behavior. This must be disabled in release builds."
|
|
12
|
+
recommendation: "Set android:debuggable to 'false' in the AndroidManifest.xml for all release builds."
|
|
13
|
+
- id: AND-CONF-002
|
|
14
|
+
name: "Application Data Backup is Enabled"
|
|
15
|
+
pattern: 'android:allowBackup\s*=\s*"true"'
|
|
16
|
+
severity: "HIGH"
|
|
17
|
+
confidence: "HIGH"
|
|
18
|
+
details:
|
|
19
|
+
cwe: "CWE-530"
|
|
20
|
+
masvs: "MSTG-STORAGE-8"
|
|
21
|
+
description: "The application's data can be backed up via ADB. If sensitive data is stored within the app, it can be extracted by anyone with physical access to the device."
|
|
22
|
+
recommendation: "If not required, set android:allowBackup to 'false'. If it is, use auto-backup rules to exclude sensitive data."
|
|
23
|
+
- id: AND-CONF-003
|
|
24
|
+
name: "Cleartext HTTP Traffic is Permitted"
|
|
25
|
+
pattern: 'android:usesCleartextTraffic\s*=\s*"true"'
|
|
26
|
+
severity: "HIGH"
|
|
27
|
+
confidence: "HIGH"
|
|
28
|
+
details:
|
|
29
|
+
cwe: "CWE-319"
|
|
30
|
+
masvs: "MSTG-NETWORK-1"
|
|
31
|
+
description: "The application is configured to permit cleartext HTTP traffic, exposing data to network sniffing."
|
|
32
|
+
recommendation: "Set android:usesCleartextTraffic to 'false' and use HTTPS for all communications. Use a Network Security Configuration file for exceptions."
|
|
33
|
+
|
|
34
|
+
- id: AND-CONF-004
|
|
35
|
+
name: "Component is Exported without Permission"
|
|
36
|
+
pattern: |
|
|
37
|
+
(?s)<(?:activity|service|receiver|provider)\b(?=(?:(?!<)[\s\S])*?android:exported\s*=\s*"true")(?:(?!(?:(?!<)[\s\S])*?android:permission\s*=)|(?=(?:(?!<)[\s\S])*?android:permission\s*=\s*"android\.permission\.(?:FOREGROUND_SERVICE|INTERNET|ACCESS_NETWORK_STATE|ACCESS_WIFI_STATE|BLUETOOTH|BLUETOOTH_ADMIN|CHANGE_NETWORK_STATE|CHANGE_WIFI_STATE|VIBRATE|WAKE_LOCK|GET_ACCOUNTS|NFC|RECEIVE_BOOT_COMPLETED|SET_ALARM|SET_WALLPAPER|SET_WALLPAPER_HINTS|TRANSMIT_IR|BROADCAST_STICKY|KILL_BACKGROUND_PROCESSES|MODIFY_AUDIO_SETTINGS|READ_SYNC_SETTINGS|READ_SYNC_STATS|WRITE_SYNC_SETTINGS|REORDER_TASKS|CHANGE_WIFI_MULTICAST_STATE|DISABLE_KEYGUARD|EXPAND_STATUS_BAR|FLASHLIGHT|PERSISTENT_ACTIVITY)"))[\s\S]*?(?:/>|>)
|
|
38
|
+
severity: "HIGH"
|
|
39
|
+
confidence: "MEDIUM"
|
|
40
|
+
details:
|
|
41
|
+
cwe: "CWE-926"
|
|
42
|
+
masvs: "MSTG-PLATFORM-2"
|
|
43
|
+
description: "An application component (Activity, Service, Receiver) is exported without a real access-control permission (either none is set, or the one set is an OS-defined 'normal' permission auto-granted to any app, not an actual restriction). This can allow malicious apps to access or abuse the component's functionality."
|
|
44
|
+
recommendation: "Set android:exported to 'false' if the component is not meant to be used by other apps. If it must be exported, protect it with a 'signature' level permission, and validate all input from the Intent regardless."
|
|
45
|
+
|
|
46
|
+
- id: AND-CONF-005
|
|
47
|
+
name: "World-Readable File Mode"
|
|
48
|
+
pattern: 'MODE_WORLD_READABLE'
|
|
49
|
+
severity: "HIGH"
|
|
50
|
+
confidence: "HIGH"
|
|
51
|
+
details:
|
|
52
|
+
cwe: "CWE-732"
|
|
53
|
+
masvs: "MSTG-STORAGE-2"
|
|
54
|
+
description: "A file is opened with MODE_WORLD_READABLE, making it readable by every other application on the device. This constant has been deprecated by Google specifically for being a security hazard."
|
|
55
|
+
recommendation: "Use MODE_PRIVATE and, if inter-process sharing is genuinely required, use a ContentProvider with explicit permission grants instead."
|
|
56
|
+
|
|
57
|
+
- id: AND-CONF-006
|
|
58
|
+
name: "World-Writable File Mode"
|
|
59
|
+
pattern: 'MODE_WORLD_WRITEABLE'
|
|
60
|
+
severity: "CRITICAL"
|
|
61
|
+
confidence: "HIGH"
|
|
62
|
+
details:
|
|
63
|
+
cwe: "CWE-732"
|
|
64
|
+
masvs: "MSTG-STORAGE-2"
|
|
65
|
+
description: "A file is opened with MODE_WORLD_WRITEABLE, allowing any other application on the device to modify its contents. This can enable data tampering or, in some cases, code injection."
|
|
66
|
+
recommendation: "Use MODE_PRIVATE. If another app must write to this data, use a ContentProvider with explicit, signature-level permission grants."
|
|
67
|
+
|
|
68
|
+
- id: AND-CONF-009
|
|
69
|
+
name: "PendingIntent Without Explicit Immutability Flag"
|
|
70
|
+
pattern: 'PendingIntent\.(getActivity|getBroadcast|getService)\s*\('
|
|
71
|
+
severity: "MEDIUM"
|
|
72
|
+
confidence: "LOW"
|
|
73
|
+
details:
|
|
74
|
+
cwe: "CWE-927"
|
|
75
|
+
masvs: "MSTG-PLATFORM-2"
|
|
76
|
+
description: "A PendingIntent is created. If it does not explicitly set FLAG_IMMUTABLE (or a deliberately-scoped FLAG_MUTABLE), a malicious app that obtains this PendingIntent may be able to fill in or modify the underlying Intent and have it executed with this app's identity."
|
|
77
|
+
recommendation: "Verify manually whether FLAG_IMMUTABLE is set at this call site (required on Android 12+ unless mutability is explicitly needed). This is a low-confidence heuristic - every PendingIntent creation is flagged for manual review, not every instance is a real bug."
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Android cryptography checks.
|
|
2
|
+
|
|
3
|
+
- id: AND-CRYPTO-007
|
|
4
|
+
name: "Deprecated SSL Protocol Accepted"
|
|
5
|
+
pattern: 'SSLContext\.getInstance\s*\(\s*"SSL"'
|
|
6
|
+
severity: "HIGH"
|
|
7
|
+
confidence: "HIGH"
|
|
8
|
+
details:
|
|
9
|
+
cwe: "CWE-327"
|
|
10
|
+
masvs: "MSTG-NETWORK-2"
|
|
11
|
+
description: "Requesting the generic \"SSL\" protocol (as opposed to a specific TLS version) can silently negotiate down to SSLv3 or earlier on some providers, both of which are broken (POODLE)."
|
|
12
|
+
recommendation: "Explicitly request \"TLSv1.2\" or \"TLSv1.3\"."
|
|
13
|
+
|
|
14
|
+
- id: AND-CRYPTO-009
|
|
15
|
+
name: "Hardcoded Cryptographic IV"
|
|
16
|
+
pattern: 'new\s+IvParameterSpec\s*\(\s*"[^"]+"\.getBytes'
|
|
17
|
+
severity: "HIGH"
|
|
18
|
+
confidence: "HIGH"
|
|
19
|
+
details:
|
|
20
|
+
cwe: "CWE-329"
|
|
21
|
+
masvs: "MSTG-CRYPTO-3"
|
|
22
|
+
description: "A hardcoded, static initialization vector (IV) is used for encryption. Reusing a static IV defeats the security properties of most cipher modes (e.g., enables pattern analysis in CBC, catastrophic key/plaintext recovery in GCM/CTR on reuse)."
|
|
23
|
+
recommendation: "Generate a fresh, random IV for every encryption operation using SecureRandom, and store/transmit it alongside the ciphertext (it does not need to be secret)."
|
|
24
|
+
|
|
25
|
+
- id: AND-CRYPTO-010
|
|
26
|
+
name: "Hardcoded Symmetric Encryption Key"
|
|
27
|
+
pattern: 'new\s+SecretKeySpec\s*\(\s*"[^"]+"\.getBytes'
|
|
28
|
+
severity: "CRITICAL"
|
|
29
|
+
confidence: "HIGH"
|
|
30
|
+
details:
|
|
31
|
+
cwe: "CWE-321"
|
|
32
|
+
masvs: "MSTG-CRYPTO-1"
|
|
33
|
+
description: "A symmetric encryption key is built from a hardcoded string literal. Anyone who decompiles the app recovers the exact key used to encrypt/decrypt all protected data."
|
|
34
|
+
recommendation: "Never embed encryption keys in source code. Derive keys via the Android Keystore system, or retrieve them from a secure backend at runtime."
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Android hardcoded-credential checks.
|
|
2
|
+
|
|
3
|
+
- id: AND-S-001
|
|
4
|
+
name: "Hardcoded API Key"
|
|
5
|
+
pattern: |
|
|
6
|
+
(?i)(api[_-]?key|api[_-]?secret)["\s]*[:=][\s"']*[A-Za-z0-9+/=_-]{16,}
|
|
7
|
+
severity: "CRITICAL"
|
|
8
|
+
confidence: "HIGH"
|
|
9
|
+
details:
|
|
10
|
+
cwe: "CWE-798"
|
|
11
|
+
masvs: "MSTG-STORAGE-1"
|
|
12
|
+
description: "A hardcoded API key or secret token was found. This allows attackers who can decompile the app to gain unauthorized access to services."
|
|
13
|
+
recommendation: "Do not hardcode credentials. Retrieve them from a secure backend at runtime, or store them encrypted using the Android Keystore system."
|
|
14
|
+
|
|
15
|
+
- id: AND-S-002
|
|
16
|
+
name: "Hardcoded Password"
|
|
17
|
+
pattern: |
|
|
18
|
+
(?i)(password|passwd|pwd)\s*[:=]\s*"(?!(?:[^"\n]*(?:password|passwd|pwd)[^"\n]*)")[^"\n]{6,}"\s*;
|
|
19
|
+
severity: "CRITICAL"
|
|
20
|
+
confidence: "MEDIUM"
|
|
21
|
+
details:
|
|
22
|
+
cwe: "CWE-259"
|
|
23
|
+
masvs: "MSTG-STORAGE-1"
|
|
24
|
+
description: "A hardcoded password may have been found in the source code. Storing credentials directly in the code is highly insecure."
|
|
25
|
+
recommendation: "Remove credentials from the code. Store them securely using the Android Keystore system."
|
|
26
|
+
|
|
27
|
+
- id: AND-S-003
|
|
28
|
+
name: "Hardcoded Private Key"
|
|
29
|
+
# Require base64 key material after the header, so a bare "-----BEGIN PRIVATE KEY-----" marker constant is not flagged.
|
|
30
|
+
pattern: '-----BEGIN( RSA| EC| OPENSSH| DSA)? PRIVATE KEY-----[\r\n\s]+[A-Za-z0-9+/]{40,}'
|
|
31
|
+
severity: "CRITICAL"
|
|
32
|
+
confidence: "HIGH"
|
|
33
|
+
details:
|
|
34
|
+
cwe: "CWE-321"
|
|
35
|
+
masvs: "MSTG-CRYPTO-1"
|
|
36
|
+
description: "A private key block (e.g., RSA) was found embedded in the code. This completely compromises the security provided by the key."
|
|
37
|
+
recommendation: "Private keys should never be stored in the application package. Use the Android Keystore for cryptographic operations or retrieve keys from a secure server at runtime."
|
|
38
|
+
|
|
39
|
+
- id: AND-S-004
|
|
40
|
+
name: "Hardcoded AWS Access Key ID"
|
|
41
|
+
pattern: 'AKIA[0-9A-Z]{16}'
|
|
42
|
+
severity: "CRITICAL"
|
|
43
|
+
confidence: "HIGH"
|
|
44
|
+
details:
|
|
45
|
+
cwe: "CWE-798"
|
|
46
|
+
masvs: "MSTG-STORAGE-1"
|
|
47
|
+
description: "An AWS Access Key ID was found hardcoded in the app. If paired with its secret key (also often nearby), this grants direct access to whatever AWS resources the key's IAM policy allows."
|
|
48
|
+
recommendation: "Revoke this key in the AWS IAM console immediately, then remove it from the code. Mobile apps should never hold long-lived AWS credentials - use temporary credentials via Cognito Identity Pools or a backend-issued token instead."
|
|
49
|
+
|
|
50
|
+
- id: AND-S-005
|
|
51
|
+
name: "Hardcoded Google API Key"
|
|
52
|
+
pattern: 'AIza[0-9A-Za-z\-_]{35}'
|
|
53
|
+
severity: "HIGH"
|
|
54
|
+
confidence: "HIGH"
|
|
55
|
+
details:
|
|
56
|
+
cwe: "CWE-798"
|
|
57
|
+
masvs: "MSTG-STORAGE-1"
|
|
58
|
+
description: "A Google API key was found hardcoded in the app. Depending on the API restrictions configured on this key (or lack thereof), this can allow abuse of billed Google Cloud/Maps/Firebase services under the app owner's account."
|
|
59
|
+
recommendation: "Restrict the key to specific APIs and to the app's own signing certificate fingerprint / package name in the Google Cloud Console. Prefer server-side calls for any API that does not require a client-side key."
|
|
60
|
+
|
|
61
|
+
- id: AND-S-006
|
|
62
|
+
name: "Hardcoded Slack Token"
|
|
63
|
+
pattern: 'xox[baprs]-[0-9A-Za-z-]{10,48}'
|
|
64
|
+
severity: "CRITICAL"
|
|
65
|
+
confidence: "HIGH"
|
|
66
|
+
details:
|
|
67
|
+
cwe: "CWE-798"
|
|
68
|
+
masvs: "MSTG-STORAGE-1"
|
|
69
|
+
description: "A Slack API/bot/user token was found hardcoded in the app. This grants whatever access the token's scopes allow within the associated Slack workspace."
|
|
70
|
+
recommendation: "Revoke the token in Slack's app management console and remove it from the app. Slack tokens must never ship inside a client application."
|
|
71
|
+
|
|
72
|
+
- id: AND-S-007
|
|
73
|
+
name: "Hardcoded Stripe Live Secret Key"
|
|
74
|
+
pattern: 'sk_live_[0-9a-zA-Z]{24,}'
|
|
75
|
+
severity: "CRITICAL"
|
|
76
|
+
confidence: "HIGH"
|
|
77
|
+
details:
|
|
78
|
+
cwe: "CWE-798"
|
|
79
|
+
masvs: "MSTG-STORAGE-1"
|
|
80
|
+
description: "A Stripe LIVE secret key was found hardcoded in the app. This key can create charges, issue refunds, and read all payment data on the connected Stripe account - this is a direct financial exposure, not just a data leak."
|
|
81
|
+
recommendation: "Roll this key immediately in the Stripe dashboard. Secret keys must never be shipped client-side; only publishable keys (pk_live_/pk_test_) belong in a mobile app, and all charge/refund logic must run server-side."
|
|
82
|
+
|
|
83
|
+
- id: AND-S-008
|
|
84
|
+
name: "Hardcoded JWT"
|
|
85
|
+
pattern: 'eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}'
|
|
86
|
+
severity: "MEDIUM"
|
|
87
|
+
confidence: "MEDIUM"
|
|
88
|
+
details:
|
|
89
|
+
cwe: "CWE-798"
|
|
90
|
+
masvs: "MSTG-STORAGE-1"
|
|
91
|
+
description: "A hardcoded JSON Web Token was found. If this is a real, live, long-lived token (as opposed to a test fixture), it grants whatever access its claims allow to anyone who decompiles the app."
|
|
92
|
+
recommendation: "Confirm whether this is test/fixture data. If it is a real token, revoke it and issue tokens dynamically per-session from the backend instead of embedding one statically."
|
|
93
|
+
|
|
94
|
+
- id: AND-S-009
|
|
95
|
+
name: "Credentials Embedded in URL"
|
|
96
|
+
pattern: 'https?://[^/\s:]+:[^/\s@"]+@'
|
|
97
|
+
severity: "HIGH"
|
|
98
|
+
confidence: "MEDIUM"
|
|
99
|
+
details:
|
|
100
|
+
cwe: "CWE-798"
|
|
101
|
+
masvs: "MSTG-STORAGE-1"
|
|
102
|
+
description: "A URL containing embedded username:password credentials (Basic Auth in the URL itself) was found. These credentials are visible in source, logs, and often browser/proxy history."
|
|
103
|
+
recommendation: "Remove credentials from the URL. Use an Authorization header set at request time from a securely stored credential, or better, an OAuth token flow."
|
|
104
|
+
|
|
105
|
+
- id: AND-S-010
|
|
106
|
+
name: "Hardcoded Encryption/Secret Key Variable"
|
|
107
|
+
pattern: |
|
|
108
|
+
(?i)(?<!#)\w*(key|secret)\w*\s*=\s*"(?!(?:SELECT|INSERT|UPDATE|DELETE)\s)(?!https?://)(?!@\w+/)(?!\w+\[[^\]]*\]")(?!(?:private|public)\s+key"\s*;)(?![a-z0-9_.:\-]+")(?![^"\n]*\\)[^"\n]{8,}"\s*;
|
|
109
|
+
severity: "CRITICAL"
|
|
110
|
+
confidence: "MEDIUM"
|
|
111
|
+
details:
|
|
112
|
+
cwe: "CWE-321"
|
|
113
|
+
masvs: "MSTG-CRYPTO-1"
|
|
114
|
+
description: "A variable named like an encryption or secret key is assigned a hardcoded literal value. If this is genuinely used as a cryptographic key, it is fully recoverable by decompilation."
|
|
115
|
+
recommendation: "Never hardcode cryptographic key material. Derive it via the Android Keystore, or retrieve it from a secure backend at runtime."
|
|
116
|
+
|
|
117
|
+
- id: AND-S-011
|
|
118
|
+
name: "Hardcoded Bearer Token"
|
|
119
|
+
pattern: |
|
|
120
|
+
(?i)bearer\s+[A-Za-z0-9\-._~+/]{20,}=*
|
|
121
|
+
severity: "HIGH"
|
|
122
|
+
confidence: "MEDIUM"
|
|
123
|
+
details:
|
|
124
|
+
cwe: "CWE-798"
|
|
125
|
+
masvs: "MSTG-STORAGE-1"
|
|
126
|
+
description: "A hardcoded Bearer authorization token was found. If live, it grants API access to whatever it authorizes, to anyone who decompiles the app."
|
|
127
|
+
recommendation: "Do not hardcode bearer tokens. Obtain them dynamically via an authenticated login flow and store them using EncryptedSharedPreferences or the Android Keystore."
|
|
128
|
+
|
|
129
|
+
- id: AND-S-012
|
|
130
|
+
name: "Database Connection String with Embedded Credentials"
|
|
131
|
+
pattern: |
|
|
132
|
+
(?i)jdbc:[a-z]+://[^:/\s]+:[^@/\s]+@
|
|
133
|
+
severity: "CRITICAL"
|
|
134
|
+
confidence: "HIGH"
|
|
135
|
+
details:
|
|
136
|
+
cwe: "CWE-798"
|
|
137
|
+
masvs: "MSTG-STORAGE-1"
|
|
138
|
+
description: "A JDBC connection string with an embedded username:password was found. A mobile app should not be connecting directly to a database at all - this pattern typically indicates a database exposed to the internet with credentials shipping in every install."
|
|
139
|
+
recommendation: "Remove direct database access from the mobile app entirely. Route all data access through an authenticated backend API."
|
|
140
|
+
|
|
141
|
+
- id: AND-S-013
|
|
142
|
+
name: "Hardcoded Twilio API Key"
|
|
143
|
+
pattern: 'SK[0-9a-fA-F]{32}'
|
|
144
|
+
severity: "HIGH"
|
|
145
|
+
confidence: "MEDIUM"
|
|
146
|
+
details:
|
|
147
|
+
cwe: "CWE-798"
|
|
148
|
+
masvs: "MSTG-STORAGE-1"
|
|
149
|
+
description: "A Twilio API Key SID was found hardcoded in the app. Paired with its secret, this allows sending SMS/calls and reading account data billed to the app owner."
|
|
150
|
+
recommendation: "Revoke the key in the Twilio console and move SMS/call operations to a backend service the app authenticates against."
|
|
151
|
+
|
|
152
|
+
- id: AND-S-014
|
|
153
|
+
name: "Hardcoded GitHub Personal Access Token"
|
|
154
|
+
pattern: 'ghp_[A-Za-z0-9]{36}'
|
|
155
|
+
severity: "CRITICAL"
|
|
156
|
+
confidence: "HIGH"
|
|
157
|
+
details:
|
|
158
|
+
cwe: "CWE-798"
|
|
159
|
+
masvs: "MSTG-STORAGE-1"
|
|
160
|
+
description: "A GitHub Personal Access Token was found hardcoded in the app. This is almost always leftover developer/CI credentials rather than something the app legitimately needs at runtime, and grants access to whatever repos/scopes the token holder has."
|
|
161
|
+
recommendation: "Revoke the token in GitHub Settings > Developer settings immediately and remove it from the source. Rotate any secrets the token could have exposed."
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Android local-storage checks.
|
|
2
|
+
|
|
3
|
+
- id: AND-STORAGE-002
|
|
4
|
+
name: "Write to Unscoped External Storage"
|
|
5
|
+
pattern: 'Environment\.getExternalStorageDirectory\s*\(\s*\)'
|
|
6
|
+
severity: "MEDIUM"
|
|
7
|
+
confidence: "LOW"
|
|
8
|
+
details:
|
|
9
|
+
cwe: "CWE-922"
|
|
10
|
+
masvs: "MSTG-STORAGE-2"
|
|
11
|
+
description: "The app writes to the shared external storage root. Files placed here are world-readable pre-Android 10 (and reachable via scoped storage exemptions afterward), and survive app uninstall. This is a low-confidence heuristic - flags every use of the API, not just sensitive-data writes."
|
|
12
|
+
recommendation: "Verify manually whether sensitive data (tokens, PII, credentials) is ever written here. If so, use app-internal storage (getFilesDir()) or scoped storage, encrypted at rest if it must be shared."
|
|
13
|
+
|
|
14
|
+
- id: AND-STORAGE-003
|
|
15
|
+
name: "Sensitive Data Possibly Copied to Clipboard"
|
|
16
|
+
pattern: |
|
|
17
|
+
(?i)ClipData\.newPlainText\s*\(\s*"[^"]*(password|token|secret|api_key)
|
|
18
|
+
severity: "MEDIUM"
|
|
19
|
+
confidence: "MEDIUM"
|
|
20
|
+
details:
|
|
21
|
+
cwe: "CWE-200"
|
|
22
|
+
masvs: "MSTG-PLATFORM-4"
|
|
23
|
+
description: "A value that looks like a password, token, or secret is copied to the system clipboard. Other apps (and, on some OEM skins, cloud clipboard sync) can read clipboard contents, making this a plaintext exfiltration path."
|
|
24
|
+
recommendation: "Avoid copying secrets to the clipboard. If unavoidable, mark the ClipDescription as sensitive (EXTRA_IS_SENSITIVE, API 33+) and clear the clipboard shortly after."
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Android WebView configuration checks.
|
|
2
|
+
|
|
3
|
+
- id: AND-WEBVIEW-002
|
|
4
|
+
name: "File Access Enabled in WebView"
|
|
5
|
+
pattern: 'setAllowFileAccess\s*\(\s*true\s*\)'
|
|
6
|
+
severity: "HIGH"
|
|
7
|
+
confidence: "HIGH"
|
|
8
|
+
details:
|
|
9
|
+
cwe: "CWE-73"
|
|
10
|
+
masvs: "MSTG-PLATFORM-5"
|
|
11
|
+
description: "Allowing file access from a WebView can lead to local file theft if a malicious web page is loaded."
|
|
12
|
+
recommendation: "Disable file access by default using `setAllowFileAccess(false)` unless explicitly required."
|
|
13
|
+
|
|
14
|
+
- id: AND-WEBVIEW-005
|
|
15
|
+
name: "Content Access Enabled in WebView"
|
|
16
|
+
pattern: 'setAllowContentAccess\s*\(\s*true\s*\)'
|
|
17
|
+
severity: "MEDIUM"
|
|
18
|
+
confidence: "MEDIUM"
|
|
19
|
+
details:
|
|
20
|
+
cwe: "CWE-668"
|
|
21
|
+
masvs: "MSTG-PLATFORM-5"
|
|
22
|
+
description: "content:// URL access is enabled in the WebView. If the WebView can be made to load an attacker-influenced content:// URL, this can expose data from other apps' ContentProviders."
|
|
23
|
+
recommendation: "Disable with setAllowContentAccess(false) unless the app specifically needs to load content:// URLs from trusted sources."
|
narvy/rules/android.yml
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Android source rules, authored by Narvy.
|
|
2
|
+
# WebView, crypto, storage, logging, SQL injection, secrets.
|
|
3
|
+
|
|
4
|
+
rules:
|
|
5
|
+
- id: android-ecb-mode
|
|
6
|
+
languages: [java, kotlin]
|
|
7
|
+
severity: ERROR
|
|
8
|
+
message: >-
|
|
9
|
+
ECB mode is used for encryption. ECB is insecure because identical
|
|
10
|
+
plaintext blocks encrypt to identical ciphertext blocks, revealing
|
|
11
|
+
patterns. Use GCM or CBC with proper padding instead.
|
|
12
|
+
metadata:
|
|
13
|
+
cwe: CWE-327
|
|
14
|
+
masvs: MSTG-CRYPTO-4
|
|
15
|
+
category: crypto
|
|
16
|
+
patterns:
|
|
17
|
+
- pattern: $CIPHER.getInstance($ALGO)
|
|
18
|
+
- metavariable-regex:
|
|
19
|
+
metavariable: $ALGO
|
|
20
|
+
regex: '.*ECB.*'
|
|
21
|
+
|
|
22
|
+
- id: android-weak-cipher
|
|
23
|
+
languages: [java, kotlin]
|
|
24
|
+
severity: ERROR
|
|
25
|
+
message: "DES/RC4 are broken ciphers. Use AES-256-GCM instead."
|
|
26
|
+
metadata:
|
|
27
|
+
cwe: CWE-327
|
|
28
|
+
masvs: MSTG-CRYPTO-4
|
|
29
|
+
category: crypto
|
|
30
|
+
patterns:
|
|
31
|
+
- pattern: $CIPHER.getInstance($ALGO)
|
|
32
|
+
- metavariable-regex:
|
|
33
|
+
metavariable: $ALGO
|
|
34
|
+
regex: '^"(DES|DESede|RC4).*'
|
|
35
|
+
|
|
36
|
+
- id: android-weak-hash
|
|
37
|
+
languages: [java, kotlin]
|
|
38
|
+
severity: WARNING
|
|
39
|
+
message: "MD5/SHA-1 are broken for security-sensitive hashing. Use SHA-256+."
|
|
40
|
+
metadata:
|
|
41
|
+
cwe: CWE-328
|
|
42
|
+
masvs: MSTG-CRYPTO-4
|
|
43
|
+
category: crypto
|
|
44
|
+
patterns:
|
|
45
|
+
- pattern: java.security.MessageDigest.getInstance($ALGO)
|
|
46
|
+
- metavariable-regex:
|
|
47
|
+
metavariable: $ALGO
|
|
48
|
+
regex: '^"(MD5|SHA-?1)"$'
|
|
49
|
+
|
|
50
|
+
- id: android-weak-prng
|
|
51
|
+
languages: [java, kotlin]
|
|
52
|
+
severity: WARNING
|
|
53
|
+
message: "java.util.Random / Math.random() is not cryptographically secure. Use SecureRandom for security-sensitive values (tokens, keys, nonces, salts, session ids)."
|
|
54
|
+
metadata:
|
|
55
|
+
cwe: CWE-338
|
|
56
|
+
masvs: MSTG-CRYPTO-6
|
|
57
|
+
category: crypto
|
|
58
|
+
patterns:
|
|
59
|
+
- pattern-either:
|
|
60
|
+
- pattern: new java.util.Random(...)
|
|
61
|
+
- pattern: new Random(...)
|
|
62
|
+
- pattern: Math.random()
|
|
63
|
+
|
|
64
|
+
- id: android-hardcoded-keystore-password
|
|
65
|
+
languages: [java, kotlin]
|
|
66
|
+
severity: ERROR
|
|
67
|
+
message: "Hardcoded KeyStore/keystore password - fully recoverable by decompilation."
|
|
68
|
+
metadata:
|
|
69
|
+
cwe: CWE-798
|
|
70
|
+
masvs: MSTG-STORAGE-1
|
|
71
|
+
category: secrets
|
|
72
|
+
patterns:
|
|
73
|
+
- pattern-either:
|
|
74
|
+
- pattern: $KS.load($IN, "$PASS".toCharArray())
|
|
75
|
+
- pattern: $KS.getKey($ALIAS, "$PASS".toCharArray())
|
|
76
|
+
|
|
77
|
+
- id: android-webview-js-enabled
|
|
78
|
+
languages: [java, kotlin]
|
|
79
|
+
severity: WARNING
|
|
80
|
+
message: "JavaScript enabled in WebView - can enable XSS if untrusted content is loaded."
|
|
81
|
+
metadata:
|
|
82
|
+
cwe: CWE-749
|
|
83
|
+
masvs: MSTG-PLATFORM-5
|
|
84
|
+
category: webview
|
|
85
|
+
patterns:
|
|
86
|
+
- pattern: $SETTINGS.setJavaScriptEnabled(true)
|
|
87
|
+
|
|
88
|
+
- id: android-webview-universal-file-access
|
|
89
|
+
languages: [java, kotlin]
|
|
90
|
+
severity: ERROR
|
|
91
|
+
message: "setAllowUniversalAccessFromFileURLs(true) allows JS in a file:// context to reach any origin - classic path to universal XSS / local file exfiltration."
|
|
92
|
+
metadata:
|
|
93
|
+
cwe: CWE-940
|
|
94
|
+
masvs: MSTG-PLATFORM-5
|
|
95
|
+
category: webview
|
|
96
|
+
patterns:
|
|
97
|
+
- pattern: $SETTINGS.setAllowUniversalAccessFromFileURLs(true)
|
|
98
|
+
|
|
99
|
+
- id: android-webview-addjavascriptinterface
|
|
100
|
+
languages: [java, kotlin]
|
|
101
|
+
severity: WARNING
|
|
102
|
+
message: "addJavascriptInterface() exposes a native object to JS in the WebView. Ensure the WebView only loads trusted content and methods are @JavascriptInterface-annotated."
|
|
103
|
+
metadata:
|
|
104
|
+
cwe: CWE-749
|
|
105
|
+
masvs: MSTG-PLATFORM-7
|
|
106
|
+
category: webview
|
|
107
|
+
patterns:
|
|
108
|
+
- pattern: $WV.addJavascriptInterface($OBJ, $NAME)
|
|
109
|
+
|
|
110
|
+
# SQLi via string-concat into a SQL sink, split by sink (rawQuery -> HIGH, execSQL -> MEDIUM); pure-literal concats suppressed.
|
|
111
|
+
- id: android-sql-injection-rawquery
|
|
112
|
+
languages: [java, kotlin]
|
|
113
|
+
severity: WARNING
|
|
114
|
+
message: "SQL built via string concatenation reaches rawQuery(). If any operand is user-controlled this is SQL injection; use parameterized queries (? placeholders + selectionArgs)."
|
|
115
|
+
metadata:
|
|
116
|
+
cwe: CWE-89
|
|
117
|
+
masvs: MSTG-PLATFORM-2
|
|
118
|
+
category: injection
|
|
119
|
+
confidence: LOW
|
|
120
|
+
patterns:
|
|
121
|
+
- pattern: $DB.rawQuery($A + $B, ...)
|
|
122
|
+
- pattern-not:
|
|
123
|
+
patterns:
|
|
124
|
+
- pattern: $DB.rawQuery($A + $B, ...)
|
|
125
|
+
- metavariable-pattern:
|
|
126
|
+
metavariable: $A
|
|
127
|
+
patterns:
|
|
128
|
+
- pattern-regex: (?s)^(?:\s|\+)*(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*)(?:(?:\s|\+)+(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*))*(?:\s|\+)*$
|
|
129
|
+
- metavariable-pattern:
|
|
130
|
+
metavariable: $B
|
|
131
|
+
patterns:
|
|
132
|
+
- pattern-regex: (?s)^(?:\s|\+)*(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*)(?:(?:\s|\+)+(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*))*(?:\s|\+)*$
|
|
133
|
+
|
|
134
|
+
- id: android-sql-injection-execsql
|
|
135
|
+
languages: [java, kotlin]
|
|
136
|
+
severity: INFO
|
|
137
|
+
message: "SQL built via string concatenation passed to execSQL(). Often DDL or a Room migration with app-internal values (lower risk); but if any operand is user-controlled it is injectable. Prefer parameterized statements."
|
|
138
|
+
metadata:
|
|
139
|
+
cwe: CWE-89
|
|
140
|
+
masvs: MSTG-PLATFORM-2
|
|
141
|
+
category: injection
|
|
142
|
+
confidence: LOW
|
|
143
|
+
patterns:
|
|
144
|
+
- pattern: $DB.execSQL($A + $B)
|
|
145
|
+
- pattern-not:
|
|
146
|
+
patterns:
|
|
147
|
+
- pattern: $DB.execSQL($A + $B)
|
|
148
|
+
- metavariable-pattern:
|
|
149
|
+
metavariable: $A
|
|
150
|
+
patterns:
|
|
151
|
+
- pattern-regex: (?s)^(?:\s|\+)*(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*)(?:(?:\s|\+)+(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*))*(?:\s|\+)*$
|
|
152
|
+
- metavariable-pattern:
|
|
153
|
+
metavariable: $B
|
|
154
|
+
patterns:
|
|
155
|
+
- pattern-regex: (?s)^(?:\s|\+)*(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*)(?:(?:\s|\+)+(?:"[^"]*"|[A-Za-z_][A-Za-z0-9_.]*\.[A-Z][A-Z0-9_]*|[A-Z][A-Z0-9_]*))*(?:\s|\+)*$
|
|
156
|
+
|
|
157
|
+
- id: android-trust-all-certs
|
|
158
|
+
languages: [java, kotlin]
|
|
159
|
+
severity: ERROR
|
|
160
|
+
message: "Custom TrustManager - verify checkServerTrusted() actually validates the chain. An empty/no-op body disables TLS certificate validation entirely (trivial MITM)."
|
|
161
|
+
metadata:
|
|
162
|
+
cwe: CWE-295
|
|
163
|
+
masvs: MSTG-NETWORK-3
|
|
164
|
+
category: network
|
|
165
|
+
patterns:
|
|
166
|
+
- pattern: |
|
|
167
|
+
new X509TrustManager() {
|
|
168
|
+
...
|
|
169
|
+
public void checkServerTrusted(...) { }
|
|
170
|
+
...
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
- id: android-trust-all-hostname-verifier
|
|
174
|
+
languages: [java, kotlin]
|
|
175
|
+
severity: ERROR
|
|
176
|
+
message: "HostnameVerifier that always returns true disables hostname validation - MITM-exploitable even with a valid cert."
|
|
177
|
+
metadata:
|
|
178
|
+
cwe: CWE-297
|
|
179
|
+
masvs: MSTG-NETWORK-3
|
|
180
|
+
category: network
|
|
181
|
+
patterns:
|
|
182
|
+
- pattern: |
|
|
183
|
+
new HostnameVerifier() {
|
|
184
|
+
...
|
|
185
|
+
public boolean verify(...) { return true; }
|
|
186
|
+
...
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
- id: android-cleartext-mixed-content
|
|
190
|
+
languages: [java, kotlin]
|
|
191
|
+
severity: WARNING
|
|
192
|
+
message: "MIXED_CONTENT_ALWAYS_ALLOW permits an HTTPS page to load HTTP sub-resources - lets a network attacker inject content despite the top-level page being HTTPS."
|
|
193
|
+
metadata:
|
|
194
|
+
cwe: CWE-296
|
|
195
|
+
masvs: MSTG-NETWORK-2
|
|
196
|
+
category: network
|
|
197
|
+
patterns:
|
|
198
|
+
- pattern: $SETTINGS.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW)
|
|
199
|
+
|
|
200
|
+
- id: android-log-sensitive-data
|
|
201
|
+
languages: [java, kotlin]
|
|
202
|
+
severity: WARNING
|
|
203
|
+
message: "Logging a value that looks like a credential/token/secret - readable via adb/crash reporters on many devices."
|
|
204
|
+
metadata:
|
|
205
|
+
cwe: CWE-532
|
|
206
|
+
masvs: MSTG-STORAGE-3
|
|
207
|
+
category: logging
|
|
208
|
+
patterns:
|
|
209
|
+
- pattern-either:
|
|
210
|
+
- pattern: android.util.Log.$LEVEL($TAG, $MSG)
|
|
211
|
+
- pattern: Log.$LEVEL($TAG, $MSG)
|
|
212
|
+
- metavariable-pattern:
|
|
213
|
+
metavariable: $MSG
|
|
214
|
+
patterns:
|
|
215
|
+
- pattern-regex: '(?i)(password|token|secret|api[_-]?key)'
|
|
216
|
+
- metavariable-pattern:
|
|
217
|
+
metavariable: $MSG
|
|
218
|
+
patterns:
|
|
219
|
+
- pattern-not-regex: '^"[^"]*"$'
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Objective-C cryptography checks.
|
|
2
|
+
|
|
3
|
+
- id: IOS-OBJC-CRYPTO-001
|
|
4
|
+
name: "Weak Cipher Algorithm (DES/3DES/RC4)"
|
|
5
|
+
pattern: 'kCCAlgorithm(DES|3DES|RC4)\b'
|
|
6
|
+
severity: "CRITICAL"
|
|
7
|
+
confidence: "HIGH"
|
|
8
|
+
details:
|
|
9
|
+
cwe: "CWE-327"
|
|
10
|
+
masvs: "MSTG-CRYPTO-4"
|
|
11
|
+
description: "DES, 3DES, and RC4 are broken/deprecated ciphers. DES has a trivially brute-forceable 56-bit key, 3DES is deprecated by NIST, RC4 has known keystream biases."
|
|
12
|
+
recommendation: "Use kCCAlgorithmAES with a 256-bit key and GCM (or CBC with a random IV + separate MAC) instead."
|
|
13
|
+
|
|
14
|
+
- id: IOS-OBJC-CRYPTO-002
|
|
15
|
+
name: "ECB Mode Encryption"
|
|
16
|
+
pattern: 'kCCOptionECBMode\b'
|
|
17
|
+
severity: "CRITICAL"
|
|
18
|
+
confidence: "HIGH"
|
|
19
|
+
details:
|
|
20
|
+
cwe: "CWE-327"
|
|
21
|
+
masvs: "MSTG-CRYPTO-4"
|
|
22
|
+
description: "ECB mode encrypts identical plaintext blocks to identical ciphertext blocks, leaking structural patterns in the plaintext (the classic 'ECB penguin' problem)."
|
|
23
|
+
recommendation: "Use GCM or CBC mode with a fresh, random IV per encryption operation instead of ECB."
|
|
24
|
+
|
|
25
|
+
- id: IOS-OBJC-CRYPTO-003
|
|
26
|
+
name: "Weak Hash Algorithm (MD5/SHA1)"
|
|
27
|
+
pattern: '\bCC_(MD5|SHA1)\s*\('
|
|
28
|
+
severity: "HIGH"
|
|
29
|
+
confidence: "HIGH"
|
|
30
|
+
details:
|
|
31
|
+
cwe: "CWE-328"
|
|
32
|
+
masvs: "MSTG-CRYPTO-4"
|
|
33
|
+
description: "MD5 and SHA-1 are cryptographically broken for security-sensitive use (collision attacks). Using them for password hashing, integrity checks, or signatures is insecure."
|
|
34
|
+
recommendation: "Use CC_SHA256 or CC_SHA512 for hashing. For password storage specifically, use a dedicated KDF (PBKDF2/bcrypt/scrypt/Argon2) via CommonKeyDerivation, never a bare hash."
|
|
35
|
+
|
|
36
|
+
- id: IOS-OBJC-CRYPTO-004
|
|
37
|
+
name: "Weak PRNG for Security-Sensitive Value"
|
|
38
|
+
pattern: '(?<!arc4)\brandom\s*\(\s*\)|\bsrand\s*\(|\brand\s*\(\s*\)'
|
|
39
|
+
severity: "HIGH"
|
|
40
|
+
confidence: "MEDIUM"
|
|
41
|
+
details:
|
|
42
|
+
cwe: "CWE-338"
|
|
43
|
+
masvs: "MSTG-CRYPTO-6"
|
|
44
|
+
description: "random()/rand()/srand() are not cryptographically secure pseudo-random number generators - their output is predictable, which is dangerous if used to generate tokens, keys, nonces, or IVs."
|
|
45
|
+
recommendation: "Use SecRandomCopyBytes(kSecRandomDefault, ...) for any security-sensitive random value."
|
|
46
|
+
|
|
47
|
+
- id: IOS-OBJC-CRYPTO-005
|
|
48
|
+
name: "Hardcoded Key Material Passed to CommonCrypto"
|
|
49
|
+
pattern: '(?:CCCrypt|CCCryptorCreate)\s*\([^;]*@"[^"]{6,}"[^;]*\)\s*;'
|
|
50
|
+
severity: "CRITICAL"
|
|
51
|
+
confidence: "MEDIUM"
|
|
52
|
+
details:
|
|
53
|
+
cwe: "CWE-321"
|
|
54
|
+
masvs: "MSTG-CRYPTO-1"
|
|
55
|
+
description: "A CommonCrypto call (CCCrypt/CCCryptorCreate) includes a string literal directly in its argument list - most commonly the hardcoded key or IV. Anyone who reads the source recovers the exact key/IV material."
|
|
56
|
+
recommendation: "Never embed key or IV material as a literal. Derive keys via the Keychain/Secure Enclave, or retrieve them from a secure backend at runtime; generate IVs randomly per-operation with SecRandomCopyBytes."
|