|
package main |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"log" |
|
"net/http" |
|
"strings" |
|
) |
|
|
|
// UserProfile represents a user's profile data |
|
type UserProfile struct { |
|
ID string `json:"id"` |
|
Name string `json:"name"` |
|
Email string `json:"email"` |
|
IsAdmin bool `json:"is_admin"` // A potentially sensitive field |
|
// This field is intentionally not exported in JSON by default for the main profile view. |
|
// We'll see how an alternate path might expose it. |
|
InternalNotes string `json:"-"` |
|
} |
|
|
|
// Mock database - in a real app, this would be a proper database. |
|
var userProfiles = map[string]UserProfile{ |
|
"user123": {ID: "user123", Name: "Alice Wonderland", Email: "[email protected]", IsAdmin: false, InternalNotes: "VIP customer, handle with care."}, |
|
"user456": {ID: "user456", Name: "Bob The Admin", Email: "[email protected]", IsAdmin: true, InternalNotes: "System administrator account."}, |
|
"user789": {ID: "user789", Name: "Charlie User", Email: "[email protected]", IsAdmin: false, InternalNotes: "Regular user, last login: 2025-05-29."}, |
|
} |
|
|
|
// --- Middleware (Simulated) --- |
|
// In a real application, this would involve robust token validation, session checks, etc. |
|
func checkAuth(r *http.Request) bool { |
|
// Extremely simplified: Check for a specific API key in headers. |
|
apiKey := r.Header.Get("X-API-Key") |
|
if apiKey == "STRONG_SECRET_API_KEY" { |
|
// Here you might also check user roles/permissions based on the API key. |
|
// For this demo, any valid key grants access to the "protected" resource. |
|
return true |
|
} |
|
return false |
|
} |
|
|
|
// --- HTTP Handlers --- |
|
|
|
// 1. Primary, "Protected" Endpoint |
|
// This endpoint is intended to be the official way to get user profiles and requires authentication. |
|
func protectedUserProfileHandler(w http.ResponseWriter, r *http.Request) { |
|
if !checkAuth(r) { |
|
log.Printf("Auth failed for %s on /api/users/{id}", r.RemoteAddr) |
|
http.Error(w, "Unauthorized: Missing or invalid API key.", http.StatusUnauthorized) |
|
return |
|
} |
|
|
|
pathParts := strings.Split(r.URL.Path, "/") // e.g., ["", "api", "users", "user123"] |
|
if len(pathParts) < 4 || pathParts[3] == "" { |
|
http.Error(w, "User ID is missing in path.", http.StatusBadRequest) |
|
return |
|
} |
|
userID := pathParts[3] |
|
|
|
profile, exists := userProfiles[userID] |
|
if !exists { |
|
http.Error(w, "User not found.", http.StatusNotFound) |
|
return |
|
} |
|
|
|
// For the protected endpoint, we might choose to return a limited view of the profile. |
|
// (InternalNotes is omitted due to `json:"-"` in UserProfile struct) |
|
log.Printf("Access granted to /api/users/%s for %s", userID, r.RemoteAddr) |
|
w.Header().Set("Content-Type", "application/json") |
|
json.NewEncoder(w).Encode(profile) |
|
} |
|
|
|
// 2. Vulnerable Alternate Path (CWE-424 Example 1: Debug Endpoint) |
|
// This endpoint might have been created for debugging or internal use but was |
|
// left exposed and without proper authentication/authorization checks. |
|
func vulnerableDebugUserProfileHandler(w http.ResponseWriter, r *http.Request) { |
|
pathParts := strings.Split(r.URL.Path, "/") // e.g., ["", "debug", "user-info", "user123"] |
|
if len(pathParts) < 4 || pathParts[3] == "" { |
|
http.Error(w, "User ID is missing in path for debug endpoint.", http.StatusBadRequest) |
|
return |
|
} |
|
userID := pathParts[3] |
|
|
|
// VULNERABILITY: No authentication or authorization check (checkAuth is NOT called). |
|
// This alternate path bypasses the security of the primary endpoint. |
|
profile, exists := userProfiles[userID] |
|
if !exists { |
|
http.Error(w, "User not found (via debug path).", http.StatusNotFound) |
|
return |
|
} |
|
|
|
// To make it worse, this debug endpoint might expose more data than the primary one. |
|
// For example, let's create a temporary struct to expose InternalNotes. |
|
type DebugProfileView struct { |
|
ID string `json:"id"` |
|
Name string `json:"name"` |
|
Email string `json:"email"` |
|
IsAdmin bool `json:"is_admin"` |
|
InternalNotes string `json:"internal_notes_exposed_by_debug"` // Explicitly exposing this |
|
} |
|
|
|
debugView := DebugProfileView{ |
|
ID: profile.ID, |
|
Name: profile.Name, |
|
Email: profile.Email, |
|
IsAdmin: profile.IsAdmin, |
|
InternalNotes: profile.InternalNotes, // Sensitive notes are now exposed |
|
} |
|
|
|
log.Printf("CWE-424 VULNERABILITY: Unauthenticated access to /debug/user-info/%s by %s (exposed sensitive notes)", userID, r.RemoteAddr) |
|
w.Header().Set("Content-Type", "application/json") |
|
json.NewEncoder(w).Encode(debugView) |
|
} |
|
|
|
// 3. Vulnerable Alternate Path (CWE-424 Example 2: Legacy or Forgotten Admin Endpoint) |
|
// This endpoint might be an old admin function that was never properly secured or was forgotten. |
|
func vulnerableLegacyAdminDumpHandler(w http.ResponseWriter, r *http.Request) { |
|
// VULNERABILITY: No authentication check. This is an admin-level data dump. |
|
// This path bypasses all security controls. |
|
|
|
allProfiles := make([]UserProfile, 0, len(userProfiles)) |
|
for _, p := range userProfiles { |
|
// To further demonstrate impact, let's say this path also exposes InternalNotes for all users. |
|
// For simplicity, we'll just dump the existing structures, but InternalNotes is usually `json:"-"`. |
|
// A real leak here might involve custom marshaling or a different struct that includes all fields. |
|
// For this example, we'll focus on the lack of auth for an admin-like function. |
|
// The `InternalNotes` will not be directly visible in this specific dump due to `json:"-"`. |
|
// The main vulnerability here is unauthorized access to an admin-level data enumeration function. |
|
allProfiles = append(allProfiles, p) |
|
} |
|
|
|
log.Printf("CWE-424 VULNERABILITY: Unauthenticated access to /internal-api/admin/dump-all-users by %s", r.RemoteAddr) |
|
w.Header().Set("Content-Type", "application/json") |
|
json.NewEncoder(w).Encode(allProfiles) |
|
} |
|
|
|
func main() { |
|
// Primary, "secure" endpoint |
|
http.HandleFunc("/api/users/", protectedUserProfileHandler) |
|
|
|
// CWE-424: Vulnerable Alternate Paths |
|
// These paths provide access to data or functionality without proper security controls. |
|
http.HandleFunc("/debug/user-info/", vulnerableDebugUserProfileHandler) // Example 1: Debug path |
|
http.HandleFunc("/internal-api/admin/dump-all-users", vulnerableLegacyAdminDumpHandler) // Example 2: Forgotten admin path |
|
|
|
fmt.Println("Server starting on http://localhost:8080") |
|
fmt.Println("---------------------------------------------------------------------") |
|
fmt.Println("Endpoints:") |
|
fmt.Println("1. Protected Profile API:") |
|
fmt.Println(" GET http://localhost:8080/api/users/{userID}") |
|
fmt.Println(" (Requires 'X-API-Key: STRONG_SECRET_API_KEY' header for auth)") |
|
fmt.Println(" Example (Authenticated): curl -H \"X-API-Key: STRONG_SECRET_API_KEY\" http://localhost:8080/api/users/user123") |
|
fmt.Println(" Example (Unauthenticated): curl http://localhost:8080/api/users/user123") |
|
fmt.Println("\n2. CWE-424 Vulnerable Debug Path (exposes more data, no auth):") |
|
fmt.Println(" GET http://localhost:8080/debug/user-info/{userID}") |
|
fmt.Println(" Example: curl http://localhost:8080/debug/user-info/user123") |
|
fmt.Println("\n3. CWE-424 Vulnerable Admin Dump Path (no auth):") |
|
fmt.Println(" GET http://localhost:8080/internal-api/admin/dump-all-users") |
|
fmt.Println(" Example: curl http://localhost:8080/internal-api/admin/dump-all-users") |
|
fmt.Println("---------------------------------------------------------------------") |
|
|
|
if err := http.ListenAndServe(":8080", nil); err != nil { |
|
log.Fatalf("Failed to start server: %v", err) |
|
} |
|
} |