Created
June 6, 2025 09:31
-
-
Save armamini/e09bd006478d1aa4cc188f353f44bbc4 to your computer and use it in GitHub Desktop.
CORS Checker
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "flag" | |
| "fmt" | |
| "net/http" | |
| "net/url" | |
| "os" | |
| "strings" | |
| "time" | |
| ) | |
| type result struct { | |
| TestOrigin string | |
| ACAOHeader string // Access-Control-Allow-Origin | |
| ACACHeader string // Access-Control-Allow-Credentials | |
| IsPotentiallyInsecure bool | |
| Reason string | |
| IsMajorIssue bool | |
| } | |
| func main() { | |
| targetURLArg := flag.String("url", "", "The target URL to check for CORS policy") | |
| flag.Parse() | |
| if *targetURLArg == "" { | |
| fmt.Println("Error: -url flag is required.") | |
| flag.Usage() | |
| os.Exit(1) | |
| } | |
| targetURL := *targetURLArg | |
| if !strings.HasPrefix(targetURL, "http://") && !strings.HasPrefix(targetURL, "https://") { | |
| targetURL = "http://" + targetURL // Default to http if no scheme | |
| } | |
| parsedTargetURL, err := url.Parse(targetURL) | |
| if err != nil { | |
| fmt.Printf("Error: Invalid URL provided: %v\n", err) | |
| os.Exit(1) | |
| } | |
| // Derive the actual origin of the target URL for comparison | |
| actualTargetOrigin := fmt.Sprintf("%s://%s", parsedTargetURL.Scheme, parsedTargetURL.Host) | |
| fmt.Printf("🔍 Checking CORS policy for: %s\n", targetURL) | |
| fmt.Printf(" (Target's own origin: %s)\n\n", actualTargetOrigin) | |
| testOrigins := []string{ | |
| "http://evil-attacker.com", | |
| "https://another-random-site.org", | |
| "null", | |
| } | |
| // HTTP client with a timeout | |
| client := &http.Client{ | |
| Timeout: 10 * time.Second, | |
| // Prevent following redirects automatically, as we want to inspect headers of the initial response | |
| CheckRedirect: func(req *http.Request, via []*http.Request) error { | |
| return http.ErrUseLastResponse | |
| }, | |
| } | |
| var results []result | |
| overallInsecureDetected := false | |
| majorIssueFound := false | |
| for _, testOrigin := range testOrigins { | |
| fmt.Printf("🧪 Testing with Origin: %s\n", testOrigin) | |
| req, err := http.NewRequest("GET", targetURL, nil) | |
| if err != nil { | |
| fmt.Printf(" ❌ Error creating request for origin %s: %v\n\n", testOrigin, err) | |
| results = append(results, result{TestOrigin: testOrigin, Reason: fmt.Sprintf("Error creating request: %v", err)}) | |
| continue | |
| } | |
| req.Header.Set("Origin", testOrigin) | |
| req.Header.Set("User-Agent", "Go-CORS-Checker/1.0") | |
| resp, err := client.Do(req) | |
| if err != nil { | |
| fmt.Printf(" ❌ Error sending request for origin %s: %v\n\n", testOrigin, err) | |
| results = append(results, result{TestOrigin: testOrigin, Reason: fmt.Sprintf("Error sending request: %v", err)}) | |
| continue | |
| } | |
| defer resp.Body.Close() | |
| acaoHeader := resp.Header.Get("Access-Control-Allow-Origin") | |
| acacHeader := resp.Header.Get("Access-Control-Allow-Credentials") | |
| currentResult := result{ | |
| TestOrigin: testOrigin, | |
| ACAOHeader: acaoHeader, | |
| ACACHeader: acacHeader, | |
| } | |
| fmt.Printf(" ➡️ Access-Control-Allow-Origin: %s\n", formatHeaderValue(acaoHeader)) | |
| fmt.Printf(" ➡️ Access-Control-Allow-Credentials: %s\n", formatHeaderValue(acacHeader)) | |
| if acaoHeader == "*" { | |
| currentResult.IsPotentiallyInsecure = true | |
| currentResult.Reason = "ACAO is '*' (allows any origin)." | |
| if acacHeader == "true" { | |
| currentResult.IsMajorIssue = true | |
| currentResult.Reason += " CRITICAL: ACAC is 'true' with ACAO='*' - this is a severe misconfiguration!" | |
| majorIssueFound = true | |
| } | |
| } else if acaoHeader == testOrigin && testOrigin != actualTargetOrigin { | |
| currentResult.IsPotentiallyInsecure = true | |
| currentResult.Reason = fmt.Sprintf("ACAO reflects the sent arbitrary origin '%s'.", testOrigin) | |
| if acacHeader == "true" { | |
| currentResult.IsMajorIssue = true | |
| currentResult.Reason += " CRITICAL: ACAC is 'true' with dynamically reflected arbitrary origin!" | |
| majorIssueFound = true | |
| } | |
| } else if acaoHeader == "null" && testOrigin == "null" { | |
| currentResult.IsPotentiallyInsecure = true | |
| currentResult.Reason = "ACAO is 'null' when 'null' Origin was sent. This can be an issue for sensitive data accessible via file:// or sandboxed iframes." | |
| if acacHeader == "true" { | |
| currentResult.Reason += " NOTE: ACAC is 'true' with 'null' origin. Review implications." | |
| } | |
| } else if acaoHeader != "" && acaoHeader != actualTargetOrigin { | |
| // ACAO is a specific, different origin, but not one we sent or '*' | |
| // This is generally fine, means it's configured for a specific list. | |
| // We only flag if it matches our *test* arbitrary origins. | |
| } | |
| if currentResult.IsPotentiallyInsecure { | |
| overallInsecureDetected = true | |
| fmt.Printf(" ⚠️ Potentially Insecure: %s\n", currentResult.Reason) | |
| } else if acaoHeader == "" { | |
| fmt.Println(" ℹ️ No Access-Control-Allow-Origin header found. (Standard behavior if CORS not intended for this origin).") | |
| } else { | |
| fmt.Println(" ✅ No obvious CORS misconfiguration detected for this origin.") | |
| } | |
| fmt.Println("") // Newline for readability between origin tests | |
| results = append(results, currentResult) | |
| } | |
| fmt.Println("------------------- SUMMARY -------------------") | |
| if !overallInsecureDetected { | |
| fmt.Println("✅ No obvious insecure CORS configurations detected based on the tested origins.") | |
| fmt.Println(" Note: This tool performs basic checks. For a full assessment, consider the specific application context,") | |
| fmt.Println(" sensitivity of data, and whether OPTIONS preflight requests behave differently.") | |
| os.Exit(0) | |
| } else { | |
| if majorIssueFound { | |
| fmt.Println("🚨 CRITICAL CORS MISCONFIGURATION DETECTED! 🚨") | |
| } else { | |
| fmt.Println("⚠️ Potentially Insecure CORS Configurations Detected:") | |
| } | |
| for _, r := range results { | |
| if r.IsPotentiallyInsecure { | |
| fmt.Printf(" - For Origin '%s': %s (ACAO: '%s', ACAC: '%s')\n", | |
| r.TestOrigin, r.Reason, formatHeaderValue(r.ACAOHeader), formatHeaderValue(r.ACACHeader)) | |
| } | |
| } | |
| fmt.Println("\nRecommendations:") | |
| fmt.Println(" - Avoid using `Access-Control-Allow-Origin: *` if sensitive data or credentials are involved.") | |
| fmt.Println(" - Do not dynamically reflect arbitrary `Origin` headers in `Access-Control-Allow-Origin`.") | |
| fmt.Println(" - Use a specific whitelist of allowed origins if possible.") | |
| fmt.Println(" - Be cautious with `Access-Control-Allow-Credentials: true`; only use it with trusted, specific origins.") | |
| fmt.Println(" - Test with OPTIONS requests as well, as preflight behavior can differ.") | |
| os.Exit(1) // Exit with error code if insecurities found | |
| } | |
| } | |
| func formatHeaderValue(headerValue string) string { | |
| if headerValue == "" { | |
| return "(not set)" | |
| } | |
| return headerValue | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment