Files
MyClub/DOCS/SECURITY_AUDIT_REPORT.md
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

423 lines
13 KiB
Markdown

# Security Audit Report
**Date:** October 15, 2025
**Auditor:** Cascade AI
**Scope:** Full application security review
---
## Executive Summary
This comprehensive security audit examined authentication, authorization, input validation, data protection, and API security across the fotbal-club application. The application demonstrates **good security practices** in most areas, but several **critical and high-priority vulnerabilities** require immediate attention.
**Overall Security Rating:** ⚠️ **MEDIUM-HIGH RISK**
---
## 🔴 Critical Findings
### 1. **Exposed API Keys and Credentials in .env File**
**Severity:** CRITICAL
**Location:** `.env` (lines 84, 96)
**Issue:**
- Real OpenRouter API key exposed: `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`
- Real Umami Analytics credentials exposed: `admin / eevRQ6h3G@!c#y4A1T`
**Impact:**
- Unauthorized API usage and potential billing charges
- Complete compromise of analytics account
- These credentials are committed to version control
**Recommendation:**
```bash
# IMMEDIATE ACTION REQUIRED:
# 1. Rotate all exposed credentials NOW
# 2. Remove from .env and use .env.example for templates
# 3. Add .env to .gitignore
# 4. Review git history and consider repository secrets scanning
# 5. Use environment-specific secrets management (Azure Key Vault, AWS Secrets Manager, etc.)
```
### 2. **Weak Default JWT Secret**
**Severity:** CRITICAL
**Location:** `pkg/utils/jwt.go` (line 67), `.env` (line 20)
**Issue:**
```go
func GetJWTSecret() string {
secret := os.Getenv("JWT_SECRET")
if secret == "" {
return "default-secret-key-change-in-production" // ⚠️ Weak default
}
return secret
}
```
Current `.env` also uses weak secret: `your_jwt_secret_key_here`
**Impact:**
- JWT tokens can be forged if default is used in production
- Complete authentication bypass possible
- All user sessions compromised
**Recommendation:**
```go
// Fail hard if JWT secret is missing or weak
func GetJWTSecret() string {
secret := os.Getenv("JWT_SECRET")
if secret == "" || secret == "default-secret-key-change-in-production" ||
secret == "your_jwt_secret_key_here" || len(secret) < 32 {
log.Fatal("SECURITY: JWT_SECRET must be set to a strong secret (minimum 32 characters)")
}
return secret
}
```
**Note:** `main.go` (line 184) checks this for production only - should check ALL environments.
---
## 🟠 High-Priority Findings
### 3. **Dev Bypass Middleware Allows Admin Access Without Authentication**
**Severity:** HIGH
**Location:** `internal/middleware/auth.go` (lines 78-92)
**Issue:**
```go
func DevBypass() gin.HandlerFunc {
return func(c *gin.Context) {
if config.AppConfig != nil && config.AppConfig.AppEnv != "production" {
if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" {
c.Set("userRole", "admin")
c.Set("user", &models.User{Role: "admin"}) // ⚠️ Fake admin user
c.Next()
return
}
}
c.Next()
}
}
```
**Impact:**
- Any request with `X-Dev-Admin: true` header gets admin access in non-production
- No authentication required
- Development/staging environments fully compromised
**Recommendation:**
```go
// Remove DevBypass entirely or restrict to localhost only
func DevBypass() gin.HandlerFunc {
return func(c *gin.Context) {
// Only allow from localhost
if config.AppConfig != nil && config.AppConfig.AppEnv == "development" {
if c.ClientIP() == "127.0.0.1" || c.ClientIP() == "::1" {
if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" {
log.Println("WARNING: DevBypass used from localhost")
c.Set("userRole", "admin")
c.Set("user", &models.User{Role: "admin"})
c.Next()
return
}
}
}
c.Next()
}
}
```
### 4. **Admin Access Token Bypass**
**Severity:** HIGH
**Location:** `internal/middleware/auth.go` (lines 18-27)
**Issue:**
```go
if config.AppConfig != nil && config.AppConfig.AdminAccessToken != "" {
header := c.GetHeader("X-Admin-Token")
if header != "" && header == config.AppConfig.AdminAccessToken {
c.Set("userRole", "admin")
c.Set("user", &models.User{Role: "admin"})
c.Next()
return
}
}
```
**Impact:**
- Bypasses normal JWT authentication
- Single token compromise = full admin access
- No rate limiting on this check
- Token doesn't expire
**Recommendation:**
- Remove this feature entirely OR
- Implement proper token rotation and expiration
- Add rate limiting
- Log all usage
- Restrict to specific IP ranges
### 5. **Insufficient Input Sanitization**
**Severity:** HIGH
**Location:** `pkg/utils/sanitize.go`
**Issue:**
- Custom regex-based HTML sanitization is prone to bypasses
- No use of industry-standard sanitization library (bluemonday)
- Comment on line 9 acknowledges: "For production, consider using bluemonday library"
**Impact:**
- XSS vulnerabilities possible
- HTML injection attacks
- JavaScript execution in user-controlled content
**Recommendation:**
```bash
# Install bluemonday
go get github.com/microcosm-cc/bluemonday
# Replace regex-based sanitization with bluemonday
```
```go
import "github.com/microcosm-cc/bluemonday"
func SanitizeHTML(html string) string {
p := bluemonday.UGCPolicy() // User-generated content policy
return p.Sanitize(html)
}
```
### 6. **Frontend XSS Risk - dangerouslySetInnerHTML Usage**
**Severity:** HIGH
**Location:** Multiple frontend files
**Affected Files:**
- `frontend/src/pages/AboutPage.tsx` (4 occurrences)
- `frontend/src/pages/ArticleDetailPage.tsx` (1 occurrence)
- `frontend/src/pages/ActivityDetailPage.tsx` (1 occurrence - using DOMPurify ✓)
- `frontend/src/pages/ClubPage.tsx` (1 occurrence)
- `frontend/src/pages/admin/NewsletterAdminPage.tsx` (2 occurrences - using sanitizeHtml ✓)
**Issue:**
Some uses sanitize properly, others don't consistently:
```tsx
// ⚠️ Direct use without sanitization
<Box dangerouslySetInnerHTML={{ __html: settings.about_html as any }} />
// ✅ Proper sanitization
<Box dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(String(data.description)) }} />
```
**Recommendation:**
- Ensure ALL `dangerouslySetInnerHTML` uses are sanitized with DOMPurify
- Create a wrapper component to enforce sanitization
---
## 🟡 Medium-Priority Findings
### 7. **CSRF Protection Gaps**
**Severity:** MEDIUM
**Location:** `internal/middleware/csrf.go`
**Issues:**
1. **In-memory token storage** (line 19): Tokens lost on server restart
2. **No CSRF on Bearer token requests** (line 62): Skips CSRF if Bearer header present
3. **1-hour token expiry**: May be too short for long forms
**Recommendation:**
```go
// Use Redis or database for token persistence
// Consider requiring CSRF for sensitive operations even with Bearer tokens
// Extend token lifetime to 4-8 hours for better UX
```
### 8. **Rate Limiting Not Applied to All Endpoints**
**Severity:** MEDIUM
**Location:** `internal/routes/routes.go`
**Issue:**
Rate limiting only on specific endpoints:
- Login: 15/minute
- Register: 5/hour
- File upload: 30/minute
- Contact form: 10/minute
- Newsletter: 30/minute
- Poll voting: 10/minute
Missing rate limiting on:
- Password reset verification
- Admin API endpoints
- File operations (delete, scan)
- Newsletter subscriber management
**Recommendation:**
Apply global rate limiting middleware with per-endpoint overrides.
### 9. **SQL Injection - Low Risk (GORM Protected)**
**Severity:** LOW (Informational)
**Location:** Throughout controllers
**Finding:**
Application uses GORM ORM properly with parameterized queries. No raw SQL detected in controllers. Examples:
```go
// ✅ Safe - parameterized
db.Where("email = ?", email).First(&user)
db.Where("LOWER(email) = LOWER(?)", email).First(&user)
```
**Status:****PASS** - No SQL injection vulnerabilities detected
### 10. **Password Policy**
**Severity:** MEDIUM
**Location:** `internal/controllers/auth_controller.go` (line 21)
**Issue:**
```go
Password string `json:"password" binding:"required,min=8"`
```
Minimum 8 characters, but no complexity requirements.
**Recommendation:**
```go
// Add password validation
func ValidatePassword(password string) error {
if len(password) < 12 {
return errors.New("password must be at least 12 characters")
}
// Check for uppercase, lowercase, number, special char
hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password)
hasLower := regexp.MustCompile(`[a-z]`).MatchString(password)
hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password)
hasSpecial := regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`).MatchString(password)
if !(hasUpper && hasLower && hasNumber && hasSpecial) {
return errors.New("password must contain uppercase, lowercase, number, and special character")
}
return nil
}
```
### 11. **File Upload Security**
**Severity:** MEDIUM
**Location:** `internal/config/config.go`, `internal/controllers/files_controller.go`
**Findings:**
**Good practices:**
- File size limits (10MB)
- MIME type whitelist
- Filename sanitization (`pkg/utils/sanitize.go`)
⚠️ **Issues:**
- No virus scanning
- SVG files allowed (can contain scripts)
- File content not validated (MIME type spoofing possible)
**Recommendation:**
```go
// 1. Remove SVG from allowed types or sanitize thoroughly
// 2. Validate file content, not just extension
// 3. Store uploads outside webroot
// 4. Consider ClamAV integration for virus scanning
```
### 12. **Session Management**
**Severity:** MEDIUM
**Location:** JWT implementation
**Issues:**
- JWT tokens are stateless - no revocation mechanism
- 24-hour expiration (reasonable but fixed)
- No refresh token mechanism
- HttpOnly cookies used (✅ good) but no rotation
**Recommendation:**
Implement token blacklist or use short-lived access tokens with refresh tokens.
---
## 🟢 Security Strengths
The application demonstrates several **excellent security practices**:
1.**Password Hashing:** Uses bcrypt with proper cost (`pkg/utils/password.go`)
2.**HttpOnly Cookies:** Prevents XSS token theft (`internal/controllers/auth_controller.go` line 150-158)
3.**Secure Cookie Flags:** Sets Secure flag when using HTTPS
4.**CORS Configuration:** Properly configured with origin validation (`main.go` line 111-130)
5.**Security Headers:** X-Content-Type-Options, X-Frame-Options, HSTS (`main.go` line 99-105)
6.**Content Security Policy:** Configurable CSP header
7.**Admin Password Verification:** Required when modifying admin accounts (`internal/controllers/auth_controller.go` line 491)
8.**Email Case-Insensitive Lookup:** Prevents duplicate accounts with different casing
9.**Graceful Shutdown:** Proper cleanup on server shutdown (`main.go`)
10.**Database Connection Pooling:** Configured limits prevent resource exhaustion
---
## Priority Action Items
### Immediate (Within 24 hours):
1. **Rotate all exposed credentials** in `.env` file
2. **Remove .env from version control**, use .env.example
3. **Set strong JWT_SECRET** (minimum 32 characters, random)
4. **Disable or restrict DevBypass middleware**
### Short-term (Within 1 week):
5. **Replace custom HTML sanitization** with bluemonday
6. **Audit all dangerouslySetInnerHTML** usage in frontend
7. **Remove or secure Admin Access Token** feature
8. **Implement comprehensive rate limiting**
### Medium-term (Within 1 month):
9. **Add password complexity requirements**
10. **Implement JWT token blacklist** or refresh tokens
11. **Add file content validation** for uploads
12. **Implement persistent CSRF token storage**
---
## Compliance Notes
### GDPR Considerations:
- ✅ Newsletter unsubscribe mechanism present
- ✅ Email preferences management
- ⚠️ Consider adding data export/deletion endpoints
### OWASP Top 10 (2021):
- **A01 Broken Access Control:** ⚠️ DevBypass and AdminAccessToken issues
- **A02 Cryptographic Failures:** ⚠️ Weak default JWT secret
- **A03 Injection:** ✅ GORM protects against SQL injection
- **A04 Insecure Design:** ⚠️ In-memory CSRF tokens
- **A05 Security Misconfiguration:** ⚠️ Exposed credentials
- **A07 Identification/Authentication Failures:** ⚠️ Weak password policy
- **A08 Software/Data Integrity:** ⚠️ No subresource integrity checks
---
## Testing Recommendations
1. **Penetration Testing:** Engage security professionals for thorough testing
2. **Dependency Scanning:** Use `go mod` security scanning tools
3. **SAST/DAST:** Implement automated security scanning in CI/CD
4. **Secret Scanning:** Use tools like GitGuardian or Gitleaks
---
## Conclusion
The fotbal-club application has a **solid security foundation** with proper use of bcrypt, GORM ORM, security headers, and CORS. However, **critical vulnerabilities** exist around exposed credentials, weak JWT defaults, and authentication bypasses that must be addressed immediately.
**Recommended next steps:**
1. Fix all CRITICAL issues within 24 hours
2. Schedule HIGH priority fixes within 1 week
3. Implement continuous security monitoring
4. Conduct regular security audits
---
**Report End**
For questions or clarifications, review the specific file locations and code references provided above.