Skip to content

Commit f6ad9db

Browse files
committed
master
1 parent 0876053 commit f6ad9db

5 files changed

Lines changed: 407 additions & 16 deletions

File tree

internal/analyzer/analyzer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func TestAnalyzeManifest(t *testing.T) {
2424
t.Fatalf("AnalyzeManifest failed: %v", err)
2525
}
2626

27-
expectedFindings := 4 // 1 permission, 1 debuggable, 1 allowBackup, 1 exported activity
27+
expectedFindings := 6 // 1 permission, 1 debuggable, 1 allowBackup, 1 cleartext, 1 backup content, 1 exported activity without permission
2828
if len(findings) != expectedFindings {
2929
t.Errorf("Expected %d findings, got %d", expectedFindings, len(findings))
3030
}

internal/analyzer/manifest.go

Lines changed: 76 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,38 @@ type Application struct {
2323
Debuggable string `xml:"http://schemas.android.com/apk/res/android debuggable,attr"`
2424
AllowBackup string `xml:"http://schemas.android.com/apk/res/android allowBackup,attr"`
2525
NetworkSecurityConfig string `xml:"http://schemas.android.com/apk/res/android networkSecurityConfig,attr"`
26+
UsesCleartextTraffic string `xml:"http://schemas.android.com/apk/res/android usesCleartextTraffic,attr"`
27+
FullBackupContent string `xml:"http://schemas.android.com/apk/res/android fullBackupContent,attr"`
2628
Activities []Component `xml:"activity"`
2729
Services []Component `xml:"service"`
2830
Receivers []Component `xml:"receiver"`
31+
Providers []Component `xml:"provider"`
2932
}
3033

31-
// Component represents an activity, service, or receiver.
34+
// Component represents an activity, service, receiver, or provider.
3235
type Component struct {
33-
Name string `xml:"http://schemas.android.com/apk/res/android name,attr"`
34-
Exported string `xml:"http://schemas.android.com/apk/res/android exported,attr"`
36+
Name string `xml:"http://schemas.android.com/apk/res/android name,attr"`
37+
Exported string `xml:"http://schemas.android.com/apk/res/android exported,attr"`
38+
Permission string `xml:"http://schemas.android.com/apk/res/android permission,attr"`
39+
IntentFilters []IntentFilter `xml:"intent-filter"`
40+
}
41+
42+
// IntentFilter represents an intent-filter within a component.
43+
type IntentFilter struct {
44+
Actions []Action `xml:"action"`
45+
}
46+
47+
// Action represents an action within an intent-filter.
48+
type Action struct {
49+
Name string `xml:"http://schemas.android.com/apk/res/android name,attr"`
3550
}
3651

3752
// SecurityFinding represents a security issue found in the manifest or resources.
3853
type SecurityFinding struct {
3954
Type string
4055
Description string
4156
Severity string
57+
MASVS string
4258
}
4359

