Files
god-eye/internal/security/checks.go
Vyntral 14c26dc726 feat: Add Multi-Agent AI Orchestration with 8 specialized agents
- Implement 8 specialized AI agents (XSS, SQLi, Auth, API, Crypto, Secrets, Headers, General)
- Add fast type-based routing for finding classification
- Include OWASP-aligned knowledge bases per agent
- Add agent handoff logic for cross-vulnerability detection
- Optimize timeouts and parallelism for local LLM
- Add new modules: cache, network, fingerprint, secrets, cloud, API, discovery
- Update documentation with multi-agent feature
2025-11-21 15:23:11 +01:00

174 lines
4.1 KiB
Go

package security
import (
"crypto/tls"
"fmt"
"net/http"
"strings"
"time"
)
// CheckOpenRedirect tests for open redirect vulnerabilities
func CheckOpenRedirect(subdomain string, timeout int) bool {
client := &http.Client{
Timeout: time.Duration(timeout) * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
return CheckOpenRedirectWithClient(subdomain, client)
}
// CheckCORS tests for CORS misconfiguration
func CheckCORS(subdomain string, timeout int) string {
client := &http.Client{
Timeout: time.Duration(timeout) * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
return CheckCORSWithClient(subdomain, client)
}
// CheckHTTPMethods tests which HTTP methods are allowed
func CheckHTTPMethods(subdomain string, timeout int) (allowed []string, dangerous []string) {
client := &http.Client{
Timeout: time.Duration(timeout) * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
return CheckHTTPMethodsWithClient(subdomain, client)
}
// WithClient versions for parallel execution with shared client
func CheckOpenRedirectWithClient(subdomain string, client *http.Client) bool {
testPayloads := []string{
"?url=https://evil.com",
"?redirect=https://evil.com",
"?next=https://evil.com",
"?return=https://evil.com",
}
baseURLs := []string{
fmt.Sprintf("https://%s", subdomain),
fmt.Sprintf("http://%s", subdomain),
}
for _, baseURL := range baseURLs {
for _, payload := range testPayloads {
testURL := baseURL + payload
resp, err := client.Get(testURL)
if err != nil {
continue
}
resp.Body.Close()
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
location := resp.Header.Get("Location")
// Check if redirect actually goes to evil.com, not just contains it as parameter
if strings.HasPrefix(location, "https://evil.com") ||
strings.HasPrefix(location, "http://evil.com") ||
strings.HasPrefix(location, "//evil.com") {
return true
}
}
}
}
return false
}
func CheckCORSWithClient(subdomain string, client *http.Client) string {
urls := []string{
fmt.Sprintf("https://%s", subdomain),
fmt.Sprintf("http://%s", subdomain),
}
for _, url := range urls {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
continue
}
req.Header.Set("Origin", "https://evil.com")
resp, err := client.Do(req)
if err != nil {
continue
}
resp.Body.Close()
acao := resp.Header.Get("Access-Control-Allow-Origin")
acac := resp.Header.Get("Access-Control-Allow-Credentials")
if acao == "*" {
if acac == "true" {
return "Wildcard + Credentials"
}
return "Wildcard Origin"
}
if acao == "https://evil.com" {
if acac == "true" {
return "Origin Reflection + Credentials"
}
return "Origin Reflection"
}
if strings.Contains(acao, "null") {
return "Null Origin Allowed"
}
}
return ""
}
func CheckHTTPMethodsWithClient(subdomain string, client *http.Client) (allowed []string, dangerous []string) {
urls := []string{
fmt.Sprintf("https://%s", subdomain),
fmt.Sprintf("http://%s", subdomain),
}
dangerousMethods := map[string]bool{
"PUT": true,
"DELETE": true,
"TRACE": true,
"PATCH": true,
}
for _, url := range urls {
req, err := http.NewRequest("OPTIONS", url, nil)
if err != nil {
continue
}
resp, err := client.Do(req)
if err != nil {
continue
}
resp.Body.Close()
// Only trust the Allow header from OPTIONS response
// Don't probe individual methods as this causes too many false positives
allowHeader := resp.Header.Get("Allow")
if allowHeader != "" {
for _, method := range strings.Split(allowHeader, ",") {
method = strings.TrimSpace(method)
allowed = append(allowed, method)
if dangerousMethods[method] {
dangerous = append(dangerous, method)
}
}
return allowed, dangerous
}
}
// If no Allow header found, don't report anything to avoid false positives
return allowed, dangerous
}