|
1 | 1 | # OWASP Top 10 Security Checklist - ProStaff API |
2 | 2 |
|
3 | | -Comprehensive security checklist based on OWASP Top 10 2021 |
| 3 | +Comprehensive security checklist covering both OWASP Top 10 2025 (Release Candidate) and OWASP Top 10 2021. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +# OWASP Top 10 2025 (Release Candidate) |
| 8 | + |
| 9 | +## A01:2025 – Broken Access Control |
| 10 | + |
| 11 | +### Authentication & Authorization |
| 12 | + |
| 13 | +- [ ] **JWT Token Security** |
| 14 | + - [ ] Tokens have expiration time |
| 15 | + - [ ] Refresh tokens implemented securely |
| 16 | + - [ ] Token blacklist on logout |
| 17 | + - [ ] Token stored securely (not in localStorage for frontend) |
| 18 | + - [ ] Secret key is strong and environment-specific |
| 19 | + |
| 20 | +- [ ] **API Authorization** |
| 21 | + - [ ] All endpoints require authentication (except public routes) |
| 22 | + - [ ] Pundit policies implemented for all resources |
| 23 | + - [ ] Organization-scoped queries (`current_organization` check) |
| 24 | + - [ ] Role-based access control (admin, coach, analyst, viewer) |
| 25 | + - [ ] No IDOR (Insecure Direct Object Reference) vulnerabilities |
| 26 | + |
| 27 | +- [ ] **Server-Side Request Forgery (SSRF)** (Merged into A01 in 2025) |
| 28 | + - [ ] **Riot API Integration** |
| 29 | + - [ ] URL validation before requests |
| 30 | + - [ ] Whitelist allowed domains |
| 31 | + - [ ] No user-controlled URLs |
| 32 | + - [ ] Timeout on external requests |
| 33 | + - [ ] **Internal Service Protection** |
| 34 | + - [ ] No access to localhost |
| 35 | + - [ ] No access to private IPs (192.168.*, 10.*, 127.*) |
| 36 | + - [ ] No access to metadata endpoints (169.254.169.254) |
| 37 | + |
| 38 | +- [ ] **Tests** |
| 39 | + ```bash |
| 40 | + # Manual test |
| 41 | + curl -H "Authorization: Bearer INVALID_TOKEN" http://localhost:3333/api/v1/dashboard |
| 42 | + # Should return 401 Unauthorized |
| 43 | + |
| 44 | + # Try accessing another org's data |
| 45 | + curl -H "Authorization: Bearer USER_ORG_A_TOKEN" \ |
| 46 | + http://localhost:3333/api/v1/players/ORG_B_PLAYER_ID |
| 47 | + # Should return 403 Forbidden or 404 Not Found |
| 48 | + |
| 49 | + # Try SSRF via webhook/callback |
| 50 | + curl -X POST http://localhost:3333/api/v1/webhooks \ |
| 51 | + -d '{"url":"http://169.254.169.254/latest/meta-data/"}' |
| 52 | + # Should be rejected |
| 53 | + ``` |
| 54 | + |
| 55 | +## A02:2025 – Security Misconfiguration |
| 56 | + |
| 57 | +### Configuration Security |
| 58 | + |
| 59 | +- [ ] **Rails Configuration** |
| 60 | + - [ ] `config.force_ssl = true` in production |
| 61 | + - [ ] Debug mode disabled in production |
| 62 | + - [ ] Detailed error pages disabled in production |
| 63 | + - [ ] Asset host configured for CDN |
| 64 | + |
| 65 | +- [ ] **Headers** |
| 66 | + - [ ] `X-Frame-Options: DENY` |
| 67 | + - [ ] `X-Content-Type-Options: nosniff` |
| 68 | + - [ ] `X-XSS-Protection: 1; mode=block` |
| 69 | + - [ ] `Strict-Transport-Security: max-age=31536000` |
| 70 | + - [ ] `Content-Security-Policy` configured |
| 71 | + - [ ] `Referrer-Policy: strict-origin-when-cross-origin` |
| 72 | + |
| 73 | +- [ ] **CORS** |
| 74 | + - [ ] Whitelist specific origins, not `*` |
| 75 | + - [ ] Credentials allowed only for trusted origins |
| 76 | + - [ ] Proper preflight handling |
| 77 | + |
| 78 | +- [ ] **Tests** |
| 79 | + ```bash |
| 80 | + # Security headers check |
| 81 | + curl -I http://localhost:3333/up | grep -E "X-Frame-Options|X-Content-Type" |
| 82 | + |
| 83 | + # Brakeman scan |
| 84 | + ./security_tests/scripts/brakeman-scan.sh |
| 85 | + ``` |
| 86 | + |
| 87 | +## A03:2025 – Software Supply Chain Failures |
| 88 | + |
| 89 | +### Dependency & Pipeline Security |
| 90 | + |
| 91 | +- [ ] **Dependency Management** |
| 92 | + - [ ] All gems up to date |
| 93 | + - [ ] No known vulnerabilities (Bundle Audit) |
| 94 | + - [ ] Unused gems removed |
| 95 | + - [ ] `Gemfile.lock` committed and verified |
| 96 | + - [ ] Dependabot enabled and monitored |
| 97 | + |
| 98 | +- [ ] **Build Pipeline Integrity** |
| 99 | + - [ ] CI/CD pipelines defined in code |
| 100 | + - [ ] Build scripts verified |
| 101 | + - [ ] Secrets not exposed in build logs |
| 102 | + - [ ] Artifact signing (if applicable) |
| 103 | + |
| 104 | +- [ ] **Tests** |
| 105 | + ```bash |
| 106 | + # Check for vulnerabilities |
| 107 | + bundle audit check --update |
| 108 | + |
| 109 | + # List outdated gems |
| 110 | + bundle outdated |
| 111 | + |
| 112 | + # OWASP Dependency Check |
| 113 | + docker run --rm -v $(pwd):/src owasp/dependency-check:latest \ |
| 114 | + --scan /src --format ALL |
| 115 | + ``` |
| 116 | + |
| 117 | +## A04:2025 – Cryptographic Failures |
| 118 | + |
| 119 | +### Data Encryption |
| 120 | + |
| 121 | +- [ ] **Passwords** |
| 122 | + - [ ] BCrypt with proper cost factor (12+) |
| 123 | + - [ ] No password in logs or error messages |
| 124 | + - [ ] Password complexity requirements enforced |
| 125 | + |
| 126 | +- [ ] **Sensitive Data** |
| 127 | + - [ ] API keys encrypted at rest |
| 128 | + - [ ] Database encryption for PII |
| 129 | + - [ ] HTTPS enforced in production |
| 130 | + - [ ] TLS 1.2+ only |
| 131 | + |
| 132 | +- [ ] **Environment Variables** |
| 133 | + - [ ] All secrets in environment variables |
| 134 | + - [ ] `.env` file in `.gitignore` |
| 135 | + - [ ] No secrets in git history |
| 136 | + - [ ] Different secrets per environment |
| 137 | + |
| 138 | +- [ ] **Tests** |
| 139 | + ```bash |
| 140 | + # Check for exposed secrets |
| 141 | + git log -p | grep -i "api_key\|secret\|password" | grep "+" |
| 142 | + |
| 143 | + # Scan for secrets in code |
| 144 | + docker run --rm -v $(pwd):/src trufflesecurity/trufflehog:latest \ |
| 145 | + git file:///src --only-verified |
| 146 | + ``` |
| 147 | + |
| 148 | +## A05:2025 – Injection |
| 149 | + |
| 150 | +### SQL & Command Injection |
| 151 | + |
| 152 | +- [ ] **ActiveRecord Queries** |
| 153 | + - [ ] No string interpolation in queries |
| 154 | + - [ ] Parameterized queries only |
| 155 | + - [ ] `.where(id: params[:id])` NOT `.where("id = #{params[:id]}")` |
| 156 | + - [ ] Review all raw SQL queries |
| 157 | + |
| 158 | +- [ ] **Command Injection** |
| 159 | + - [ ] No `system()`, `exec()`, backticks with user input |
| 160 | + - [ ] If shell commands needed, use `Open3.capture3` with whitelisting |
| 161 | + |
| 162 | +- [ ] **Tests** |
| 163 | + ```bash |
| 164 | + # ZAP API scan includes injection tests |
| 165 | + ./security_tests/scripts/zap-api-scan.sh |
| 166 | + |
| 167 | + # Manual SQL injection test |
| 168 | + curl -X GET "http://localhost:3333/api/v1/players?name=admin'%20OR%20'1'='1" |
| 169 | + # Should NOT return data or error with SQL |
| 170 | + ``` |
| 171 | + |
| 172 | +## A06:2025 – Insecure Design |
| 173 | + |
| 174 | +### Architecture Security |
| 175 | + |
| 176 | +- [ ] **Rate Limiting** |
| 177 | + - [ ] Rack::Attack configured |
| 178 | + - [ ] Login endpoint throttled |
| 179 | + - [ ] API endpoints rate limited per user/IP |
| 180 | + - [ ] Exponential backoff on failed attempts |
| 181 | + |
| 182 | +- [ ] **Input Validation** |
| 183 | + - [ ] Strong parameters in all controllers |
| 184 | + - [ ] Data type validation |
| 185 | + - [ ] Length limits on strings |
| 186 | + - [ ] Regex validation where needed |
| 187 | + |
| 188 | +- [ ] **Business Logic** |
| 189 | + - [ ] State transitions validated |
| 190 | + - [ ] No race conditions in critical operations |
| 191 | + - [ ] Idempotency for mutations |
| 192 | + - [ ] Transaction locks where needed |
| 193 | + |
| 194 | +- [ ] **Tests** |
| 195 | + ```bash |
| 196 | + # Rate limiting test |
| 197 | + for i in {1..100}; do |
| 198 | + curl -X POST http://localhost:3333/api/v1/auth/login \ |
| 199 | + -d '{"email":"[email protected]","password":"wrong"}' & |
| 200 | + done |
| 201 | + # Should eventually return 429 Too Many Requests |
| 202 | + ``` |
| 203 | + |
| 204 | +## A07:2025 – Authentication Failures |
| 205 | + |
| 206 | +### Authentication Security |
| 207 | + |
| 208 | +- [ ] **Password Security** |
| 209 | + - [ ] Minimum 8 characters |
| 210 | + - [ ] Complexity requirements |
| 211 | + - [ ] No common passwords (have_i_been_pwned check) |
| 212 | + - [ ] Bcrypt cost factor 12+ |
| 213 | + |
| 214 | +- [ ] **Session Management** |
| 215 | + - [ ] JWT expiration (15 min access, 7 day refresh) |
| 216 | + - [ ] Secure session storage (Redis) |
| 217 | + - [ ] Session invalidation on logout |
| 218 | + - [ ] Session timeout after inactivity |
| 219 | + |
| 220 | +- [ ] **Account Recovery** |
| 221 | + - [ ] Secure password reset flow |
| 222 | + - [ ] Time-limited reset tokens |
| 223 | + - [ ] Email verification |
| 224 | + - [ ] Rate limited reset requests |
| 225 | + |
| 226 | +- [ ] **Tests** |
| 227 | + ```bash |
| 228 | + # Weak password test |
| 229 | + curl -X POST http://localhost:3333/api/v1/auth/register \ |
| 230 | + -H "Content-Type: application/json" \ |
| 231 | + -d '{"email":"[email protected]","password":"123"}' |
| 232 | + # Should be rejected |
| 233 | + ``` |
| 234 | + |
| 235 | +## A08:2025 – Software or Data Integrity Failures |
| 236 | + |
| 237 | +### Code Integrity |
| 238 | + |
| 239 | +- [ ] **CI/CD Security** |
| 240 | + - [ ] Signed commits |
| 241 | + - [ ] Code review required |
| 242 | + - [ ] Branch protection |
| 243 | + - [ ] Automated tests pass |
| 244 | + |
| 245 | +- [ ] **Serialization** |
| 246 | + - [ ] No unsafe deserialization |
| 247 | + - [ ] JSON parsing only |
| 248 | + - [ ] No YAML.load (use YAML.safe_load) |
| 249 | + - [ ] No Marshal.load on user input |
| 250 | + |
| 251 | +- [ ] **Auto-updates** |
| 252 | + - [ ] Review before auto-merge |
| 253 | + - [ ] Test auto-updated dependencies |
| 254 | + - [ ] Pin critical dependencies |
| 255 | + |
| 256 | +- [ ] **Tests** |
| 257 | + ```bash |
| 258 | + # Check for unsafe deserialization |
| 259 | + grep -r "Marshal.load\|YAML.load" app/ |
| 260 | + ``` |
| 261 | + |
| 262 | +## A09:2025 – Logging & Alerting Failures |
| 263 | + |
| 264 | +### Logging & Monitoring |
| 265 | + |
| 266 | +- [ ] **Application Logs** |
| 267 | + - [ ] Authentication attempts logged |
| 268 | + - [ ] Authorization failures logged |
| 269 | + - [ ] Sensitive operations logged |
| 270 | + - [ ] No sensitive data in logs (passwords, tokens) |
| 271 | + |
| 272 | +- [ ] **Audit Trail** |
| 273 | + - [ ] AuditLog model tracks changes |
| 274 | + - [ ] Who, what, when recorded |
| 275 | + - [ ] IP address logged |
| 276 | + - [ ] Tamper-proof logs |
| 277 | + |
| 278 | +- [ ] **Monitoring** |
| 279 | + - [ ] Error tracking (Sentry/Rollbar) |
| 280 | + - [ ] Performance monitoring (New Relic/Scout) |
| 281 | + - [ ] Uptime monitoring |
| 282 | + - [ ] Alert on suspicious activity |
| 283 | + |
| 284 | +- [ ] **Tests** |
| 285 | + ```bash |
| 286 | + # Check logs don't contain secrets |
| 287 | + grep -r "password\|token\|secret" log/ | grep -v "filtered" |
| 288 | + ``` |
| 289 | + |
| 290 | +## A10:2025 – Mishandling of Exceptional Conditions |
| 291 | + |
| 292 | +### Error Handling & Logic |
| 293 | + |
| 294 | +- [ ] **Fail Safe** |
| 295 | + - [ ] System fails closed (deny access) on error |
| 296 | + - [ ] Transactions rolled back on failure |
| 297 | + - [ ] Default case in switch/case statements handles unexpected values |
| 298 | + |
| 299 | +- [ ] **Error Messages** |
| 300 | + - [ ] No stack traces in API responses (production) |
| 301 | + - [ ] Generic error messages for security failures (e.g., "Invalid credentials" not "User not found") |
| 302 | + - [ ] Proper HTTP status codes (400, 401, 403, 404, 500) |
| 303 | + |
| 304 | +- [ ] **Tests** |
| 305 | + ```bash |
| 306 | + # Trigger error and check response |
| 307 | + curl -H "Content-Type: application/json" \ |
| 308 | + -d '{"malformed_json":' \ |
| 309 | + http://localhost:3333/api/v1/dashboard |
| 310 | + # Should return 400 Bad Request, no stack trace |
| 311 | + ``` |
| 312 | + |
| 313 | +--- |
| 314 | + |
| 315 | +# OWASP Top 10 2021 |
4 | 316 |
|
5 | 317 | ## A01:2021 – Broken Access Control |
6 | 318 |
|
|
0 commit comments