4460
var dangerousPermissions = map[string]bool{
@@ -73,59 +89,106 @@ func AnalyzeManifest(manifestPath string) ([]SecurityFinding, error) {
7389

7490
var findings []SecurityFinding
7591

76-
// 1. Dangerous Permissions
92+
// 1. Dangerous Permissions (MASVS-PRIVACY)
7793
for _, p := range manifest.Permissions {
7894
if dangerousPermissions[p.Name] {
7995
findings = append(findings, SecurityFinding{
8096
Type: "Permission",
8197
Description: "Dangerous permission requested: " + p.Name,
8298
Severity: "medium",
99+
MASVS: "MASVS-PRIVACY-1",
83100
})
84101
}
85102
}
86103

87-
// 2. Debuggable
104+
// 2. Debuggable (MASVS-RESILIENCE)
88105
if manifest.Application.Debuggable == "true" {
89106
findings = append(findings, SecurityFinding{
90107
Type: "Manifest",
91108
Description: "Application is debuggable",
92109
Severity: "high",
110+
MASVS: "MASVS-RESILIENCE-2",
93111
})
94112
}
95113

96-
// 3. AllowBackup
114+
// 3. AllowBackup (MASVS-STORAGE)
97115
if manifest.Application.AllowBackup == "true" || manifest.Application.AllowBackup == "" {
98116
findings = append(findings, SecurityFinding{
99117
Type: "Manifest",
100118
Description: "Application allows backup (allowBackup should be explicitly false)",
101119
Severity: "medium",
120+
MASVS: "MASVS-STORAGE-2",
102121
})
103122
}
104123

105-
// 4. Network Security Config
124+
// 4. Cleartext Traffic (MASVS-NETWORK)
125+
if manifest.Application.UsesCleartextTraffic == "true" || manifest.Application.UsesCleartextTraffic == "" {
126+
findings = append(findings, SecurityFinding{
127+
Type: "Network",
128+
Description: "Application allows cleartext traffic (HTTP). Set usesCleartextTraffic to false.",
129+
Severity: "high",
130+
MASVS: "MASVS-NETWORK-1",
131+
})
132+
}
133+
134+
// 5. Network Security Config
106135
if manifest.Application.NetworkSecurityConfig != "" {
107136
findings = append(findings, SecurityFinding{
108137
Type: "Manifest",
109138
Description: "Network Security Configuration is defined: " + manifest.Application.NetworkSecurityConfig,
110139
Severity: "info",
140+
MASVS: "MASVS-NETWORK-1",
141+
})
142+
}
143+
144+
// 6. Full Backup Content
145+
if manifest.Application.FullBackupContent == "" && manifest.Application.AllowBackup != "false" {
146+
findings = append(findings, SecurityFinding{
147+
Type: "Manifest",
148+
Description: "Full backup content not specified. Define android:fullBackupContent to control backup",
149+
Severity: "low",
150+
MASVS: "MASVS-STORAGE-2",
111151
})
112152
}
113153

114-
// 5. Exported Components
115-
checkExported(manifest.Application.Activities, "Activity", &findings)
116-
checkExported(manifest.Application.Services, "Service", &findings)
117-
checkExported(manifest.Application.Receivers, "Receiver", &findings)
154+
// 7. Exported Components (MASVS-PLATFORM)
155+
checkExportedWithIntentFilter(manifest.Application.Activities, "Activity", &findings)
156+
checkExportedWithIntentFilter(manifest.Application.Services, "Service", &findings)
157+
checkExportedWithIntentFilter(manifest.Application.Receivers, "Receiver", &findings)
158+
checkExportedWithIntentFilter(manifest.Application.Providers, "Provider", &findings)
118159

119160
return findings, nil
120161
}
121162

122-
func checkExported(components []Component, componentType string, findings *[]SecurityFinding) {
163+
func checkExportedWithIntentFilter(components []Component, componentType string, findings *[]SecurityFinding) {
123164
for _, c := range components {
165+
// If explicitly exported=true
124166
if c.Exported == "true" {
167+
// Check if it has permission protection
168+
if c.Permission == "" {
169+
*findings = append(*findings, SecurityFinding{
170+
Type: componentType,
171+
Description: fmt.Sprintf("Exported %s without permission: %s", componentType, c.Name),
172+
Severity: "high",
173+
MASVS: "MASVS-PLATFORM-1",
174+
})
175+
} else {
176+
*findings = append(*findings, SecurityFinding{
177+
Type: componentType,
178+
Description: fmt.Sprintf("Exported %s found (with permission): %s", componentType, c.Name),
179+
Severity: "info",
180+
MASVS: "MASVS-PLATFORM-1",
181+
})
182+
}
183+
}
184+
185+
// If component has intent-filter, it's implicitly exported (Android < 12)
186+
if len(c.IntentFilters) > 0 && c.Exported == "" {
125187
*findings = append(*findings, SecurityFinding{
126188
Type: componentType,
127-
Description: fmt.Sprintf("Exported %s found: %s", componentType, c.Name),
189+
Description: fmt.Sprintf("%s with intent-filter is implicitly exported: %s (set exported explicitly)", componentType, c.Name),
128190
Severity: "medium",
191+
MASVS: "MASVS-PLATFORM-1",
129192
})
130193
}
131194
}

internal/patterns/default_patterns.json

Lines changed: 170 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@
1818
"severity": "high",
1919
"confidence": "high"
2020
},
21+
{
22+
"name": "Amazon AWS Secret Key",
23+
"regex": "(?i)aws(.{0,20})?(?-i)['\\\"|=]([0-9a-zA-Z/+=]{40})['\\\"]",
24+
"severity": "high",
25+
"confidence": "medium"
26+
},
2127
{
2228
"name": "Amazon MWS Auth Token",
2329
"regex": "amzn\\.mws\\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
@@ -30,33 +36,195 @@
3036
"severity": "high",
3137
"confidence": "high"
3238
},
39+
{
40+
"name": "Facebook OAuth",
41+
"regex": "[f|F][a|A][c|C][e|E][b|B][o|O][o|O][k|K].*['|\\\"][0-9a-f]{32}['|\\\"]",
42+
"severity": "high",
43+
"confidence": "medium"
44+
},
3345
{
3446
"name": "GitHub Personal Access Token",
3547
"regex": "ghp_[a-zA-Z0-9]{36}",
3648
"severity": "high",
3749
"confidence": "high"
3850
},
51+
{
52+
"name": "GitHub OAuth Token",
53+
"regex": "gho_[a-zA-Z0-9]{36}",
54+
"severity": "high",
55+
"confidence": "high"
56+
},
57+
{
58+
"name": "GitHub App Token",
59+
"regex": "(ghu|ghs)_[a-zA-Z0-9]{36}",
60+
"severity": "high",
61+
"confidence": "high"
62+
},
3963
{
4064
"name": "PayPal Braintree Access Token",
4165
"regex": "access_token\\$production\\$[0-9a-z]{16}\\$[0-9a-f]{32}",
4266
"severity": "high",
4367
"confidence": "high"
4468
},
69+
{
70+
"name": "Stripe API Key",
71+
"regex": "sk_live_[0-9a-zA-Z]{24}",
72+
"severity": "high",
73+
"confidence": "high"
74+
},
75+
{
76+
"name": "Stripe Publishable Key",
77+
"regex": "pk_live_[0-9a-zA-Z]{24}",
78+
"severity": "medium",
79+
"confidence": "high"
80+
},
81+
{
82+
"name": "Stripe Restricted Key",
83+
"regex": "rk_live_[0-9a-zA-Z]{24}",
84+
"severity": "high",
85+
"confidence": "high"
86+
},
87+
{
88+
"name": "Square Access Token",
89+
"regex": "sq0atp-[0-9A-Za-z\\-_]{22}",
90+
"severity": "high",
91+
"confidence": "high"
92+
},
93+
{
94+
"name": "Square OAuth Secret",
95+
"regex": "sq0csp-[0-9A-Za-z\\-_]{43}",
96+
"severity": "high",
97+
"confidence": "high"
98+
},
99+
{
100+
"name": "Twilio API Key",
101+
"regex": "SK[0-9a-fA-F]{32}",
102+
"severity": "high",
103+
"confidence": "high"
104+
},
45105
{
46106
"name": "Mailgun API Key",
47107
"regex": "key-[0-9a-zA-Z]{32}",
48108
"severity": "high",
49109
"confidence": "high"
50110
},
111+
{
112+
"name": "SendGrid API Key",
113+
"regex": "SG\\.[0-9A-Za-z\\-_]{22}\\.[0-9A-Za-z\\-_]{43}",
114+
"severity": "high",
115+
"confidence": "high"
116+
},
51117
{
52118
"name": "Slack Token",
53-
"regex": "xox[bapz]-[0-9]{12}-[0-9]{12}-[0-9a-zA-Z]{24}",
119+
"regex": "xox[baprs]-[0-9]{12}-[0-9]{12}-[0-9a-zA-Z]{24}",
120+
"severity": "high",
121+
"confidence": "high"
122+
},
123+
{
124+
"name": "Slack Webhook",
125+
"regex": "https://hooks\\.slack\\.com/services/T[a-zA-Z0-9_]{8}/B[a-zA-Z0-9_]{8}/[a-zA-Z0-9_]{24}",
126+
"severity": "high",
127+
"confidence": "high"
128+
},
129+
{
130+
"name": "Azure Storage Account Key",
131+
"regex": "(?i)azure(.{0,20})?['\\\"|=]([a-zA-Z0-9/+=]{88})['\\\"]",
54132
"severity": "high",
133+
"confidence": "medium"
134+
},
135+
{
136+
"name": "Google Cloud Service Account",
137+
"regex": "\"type\": \"service_account\"",
138+
"severity": "high",
139+
"confidence": "high"
140+
},
141+
{
142+
"name": "Heroku API Key",
143+
"regex": "[h|H][e|E][r|R][o|O][k|K][u|U].*[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}",
144+
"severity": "high",
145+
"confidence": "medium"
146+
},
147+
{
148+
"name": "DigitalOcean OAuth Token",
149+
"regex": "dop_v1_[a-f0-9]{64}",
150+
"severity": "high",
151+
"confidence": "high"
152+
},
153+
{
154+
"name": "NPM Token",
155+
"regex": "npm_[a-zA-Z0-9]{36}",
156+
"severity": "high",
157+
"confidence": "high"
158+
},
159+
{
160+
"name": "PyPI API Token",
161+
"regex": "pypi-AgEIcHlwaS5vcmc[A-Za-z0-9-_]{70,}",
162+
"severity": "high",
163+
"confidence": "high"
164+
},
165+
{
166+
"name": "Shopify Access Token",
167+
"regex": "shpat_[a-fA-F0-9]{32}",
168+
"severity": "high",
169+
"confidence": "high"
170+
},
171+
{
172+
"name": "Telegram Bot Token",
173+
"regex": "[0-9]{8,10}:[a-zA-Z0-9_-]{35}",
174+
"severity": "medium",
175+
"confidence": "medium"
176+
},
177+
{
178+
"name": "Discord Bot Token",
179+
"regex": "[MN][a-zA-Z\\d]{23}\\.[\\w-]{6}\\.[\\w-]{27}",
180+
"severity": "high",
181+
"confidence": "high"
182+
},
183+
{
184+
"name": "JWT Token",
185+
"regex": "eyJ[A-Za-z0-9-_=]+\\.eyJ[A-Za-z0-9-_=]+\\.[A-Za-z0-9-_.+/=]*",
186+
"severity": "medium",
187+
"confidence": "medium"
188+
},
189+
{
190+
"name": "RSA Private Key",
191+
"regex": "-----BEGIN RSA PRIVATE KEY-----",
192+
"severity": "critical",
193+
"confidence": "high"
194+
},
195+
{
196+
"name": "SSH Private Key",
197+
"regex": "-----BEGIN OPENSSH PRIVATE KEY-----",
198+
"severity": "critical",
55199
"confidence": "high"
56200
},
201+
{
202+
"name": "PGP Private Key",
203+
"regex": "-----BEGIN PGP PRIVATE KEY BLOCK-----",
204+
"severity": "critical",
205+
"confidence": "high"
206+
},
207+
{
208+
"name": "Hardcoded Password",
209+
"regex": "(?i)(password|passwd|pwd)(.{0,20})[=:]['\\\"]([^'\\\"\\s]{4,})['\\\"]",
210+
"severity": "high",
211+
"confidence": "medium"
212+
},
213+
{
214+
"name": "Hardcoded API Key",
215+
"regex": "(?i)(api[_-]?key|apikey)(.{0,20})[=:]['\\\"]([a-zA-Z0-9_\\-]{8,})['\\\"]",
216+
"severity": "high",
217+
"confidence": "medium"
218+
},
219+
{
220+
"name": "Hardcoded Auth Token",
221+
"regex": "(?i)(auth[_-]?token|authorization)(.{0,20})[=:]['\\\"]([a-zA-Z0-9_\\-]{8,})['\\\"]",
222+
"severity": "high",
223+
"confidence": "medium"
224+
},
57225
{
58226
"name": "Generic Secret",
59-
"regex": "(?i)secret(.{0,20})(['\"|:=])(.{0,20})",
227+
"regex": "(?i)secret(.{0,20})(['\\\"|:=])(.{8,})",
60228
"severity": "low",
61229
"confidence": "low"
62230
}

0 commit comments

Comments
 (0)