cyberaudit-skill 3.0.0
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.
- package/README.md +77 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +241 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +126 -0
- package/package.json +58 -0
- package/skills/cyberaudit/AGENT-BOOT.md +122 -0
- package/skills/cyberaudit/COMMANDS.md +1125 -0
- package/skills/cyberaudit/INSTALL.md +311 -0
- package/skills/cyberaudit/MASTER.md +194 -0
- package/skills/cyberaudit/README.md +154 -0
- package/skills/cyberaudit/USAGE-GUIDE.md +107 -0
- package/skills/cyberaudit/mobile/MOBILE-CHECKLIST.md +245 -0
- package/skills/cyberaudit/mobile/MOBILE-PHILOSOPHY.md +352 -0
- package/skills/cyberaudit/mobile/MOBILE-REMEDIATION-LIBRARY.md +468 -0
- package/skills/cyberaudit/mobile/MOBILE-THREAT-MODELS.md +279 -0
- package/skills/cyberaudit/mobile/frameworks/EXPO.md +247 -0
- package/skills/cyberaudit/mobile/frameworks/FLUTTER.md +338 -0
- package/skills/cyberaudit/mobile/frameworks/IONIC.md +248 -0
- package/skills/cyberaudit/mobile/frameworks/REACT-NATIVE.md +479 -0
- package/skills/cyberaudit/mobile/vulnerabilities/AUTH-MOBILE.md +160 -0
- package/skills/cyberaudit/mobile/vulnerabilities/BINARY-ANALYSIS.md +193 -0
- package/skills/cyberaudit/mobile/vulnerabilities/CRYPTO-MOBILE.md +192 -0
- package/skills/cyberaudit/mobile/vulnerabilities/IPC-DEEPLINKS.md +231 -0
- package/skills/cyberaudit/mobile/vulnerabilities/NETWORK-MOBILE.md +165 -0
- package/skills/cyberaudit/mobile/vulnerabilities/PERMISSIONS.md +180 -0
- package/skills/cyberaudit/mobile/vulnerabilities/RUNTIME-MOBILE.md +214 -0
- package/skills/cyberaudit/mobile/vulnerabilities/STORAGE.md +197 -0
- package/skills/cyberaudit/reports/EXECUTIVE-SUMMARY-TEMPLATE.md +188 -0
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-MOBILE.md +410 -0
- package/skills/cyberaudit/reports/REPORT-TEMPLATE-WEB.md +222 -0
- package/skills/cyberaudit/shared/COMPLIANCE.md +307 -0
- package/skills/cyberaudit/shared/CVSS-GUIDE.md +175 -0
- package/skills/cyberaudit/shared/OWASP-MAPPER.md +130 -0
- package/skills/cyberaudit/shared/SEVERITY-SCORING.md +102 -0
- package/skills/cyberaudit/shared/THREAT-MODELING.md +112 -0
- package/skills/cyberaudit/web/WEB-CHECKLIST.md +222 -0
- package/skills/cyberaudit/web/WEB-PHILOSOPHY.md +308 -0
- package/skills/cyberaudit/web/WEB-REMEDIATION-LIBRARY.md +665 -0
- package/skills/cyberaudit/web/WEB-THREAT-MODELS.md +460 -0
- package/skills/cyberaudit/web/frameworks/ANGULAR.md +169 -0
- package/skills/cyberaudit/web/frameworks/EXPRESS.md +185 -0
- package/skills/cyberaudit/web/frameworks/LARAVEL.md +371 -0
- package/skills/cyberaudit/web/frameworks/NESTJS.md +337 -0
- package/skills/cyberaudit/web/frameworks/NEXTJS.md +352 -0
- package/skills/cyberaudit/web/frameworks/REACT.md +346 -0
- package/skills/cyberaudit/web/frameworks/VUE.md +205 -0
- package/skills/cyberaudit/web/vulnerabilities/AUTH-AUTHZ.md +117 -0
- package/skills/cyberaudit/web/vulnerabilities/BUSINESS-LOGIC.md +603 -0
- package/skills/cyberaudit/web/vulnerabilities/CORS.md +117 -0
- package/skills/cyberaudit/web/vulnerabilities/CSRF.md +131 -0
- package/skills/cyberaudit/web/vulnerabilities/DESERIALIZATION.md +96 -0
- package/skills/cyberaudit/web/vulnerabilities/HEADERS.md +105 -0
- package/skills/cyberaudit/web/vulnerabilities/IDOR-BOLA.md +153 -0
- package/skills/cyberaudit/web/vulnerabilities/INJECTION.md +165 -0
- package/skills/cyberaudit/web/vulnerabilities/SECRETS.md +119 -0
- package/skills/cyberaudit/web/vulnerabilities/SSRF.md +124 -0
- package/skills/cyberaudit/web/vulnerabilities/SUPPLY-CHAIN.md +142 -0
- package/skills/cyberaudit/web/vulnerabilities/XSS.md +133 -0
- package/skills/cyberaudit/web/vulnerabilities/XXE.md +94 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# 🌐 CORS — CROSS-ORIGIN RESOURCE SHARING GUIDE
|
|
2
|
+
|
|
3
|
+
═══════════════════════════════════════════════════════════════
|
|
4
|
+
UNDERSTANDING CORS
|
|
5
|
+
═══════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
SOP (Same-Origin Policy) = Browser protection
|
|
8
|
+
By default, the browser blocks cross-origin requests.
|
|
9
|
+
CORS is a mechanism to selectively RELAX SOP.
|
|
10
|
+
|
|
11
|
+
Misconfigured CORS = Useless SOP
|
|
12
|
+
|
|
13
|
+
CORS CONCERNS :
|
|
14
|
+
→ JS fetch/axios requests from the browser
|
|
15
|
+
→ Not curl, Postman, or server-to-server requests
|
|
16
|
+
→ Only what the browser enforces
|
|
17
|
+
|
|
18
|
+
SO : CORS is protection against browser attacks
|
|
19
|
+
NOT against direct server-side calls
|
|
20
|
+
|
|
21
|
+
═══════════════════════════════════════════════════════════════
|
|
22
|
+
DANGEROUS PATTERNS
|
|
23
|
+
═══════════════════════════════════════════════════════════════
|
|
24
|
+
|
|
25
|
+
CRITICAL PATTERN — Wildcard with credentials
|
|
26
|
+
DETECT :
|
|
27
|
+
Access-Control-Allow-Origin: *
|
|
28
|
+
Access-Control-Allow-Credentials: true
|
|
29
|
+
|
|
30
|
+
IMPACT :
|
|
31
|
+
Any site can make authenticated requests
|
|
32
|
+
Session data theft, actions on behalf of the user
|
|
33
|
+
|
|
34
|
+
NOTE : Modern browsers refuse * + credentials
|
|
35
|
+
But some custom configs allow it
|
|
36
|
+
|
|
37
|
+
CRITICAL PATTERN — Origin reflection without validation
|
|
38
|
+
DETECT :
|
|
39
|
+
// Bad "dynamic" implementation
|
|
40
|
+
const origin = req.headers.origin
|
|
41
|
+
res.setHeader('Access-Control-Allow-Origin', origin) // Reflect everything!
|
|
42
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true')
|
|
43
|
+
|
|
44
|
+
IMPACT :
|
|
45
|
+
Any origin is accepted → equivalent to *
|
|
46
|
+
But with credentials → even worse than *
|
|
47
|
+
|
|
48
|
+
HIGH PATTERN — null origin accepted
|
|
49
|
+
DETECT :
|
|
50
|
+
if (origin === 'null' || allowedOrigins.includes(origin)) {
|
|
51
|
+
res.setHeader('Access-Control-Allow-Origin', origin)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
IMPACT :
|
|
55
|
+
Local files (file://) and sandboxed iframes
|
|
56
|
+
send Origin: null → access granted
|
|
57
|
+
|
|
58
|
+
HIGH PATTERN — Overly broad subdomains
|
|
59
|
+
DETECT :
|
|
60
|
+
// Poorly written regex
|
|
61
|
+
if (origin.endsWith('.yourapp.com')) {
|
|
62
|
+
// evil.yourapp.com.attacker.com → passes!
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Better but still risky if subdomains compromised :
|
|
66
|
+
if (origin.match(/\.yourapp\.com$/)) {
|
|
67
|
+
// An XSS'd subdomain compromises everything
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
═══════════════════════════════════════════════════════════════
|
|
71
|
+
CORS REMEDIATION
|
|
72
|
+
═══════════════════════════════════════════════════════════════
|
|
73
|
+
|
|
74
|
+
COMPLETE SECURE CONFIGURATION
|
|
75
|
+
const ALLOWED_ORIGINS = new Set([
|
|
76
|
+
'https://yourapp.com',
|
|
77
|
+
'https://www.yourapp.com',
|
|
78
|
+
'https://admin.yourapp.com',
|
|
79
|
+
...(process.env.NODE_ENV === 'development'
|
|
80
|
+
? ['http://localhost:3000', 'http://localhost:3001']
|
|
81
|
+
: [])
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
function corsMiddleware(req, res, next) {
|
|
85
|
+
const origin = req.headers.origin
|
|
86
|
+
|
|
87
|
+
if (!origin) {
|
|
88
|
+
// Request without Origin (curl, Postman, server-to-server)
|
|
89
|
+
// Allow but without CORS header
|
|
90
|
+
return next()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (ALLOWED_ORIGINS.has(origin)) {
|
|
94
|
+
res.setHeader('Access-Control-Allow-Origin', origin)
|
|
95
|
+
res.setHeader('Access-Control-Allow-Credentials', 'true')
|
|
96
|
+
res.setHeader('Access-Control-Allow-Methods',
|
|
97
|
+
'GET, POST, PUT, PATCH, DELETE, OPTIONS')
|
|
98
|
+
res.setHeader('Access-Control-Allow-Headers',
|
|
99
|
+
'Content-Type, Authorization, X-Requested-With')
|
|
100
|
+
res.setHeader('Access-Control-Max-Age', '86400') // Cache preflight
|
|
101
|
+
|
|
102
|
+
if (req.method === 'OPTIONS') {
|
|
103
|
+
return res.status(204).end()
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Silently reject (no CORS header)
|
|
107
|
+
// The browser will block the request
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
next()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Vary header important for caches
|
|
114
|
+
app.use((req, res, next) => {
|
|
115
|
+
res.setHeader('Vary', 'Origin')
|
|
116
|
+
next()
|
|
117
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# 🎭 CSRF — CROSS-SITE REQUEST FORGERY GUIDE
|
|
2
|
+
|
|
3
|
+
═══════════════════════════════════════════════════════════════
|
|
4
|
+
DEFINITION
|
|
5
|
+
═══════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
CSRF = The attacker forces the victim's browser
|
|
8
|
+
to send an authenticated request without their knowledge.
|
|
9
|
+
|
|
10
|
+
Prerequisites :
|
|
11
|
+
→ The victim is logged into the target application
|
|
12
|
+
→ The application uses session cookies
|
|
13
|
+
→ No CSRF protection in place
|
|
14
|
+
|
|
15
|
+
OWASP : A01:2021 (in Broken Access Control)
|
|
16
|
+
CWE : CWE-352
|
|
17
|
+
|
|
18
|
+
ATTACK EXAMPLE :
|
|
19
|
+
1. Victim is logged into bank.com
|
|
20
|
+
2. Victim visits evil.com
|
|
21
|
+
3. evil.com contains :
|
|
22
|
+
<img src="https://bank.com/transfer?to=attacker&amount=1000">
|
|
23
|
+
4. The browser sends the request with bank.com cookies
|
|
24
|
+
5. Transfer made without the victim's knowledge
|
|
25
|
+
|
|
26
|
+
═══════════════════════════════════════════════════════════════
|
|
27
|
+
PATTERNS TO DETECT
|
|
28
|
+
═══════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
CSRF DISABLED (Laravel)
|
|
31
|
+
Route::withoutMiddleware([VerifyCsrfToken::class])
|
|
32
|
+
->group(...)
|
|
33
|
+
|
|
34
|
+
// In VerifyCsrfToken.php
|
|
35
|
+
protected $except = [
|
|
36
|
+
'*', // CRITICAL : disables CSRF everywhere
|
|
37
|
+
'api/*',
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
CSRF MISSING (API without protection)
|
|
41
|
+
// API accepting mutations without CSRF token
|
|
42
|
+
// AND without Origin verification
|
|
43
|
+
app.post('/api/delete-account', deleteAccountHandler)
|
|
44
|
+
|
|
45
|
+
SameSite NOT CONFIGURED
|
|
46
|
+
res.cookie('session', token, {
|
|
47
|
+
httpOnly: true,
|
|
48
|
+
secure: true,
|
|
49
|
+
// SameSite missing → default depends on browser
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
═══════════════════════════════════════════════════════════════
|
|
53
|
+
REMEDIATION
|
|
54
|
+
═══════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
56
|
+
APPROACH 1 — SAMESITE COOKIES (modern, recommended)
|
|
57
|
+
res.cookie('session', token, {
|
|
58
|
+
httpOnly: true,
|
|
59
|
+
secure: true,
|
|
60
|
+
sameSite: 'Strict', // Or 'Lax' for incoming links
|
|
61
|
+
// Strict : Cookie sent only on first-party navigation
|
|
62
|
+
// Lax : Cookie sent for links, not for sub-resources
|
|
63
|
+
// None : Sent everywhere (requires Secure=true)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// SameSite=Lax vs Strict :
|
|
67
|
+
// Strict : login lost if user comes from external link
|
|
68
|
+
// Lax : Balance security/UX
|
|
69
|
+
// → Recommendation : Lax for public apps, Strict for sensitive apps
|
|
70
|
+
|
|
71
|
+
APPROACH 2 — CSRF TOKEN (classic)
|
|
72
|
+
LARAVEL :
|
|
73
|
+
// Automatic with VerifyCsrfToken middleware
|
|
74
|
+
// In Blade forms :
|
|
75
|
+
<form method="POST">
|
|
76
|
+
@csrf
|
|
77
|
+
...
|
|
78
|
+
</form>
|
|
79
|
+
|
|
80
|
+
// For JS API calls :
|
|
81
|
+
fetch('/api/data', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'X-CSRF-TOKEN': document.cookie.match(/XSRF-TOKEN=([^;]+)/)?.[1] }
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
NODE.JS WITH CSURF (deprecated) or csrf-csrf :
|
|
87
|
+
import { doubleCsrf } from 'csrf-csrf'
|
|
88
|
+
|
|
89
|
+
const { generateToken, doubleCsrfProtection } = doubleCsrf({
|
|
90
|
+
getSecret: () => process.env.CSRF_SECRET,
|
|
91
|
+
cookieName: '__Host-psifi.x-csrf-token',
|
|
92
|
+
cookieOptions: { sameSite: 'Strict', secure: true }
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
app.use(doubleCsrfProtection)
|
|
96
|
+
|
|
97
|
+
APPROACH 3 — ORIGIN VERIFICATION
|
|
98
|
+
// For APIs without cookies (JWT in headers)
|
|
99
|
+
// CSRF is not applicable if token is in Authorization header
|
|
100
|
+
// CORS is the protection in this case
|
|
101
|
+
|
|
102
|
+
// Verify Origin for mutations :
|
|
103
|
+
app.use((req, res, next) => {
|
|
104
|
+
if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)) {
|
|
105
|
+
const origin = req.headers.origin
|
|
106
|
+
const referer = req.headers.referer
|
|
107
|
+
|
|
108
|
+
const allowedOrigins = ['https://yourapp.com']
|
|
109
|
+
|
|
110
|
+
if (origin && !allowedOrigins.includes(origin)) {
|
|
111
|
+
return res.status(403).json({ error: 'Origin not allowed' })
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
next()
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
WEBHOOKS — SPECIAL CASE
|
|
118
|
+
// Webhooks receive POST from third-party services
|
|
119
|
+
// Exclude from CSRF BUT validate HMAC signature
|
|
120
|
+
|
|
121
|
+
// Stripe
|
|
122
|
+
app.post('/webhooks/stripe',
|
|
123
|
+
express.raw({ type: 'application/json' }), // Raw body for signature
|
|
124
|
+
(req, res) => {
|
|
125
|
+
const sig = req.headers['stripe-signature']
|
|
126
|
+
const event = stripe.webhooks.constructEvent(
|
|
127
|
+
req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
|
|
128
|
+
)
|
|
129
|
+
// ... processing
|
|
130
|
+
}
|
|
131
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# 💣 DESERIALIZATION — VULNERABILITY GUIDE
|
|
2
|
+
|
|
3
|
+
═══════════════════════════════════════════════════════════════
|
|
4
|
+
DEFINITION
|
|
5
|
+
═══════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
INSECURE DESERIALIZATION =
|
|
8
|
+
Deserializing untrusted data can lead to :
|
|
9
|
+
→ Remote Code Execution (RCE) via gadget chains
|
|
10
|
+
→ Object injection with malicious behavior
|
|
11
|
+
→ DoS via malformed objects
|
|
12
|
+
|
|
13
|
+
OWASP : A08:2021 — Software and Data Integrity Failures
|
|
14
|
+
CWE : CWE-502
|
|
15
|
+
|
|
16
|
+
MOST AT-RISK LANGUAGES :
|
|
17
|
+
→ PHP (unserialize)
|
|
18
|
+
→ Java (ObjectInputStream)
|
|
19
|
+
→ Python (pickle)
|
|
20
|
+
→ Ruby (Marshal)
|
|
21
|
+
→ Node.js (rare but possible)
|
|
22
|
+
|
|
23
|
+
═══════════════════════════════════════════════════════════════
|
|
24
|
+
PATTERNS TO DETECT
|
|
25
|
+
═══════════════════════════════════════════════════════════════
|
|
26
|
+
|
|
27
|
+
PHP — CRITICAL :
|
|
28
|
+
unserialize($userInput)
|
|
29
|
+
unserialize(base64_decode($cookie))
|
|
30
|
+
unserialize(base64_decode($_COOKIE['cart']))
|
|
31
|
+
unserialize(file_get_contents($userProvidedPath))
|
|
32
|
+
|
|
33
|
+
// Second-order : data deserialized from DB
|
|
34
|
+
// that was inserted via user input
|
|
35
|
+
|
|
36
|
+
PYTHON — CRITICAL :
|
|
37
|
+
pickle.loads(userInput)
|
|
38
|
+
pickle.load(file_from_user)
|
|
39
|
+
yaml.load(userInput) // PyYAML without Loader=yaml.SafeLoader
|
|
40
|
+
|
|
41
|
+
NODE.JS — LESS COMMON BUT PRESENT :
|
|
42
|
+
// node-serialize
|
|
43
|
+
serialize.unserialize(userInput)
|
|
44
|
+
// eval() in custom parsers
|
|
45
|
+
|
|
46
|
+
═══════════════════════════════════════════════════════════════
|
|
47
|
+
REMEDIATION
|
|
48
|
+
═══════════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
PHP :
|
|
51
|
+
// ❌ Never deserialize user data
|
|
52
|
+
unserialize($userInput) // Never
|
|
53
|
+
|
|
54
|
+
// ✅ Use JSON
|
|
55
|
+
$data = json_decode($userInput, true)
|
|
56
|
+
|
|
57
|
+
// ✅ If serialize() needed for internal cache :
|
|
58
|
+
// Ensure data never comes from the user
|
|
59
|
+
// Sign serialized data with HMAC
|
|
60
|
+
$serialized = serialize($data)
|
|
61
|
+
$hmac = hash_hmac('sha256', $serialized, $secret)
|
|
62
|
+
$stored = $hmac . ':' . $serialized
|
|
63
|
+
|
|
64
|
+
// On read :
|
|
65
|
+
[$storedHmac, $storedData] = explode(':', $stored, 2)
|
|
66
|
+
if (!hash_equals($storedHmac, hash_hmac('sha256', $storedData, $secret))) {
|
|
67
|
+
throw new Exception('Corrupted data')
|
|
68
|
+
}
|
|
69
|
+
$data = unserialize($storedData)
|
|
70
|
+
|
|
71
|
+
PYTHON :
|
|
72
|
+
# ❌ Never pickle with user input
|
|
73
|
+
data = pickle.loads(userInput) # RCE possible
|
|
74
|
+
|
|
75
|
+
# ✅ JSON
|
|
76
|
+
data = json.loads(userInput)
|
|
77
|
+
|
|
78
|
+
# ✅ Secure PyYAML
|
|
79
|
+
data = yaml.load(userInput, Loader=yaml.SafeLoader)
|
|
80
|
+
# yaml.SafeLoader does not execute Python code
|
|
81
|
+
|
|
82
|
+
# If pickle needed for internal cache :
|
|
83
|
+
# Sign with hmac before storage
|
|
84
|
+
import hmac, hashlib, pickle
|
|
85
|
+
|
|
86
|
+
def safe_serialize(data, secret):
|
|
87
|
+
pickled = pickle.dumps(data)
|
|
88
|
+
sig = hmac.new(secret, pickled, hashlib.sha256).digest()
|
|
89
|
+
return sig + pickled
|
|
90
|
+
|
|
91
|
+
def safe_deserialize(data, secret):
|
|
92
|
+
sig, pickled = data[:32], data[32:]
|
|
93
|
+
expected = hmac.new(secret, pickled, hashlib.sha256).digest()
|
|
94
|
+
if not hmac.compare_digest(sig, expected):
|
|
95
|
+
raise ValueError('Invalid signature')
|
|
96
|
+
return pickle.loads(pickled)
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# 🛡️ SECURITY HEADERS — VULNERABILITY GUIDE
|
|
2
|
+
|
|
3
|
+
═══════════════════════════════════════════════════════════════
|
|
4
|
+
ESSENTIAL SECURITY HEADERS
|
|
5
|
+
═══════════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
EACH MISSING HEADER = MISSING PROTECTION
|
|
8
|
+
|
|
9
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
10
|
+
CONTENT-SECURITY-POLICY (CSP)
|
|
11
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
ROLE : Controls authorized sources for each resource type
|
|
13
|
+
PROTECTS : XSS, script injection, clickjacking, data exfiltration
|
|
14
|
+
MISSING : XSS can execute arbitrary code
|
|
15
|
+
|
|
16
|
+
CHECK :
|
|
17
|
+
□ Present on all HTML pages
|
|
18
|
+
□ Restrictive default-src
|
|
19
|
+
□ unsafe-inline absent from script-src
|
|
20
|
+
□ unsafe-eval absent from script-src
|
|
21
|
+
□ frame-ancestors configured
|
|
22
|
+
□ base-uri configured (prevents base tag injection)
|
|
23
|
+
□ form-action configured
|
|
24
|
+
|
|
25
|
+
TEST : https://csp-evaluator.withgoogle.com/
|
|
26
|
+
|
|
27
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
28
|
+
STRICT-TRANSPORT-SECURITY (HSTS)
|
|
29
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
30
|
+
ROLE : Forces the browser to use HTTPS only
|
|
31
|
+
PROTECTS : SSL stripping, downgrade attacks
|
|
32
|
+
MISSING : HTTP connection possible → MitM
|
|
33
|
+
|
|
34
|
+
RECOMMENDED VALUE :
|
|
35
|
+
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
|
|
36
|
+
|
|
37
|
+
CHECK :
|
|
38
|
+
□ max-age ≥ 31536000 (1 year)
|
|
39
|
+
□ includeSubDomains if all subdomains are HTTPS
|
|
40
|
+
□ preload to be in browser HSTS list
|
|
41
|
+
□ Server on HTTPS (otherwise this header is ignored)
|
|
42
|
+
|
|
43
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
44
|
+
X-FRAME-OPTIONS
|
|
45
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
46
|
+
ROLE : Prevents loading in an iframe
|
|
47
|
+
PROTECTS : Clickjacking
|
|
48
|
+
MISSING : The app can be loaded in a malicious iframe
|
|
49
|
+
|
|
50
|
+
RECOMMENDED VALUE :
|
|
51
|
+
X-Frame-Options: DENY
|
|
52
|
+
// OR if embedding needed :
|
|
53
|
+
X-Frame-Options: SAMEORIGIN
|
|
54
|
+
|
|
55
|
+
NOTE : Replaced by CSP frame-ancestors but X-Frame-Options
|
|
56
|
+
is still supported by older browsers
|
|
57
|
+
|
|
58
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
59
|
+
X-CONTENT-TYPE-OPTIONS
|
|
60
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
61
|
+
ROLE : Prevents MIME sniffing
|
|
62
|
+
PROTECTS : Malicious file upload served as HTML/JS
|
|
63
|
+
MISSING : Uploaded file interpreted with wrong MIME type
|
|
64
|
+
|
|
65
|
+
SINGLE VALUE :
|
|
66
|
+
X-Content-Type-Options: nosniff
|
|
67
|
+
|
|
68
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
69
|
+
REFERRER-POLICY
|
|
70
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
71
|
+
ROLE : Controls info sent in the Referer header
|
|
72
|
+
PROTECTS : Sensitive URL leakage (tokens in URL, internal paths)
|
|
73
|
+
|
|
74
|
+
RECOMMENDED VALUE :
|
|
75
|
+
Referrer-Policy: strict-origin-when-cross-origin
|
|
76
|
+
// Sends : origin only for cross-origin, full URL for same-origin
|
|
77
|
+
|
|
78
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
79
|
+
PERMISSIONS-POLICY
|
|
80
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
81
|
+
ROLE : Controls available browser APIs
|
|
82
|
+
PROTECTS : Unauthorized access to camera, mic, geolocation
|
|
83
|
+
|
|
84
|
+
RECOMMENDED VALUE :
|
|
85
|
+
Permissions-Policy: camera=(), microphone=(), geolocation=(),
|
|
86
|
+
payment=(), usb=(), bluetooth=()
|
|
87
|
+
// Disable everything the app does not use
|
|
88
|
+
|
|
89
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
90
|
+
HEADERS TO REMOVE
|
|
91
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
92
|
+
X-Powered-By → Reveals Express, PHP version, etc.
|
|
93
|
+
Server → Reveals nginx/apache version
|
|
94
|
+
X-AspNet-Version → Reveals .NET version
|
|
95
|
+
X-AspNetMvc-Version
|
|
96
|
+
|
|
97
|
+
This info helps an attacker target specific CVEs.
|
|
98
|
+
|
|
99
|
+
═══════════════════════════════════════════════════════════════
|
|
100
|
+
TEST TOOL
|
|
101
|
+
═══════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
→ https://securityheaders.com/
|
|
104
|
+
→ https://observatory.mozilla.org/
|
|
105
|
+
→ curl -I https://yourapp.com
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# 🎯 IDOR / BOLA — VULNERABILITY GUIDE
|
|
2
|
+
# Insecure Direct Object Reference / Broken Object Level Authorization
|
|
3
|
+
|
|
4
|
+
═══════════════════════════════════════════════════════════════
|
|
5
|
+
DEFINITION
|
|
6
|
+
═══════════════════════════════════════════════════════════════
|
|
7
|
+
|
|
8
|
+
IDOR = Access to a resource by manipulating its identifier
|
|
9
|
+
without verifying the user has permission.
|
|
10
|
+
|
|
11
|
+
BOLA = OWASP API Security term for the same concept on the API side.
|
|
12
|
+
|
|
13
|
+
It is the most common and most impactful vulnerability
|
|
14
|
+
in modern web applications.
|
|
15
|
+
|
|
16
|
+
OWASP Top 10 : A01:2021
|
|
17
|
+
OWASP API : API1:2023
|
|
18
|
+
|
|
19
|
+
═══════════════════════════════════════════════════════════════
|
|
20
|
+
DETECTING IDOR
|
|
21
|
+
═══════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
SEARCH EVERYWHERE AN ID IS USED :
|
|
24
|
+
URLs : /api/users/123, /documents/456
|
|
25
|
+
Body : { "userId": 123, "orderId": 456 }
|
|
26
|
+
Query params : ?accountId=789&fileId=101
|
|
27
|
+
Headers : X-User-Id: 123 (custom headers)
|
|
28
|
+
|
|
29
|
+
TYPES OF IDOR :
|
|
30
|
+
Sequential numeric IDs : /order/1042 → /order/1041
|
|
31
|
+
Base64 IDs : /file/dXNlcjEyMw==
|
|
32
|
+
UUIDs exposed in API : If listing possible → enumeration
|
|
33
|
+
Predictable file names : /uploads/invoice_user123_2024.pdf
|
|
34
|
+
File paths : /download?path=users/123/file.pdf
|
|
35
|
+
|
|
36
|
+
VULNERABLE CODE TO DETECT :
|
|
37
|
+
// Node.js
|
|
38
|
+
app.get('/api/orders/:id', async (req, res) => {
|
|
39
|
+
const order = await Order.findById(req.params.id)
|
|
40
|
+
// NO check that order.userId === req.user.id !
|
|
41
|
+
res.json(order)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// PHP/Laravel
|
|
45
|
+
public function show($id) {
|
|
46
|
+
$invoice = Invoice::find($id)
|
|
47
|
+
return $invoice // NO ownership check!
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prisma/TypeORM
|
|
51
|
+
async getDocument(id: string) {
|
|
52
|
+
return this.db.document.findUnique({ where: { id } })
|
|
53
|
+
// No where userId!
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
═══════════════════════════════════════════════════════════════
|
|
57
|
+
IDOR VARIANTS
|
|
58
|
+
═══════════════════════════════════════════════════════════════
|
|
59
|
+
|
|
60
|
+
INDIRECT IDOR (most often missed)
|
|
61
|
+
// A can access B that belongs to C without being C
|
|
62
|
+
GET /api/projects/123/members
|
|
63
|
+
// The user is a member of project 123
|
|
64
|
+
// But do they have the right to see the members?
|
|
65
|
+
|
|
66
|
+
IDOR VIA RELATIONS
|
|
67
|
+
GET /api/comments/456
|
|
68
|
+
// Comment 456 belongs to a private post
|
|
69
|
+
// But comment access does not check post visibility
|
|
70
|
+
|
|
71
|
+
IDOR IN BATCH OPERATIONS
|
|
72
|
+
DELETE /api/messages?ids=1,2,3,4,5
|
|
73
|
+
// Is each ID verified individually?
|
|
74
|
+
|
|
75
|
+
IDOR IN WRITE OPERATIONS (often more severe)
|
|
76
|
+
PUT /api/profiles/123
|
|
77
|
+
// User 456 modifies profile 123
|
|
78
|
+
|
|
79
|
+
IDOR VIA EXPORT
|
|
80
|
+
GET /api/export?userId=123
|
|
81
|
+
// Exports user 123's data
|
|
82
|
+
|
|
83
|
+
═══════════════════════════════════════════════════════════════
|
|
84
|
+
REMEDIATION
|
|
85
|
+
═══════════════════════════════════════════════════════════════
|
|
86
|
+
|
|
87
|
+
FUNDAMENTAL PRINCIPLE :
|
|
88
|
+
The user ID ALWAYS comes from the token/session.
|
|
89
|
+
NEVER from the body, params, or query strings.
|
|
90
|
+
|
|
91
|
+
UNIVERSAL FIX PATTERN :
|
|
92
|
+
// Instead of :
|
|
93
|
+
resource = findById(requestedId)
|
|
94
|
+
|
|
95
|
+
// Always :
|
|
96
|
+
resource = findById(requestedId, WHERE ownerId = currentUserId)
|
|
97
|
+
|
|
98
|
+
NODE.JS — COMPLETE FIX
|
|
99
|
+
// Middleware that extracts user from token
|
|
100
|
+
const getCurrentUser = (req) => {
|
|
101
|
+
// Already done by JWT middleware
|
|
102
|
+
return req.user // Comes from the token, not the body
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Secured route
|
|
106
|
+
app.get('/api/orders/:orderId', auth, async (req, res) => {
|
|
107
|
+
const currentUser = getCurrentUser(req)
|
|
108
|
+
|
|
109
|
+
const order = await Order.findOne({
|
|
110
|
+
where: {
|
|
111
|
+
id: req.params.orderId,
|
|
112
|
+
userId: currentUser.id, // Ownership check integrated in query
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
if (!order) {
|
|
117
|
+
// 404 rather than 403 : don't confirm the resource exists
|
|
118
|
+
return res.status(404).json({ error: 'Resource not found' })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
res.json(order)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
USE UUIDS
|
|
125
|
+
// Instead of sequential IDs :
|
|
126
|
+
id: 1042, 1043, 1044... → trivial to enumerate
|
|
127
|
+
|
|
128
|
+
// Use UUIDs v4 :
|
|
129
|
+
id: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
|
|
130
|
+
// Impossible to guess, but ownership check remains mandatory!
|
|
131
|
+
|
|
132
|
+
IDOR MONITORING
|
|
133
|
+
// Alert on abnormal access patterns
|
|
134
|
+
const accessLog = async (userId, resourceId, success) => {
|
|
135
|
+
await AuditLog.create({
|
|
136
|
+
userId, resourceId, success,
|
|
137
|
+
timestamp: new Date(),
|
|
138
|
+
ip: req.ip,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// If >10 denied accesses in 1 minute → alert
|
|
142
|
+
const recentFailures = await AuditLog.count({
|
|
143
|
+
where: {
|
|
144
|
+
userId,
|
|
145
|
+
success: false,
|
|
146
|
+
timestamp: { $gt: new Date(Date.now() - 60000) }
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (recentFailures > 10) {
|
|
151
|
+
await alertSecurityTeam({ userId, type: 'possible_idor_scan' })
|
|
152
|
+
}
|
|
153
|
+
}
|